fractalkitty looking for math? fractalkitty.com

Mathober 2025

Bidiminished

#classes#3d#shader#interactive#animation#math#geometry#mathober

sqrtPHIPHI2PolyhedronDefedgesfaceedgecacheKeyverticesscaledmaxDistdistscaleVertexTransformscenterinvLenPolyhedraRendererinstancesoffsetsscalesrotationspolyRendererinitPolyhedradrawPolyhedron


let t = 0;
const sqrt = Math.sqrt;
const PHI = (1 + sqrt(5)) / 2;
const PHI2 = PHI * PHI;
function setup() {
  createCanvas(500, 500, WEBGL);
  background(255);
  describe(
    "A metabidiminished icosahedron spinning on radial graph paper - the shape is transparent and the paper is white with grey marks. Its a icosahedron that has had two of the stellated areas cut off.  "
  );
  noFill();
  rectMode(CENTER);
  background(245, 250, 255);
}

function draw() {
  ambientLight(255);
  background(245, 250, 255);
  t += 0.02;
  orbitControl();
  rotateX(0);
  rotateY(0.01);
  n = 25;
  d = width / n;

  push();
  translate(0, 0, -100);
  stroke(100, 110, 120, 40);

  for (let i = 0; i < n; i++) {
    push();
    rotate(((2 * PI) / n) * i);
    strokeWeight(0.5);
    line(-width / 1.5, 0, width / 1.5, 0);

    circle(0, 0, (width / n) * 2 * i);
    pop();
  }

  stroke(75, 85, 95, 20);

  strokeWeight(1);
  stroke(100, 110, 120, 30);
  rect(0, 0, width / 1.02, width / 1.02);
  rect(0, 0, width / 1.04, width / 1.04);
  pop();
  push();
  fill(75, 85, 95, 10);
  stroke(40, 50, 60, 40);
  rot();
  metabidiminishedIcosahedron(width / 3, 1);
  pop();
}

class PolyhedronDef {
  constructor(baseVertices, faces, vertexGroups = {}) {
    this.baseVertices = baseVertices;
    this.faces = faces;
    this.vertexGroups = vertexGroups;
    this.faceSize = faces.length > 0 ? faces[0].length : 3;
    this.cache = new Map();

    this.edges = this.calculateEdges();
  }

  calculateEdges() {
    const edges = new Set();
    for (const face of this.faces) {
      const len = face.length;
      for (let i = 0; i < len; i++) {
        const v1 = face[i];
        const v2 = face[(i + 1) % len];
        const edge = v1 < v2 ? `${v1}-${v2}` : `${v2}-${v1}`;
        edges.add(edge);
      }
    }
    return edges;
  }

  createVertices(params = {}) {
    const cacheKey = JSON.stringify(params);
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }

    const vertices = new Float32Array(this.baseVertices.flat());

    for (const [groupName, transform] of Object.entries(params)) {
      if (this.vertexGroups[groupName]) {
        const { indices, method = "scaleFromOrigin" } = this.vertexGroups[
          groupName
        ];
        VertexTransforms[method](vertices, indices, transform);
      }
    }

    this.cache.set(cacheKey, vertices);
    return vertices;
  }

  getGeometryData(radius) {
    const vertices = this.createVertices();
    const scaled = new Float32Array(vertices.length);

    let maxDist = 0;
    for (let i = 0; i < vertices.length; i += 3) {
      const dist =
        vertices[i] * vertices[i] +
        vertices[i + 1] * vertices[i + 1] +
        vertices[i + 2] * vertices[i + 2];
      if (dist > maxDist) maxDist = dist;
    }

    const scale = radius / Math.sqrt(maxDist);

    for (let i = 0; i < vertices.length; i += 3) {
      scaled[i] = vertices[i] * scale;
      scaled[i + 1] = vertices[i + 1] * scale;
      scaled[i + 2] = vertices[i + 2] * scale;
    }

    return {
      vertices: scaled,
      faces: this.faces,
      edges: this.edges
    };
  }
}

const VertexTransforms = {
  scaleFromCenter: (vertices, indices, scale) => {
    const center = [0, 0, 0];
    const len = indices.length;

    for (let i = 0; i < len; i++) {
      const idx = indices[i] * 3;
      center[0] += vertices[idx];
      center[1] += vertices[idx + 1];
      center[2] += vertices[idx + 2];
    }

    const invLen = 1 / len;
    center[0] *= invLen;
    center[1] *= invLen;
    center[2] *= invLen;

    for (let i = 0; i < len; i++) {
      const idx = indices[i] * 3;
      vertices[idx] = center[0] + (vertices[idx] - center[0]) * scale;
      vertices[idx + 1] = center[1] + (vertices[idx + 1] - center[1]) * scale;
      vertices[idx + 2] = center[2] + (vertices[idx + 2] - center[2]) * scale;
    }
  },

  scaleFromOrigin: (vertices, indices, scale) => {
    const len = indices.length;
    for (let i = 0; i < len; i++) {
      const idx = indices[i] * 3;
      vertices[idx] *= scale;
      vertices[idx + 1] *= scale;
      vertices[idx + 2] *= scale;
    }
  }
};

class PolyhedraRenderer {
  constructor() {
    this.instancedRenderer = {
      initialized: false,
      instances: [],
      shaders: {
        vert: `
          attribute vec3 aPosition;
          attribute vec3 aOffset;
          attribute vec3 aScale;
          attribute vec3 aRotation;
          
          uniform mat4 uProjectionMatrix;
          uniform mat4 uModelViewMatrix;
          
          void main() {
            float cx = cos(aRotation.x);
            float sx = sin(aRotation.x);
            float cy = cos(aRotation.y);
            float sy = sin(aRotation.y);
            
            mat4 rotX = mat4(
              1.0, 0.0, 0.0, 0.0,
              0.0, cx, -sx, 0.0,
              0.0, sx, cx, 0.0,
              0.0, 0.0, 0.0, 1.0
            );
            
            mat4 rotY = mat4(
              cy, 0.0, sy, 0.0,
              0.0, 1.0, 0.0, 0.0,
              -sy, 0.0, cy, 0.0,
              0.0, 0.0, 0.0, 1.0
            );
            
            vec4 scaledPos = vec4(aPosition * aScale, 1.0);
            vec4 rotatedPos = rotY * rotX * scaledPos;
            vec4 worldPos = rotatedPos + vec4(aOffset, 0.0);
            
            gl_Position = uProjectionMatrix * uModelViewMatrix * worldPos;
          }
        `,
        frag: `
          precision mediump float;
          uniform vec4 uColor;
          void main() {
            gl_FragColor = uColor;
          }
        `
      }
    };

    this.geometryCache = new Map();
  }

  init(p5Instance) {
    if (this.instancedRenderer.initialized) return;

    const gl = p5Instance._renderer.GL;
    this.gl = gl;
    this.p5 = p5Instance;

    this.shader = p5Instance.createShader(
      this.instancedRenderer.shaders.vert,
      this.instancedRenderer.shaders.frag
    );

    this.buffers = {
      position: gl.createBuffer(),
      offset: gl.createBuffer(),
      scale: gl.createBuffer(),
      rotation: gl.createBuffer()
    };

    this.instancedRenderer.initialized = true;
  }

  _queueInstance(shape, params) {
    this.instancedRenderer.instances.push({
      shape,
      params
    });
  }

  _render() {
    if (!this.instancedRenderer.initialized) return;

    this.p5.shader(this.shader);

    const instances = this.instancedRenderer.instances;
    const offsets = new Float32Array(instances.length * 3);
    const scales = new Float32Array(instances.length * 3);
    const rotations = new Float32Array(instances.length * 3);

    instances.forEach((instance, i) => {
      const { x, y, z, scaleX, scaleY, scaleZ, rotX, rotY } = instance.params;
      const idx = i * 3;

      offsets[idx] = x;
      offsets[idx + 1] = y;
      offsets[idx + 2] = z || 0;

      scales[idx] = scaleX;
      scales[idx + 1] = scaleY;
      scales[idx + 2] = scaleZ;

      rotations[idx] = rotX;
      rotations[idx + 1] = rotY;
      rotations[idx + 2] = 0;
    });

    const gl = this.gl;
    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.offset);
    gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.DYNAMIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.scale);
    gl.bufferData(gl.ARRAY_BUFFER, scales, gl.DYNAMIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.rotation);
    gl.bufferData(gl.ARRAY_BUFFER, rotations, gl.DYNAMIC_DRAW);

    this.instancedRenderer.instances = [];
  }
}

const polyRenderer = new PolyhedraRenderer();

function initPolyhedra() {
  polyRenderer.init(this);
}

function drawPolyhedron(shape, x, y, radius, ...params) {
  const transformParams = {};
  if (shape.vertexGroups.outerVerts && params[0]) {
    transformParams.outerVerts = params[0];
  }
  if (shape.vertexGroups.corners && params[0]) {
    transformParams.corners = params[0];
  }
  if (shape.vertexGroups.horizontalPoints && params[0]) {
    transformParams.horizontalPoints = params[0];
  }
  if (shape.vertexGroups.verticalPoints && params[1]) {
    transformParams.verticalPoints = params[1];
  }
  if (shape.vertexGroups.depthPoints && params[2]) {
    transformParams.depthPoints = params[2];
  }

  const vertices = shape.createVertices(transformParams);
  const scaled = new Float32Array(vertices.length);

  let maxDist = 0;
  for (let i = 0; i < vertices.length; i += 3) {
    const dist =
      vertices[i] * vertices[i] +
      vertices[i + 1] * vertices[i + 1] +
      vertices[i + 2] * vertices[i + 2];
    if (dist > maxDist) maxDist = dist;
  }

  const scale = radius / sqrt(maxDist);
  for (let i = 0; i < vertices.length; i += 3) {
    scaled[i] = vertices[i] * scale;
    scaled[i + 1] = vertices[i + 1] * scale;
    scaled[i + 2] = vertices[i + 2] * scale;
  }

  push();
  translate(x, y);
  for (const face of shape.faces) {
    beginShape();
    for (let vi of face) {
      const idx = vi * 3;
      vertex(scaled[idx], scaled[idx + 1], scaled[idx + 2]);
    }
    endShape(CLOSE);
  }

  noFill();
  beginShape(LINES);
  for (const edge of shape.edges) {
    const [v1, v2] = edge.split("-").map(Number);
    const idx1 = v1 * 3;
    const idx2 = v2 * 3;
    vertex(scaled[idx1], scaled[idx1 + 1], scaled[idx1 + 2]);
    vertex(scaled[idx2], scaled[idx2 + 1], scaled[idx2 + 2]);
  }
  endShape();

  beginShape(POINTS);
  for (let i = 0; i < scaled.length; i += 3) {
    vertex(scaled[i], scaled[i + 1], scaled[i + 2]);
  }
  endShape();
  pop();
}

function renderPolyhedra() {
  polyRenderer._render();
}

const METABIDIMINISHED_ICOSAHEDRON = new PolyhedronDef(
  [
    [0, 0, -1.175571],
    [1.051462, 0, 0.5257311],
    [0.3249197, 1, 0.5257311],
    [-0.8506508, 0.618034, 0.5257311],
    [-0.8506508, -0.618034, 0.5257311],
    [0.3249197, -1, 0.5257311],
    [0.8506508, 0.618034, -0.5257311],
    [0.8506508, -0.618034, -0.5257311],
    [-0.3249197, 1, -0.5257311],
    [-0.3249197, -1, -0.5257311]
  ],

  [
    [1, 5, 7],
    [1, 7, 6],
    [1, 6, 2],
    [2, 6, 8],
    [2, 8, 3],
    [4, 9, 5],
    [5, 9, 7],
    [6, 7, 0],
    [6, 0, 8],
    [7, 9, 0],

    [1, 2, 3, 4, 5],
    [0, 9, 4, 3, 8]
  ],

  {
    topPentagon: {
      indices: [1, 2, 3, 4, 5],
      method: "scaleFromCenter"
    },
    bottomPentagon: {
      indices: [0, 9, 4, 3, 8],
      method: "scaleFromCenter"
    },
    middleBand: {
      indices: [6, 7, 8, 9],
      method: "scaleFromOrigin"
    }
  }
);

function metabidiminishedIcosahedron(
  radius = 100,
  topScale = 1,
  bottomScale = 1
) {
  drawPolyhedron(
    METABIDIMINISHED_ICOSAHEDRON,
    0,
    0,
    radius,
    topScale,
    bottomScale
  );
}

function rot() {
  rotateX(-t);
  rotateY(t / 2);
}