Polyhedron
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 dodecahedron spinning on radial graph paper - the shape is transparent and the paper is white with grey marks. "
);
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();
dodecahedron(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 DODECAHEDRON = new PolyhedronDef(
[
[1, 1, 1],
[1, 1, -1],
[1, -1, 1],
[1, -1, -1],
[-1, 1, 1],
[-1, 1, -1],
[-1, -1, 1],
[-1, -1, -1],
[0, PHI, 1 / PHI],
[0, PHI, -1 / PHI],
[0, -PHI, 1 / PHI],
[0, -PHI, -1 / PHI],
[PHI, 1 / PHI, 0],
[PHI, -1 / PHI, 0],
[-PHI, 1 / PHI, 0],
[-PHI, -1 / PHI, 0],
[1 / PHI, 0, PHI],
[-1 / PHI, 0, PHI],
[1 / PHI, 0, -PHI],
[-1 / PHI, 0, -PHI]
],
[
[0, 8, 4, 17, 16],
[0, 12, 1, 9, 8],
[0, 16, 2, 13, 12],
[4, 8, 9, 5, 14],
[4, 14, 15, 6, 17],
[2, 16, 17, 6, 10],
[2, 10, 11, 3, 13],
[1, 12, 13, 3, 18],
[1, 18, 19, 5, 9],
[3, 11, 7, 19, 18],
[6, 15, 7, 11, 10],
[5, 19, 7, 15, 14]
],
{
corners: {
indices: [0, 1, 2, 3, 4, 5, 6, 7],
method: "scaleFromOrigin"
},
topPentagon: {
indices: [8, 9, 12, 1, 0],
method: "scaleFromCenter"
},
bottomPentagon: {
indices: [10, 11, 13, 2, 3],
method: "scaleFromCenter"
},
middleBand: {
indices: [16, 17, 18, 19, 14, 15],
method: "scaleFromOrigin"
}
}
);
function dodecahedron(radius = 100, cornerScale = 1, middleScale = 1) {
drawPolyhedron(DODECAHEDRON, 0, 0, radius, cornerScale, middleScale);
}
function rot() {
rotateX(-t);
rotateY(t / 2);
}