function draw() {
background(0); // Black background
// Update rotation angles if auto-rotate is on
if (autoRotate) {
angleXW += rotationSpeedXW;
angleYZ += rotationSpeedYZ;
}
// --- 4D Rotation ---
// Create rotation matrices for x-w and y-z planes
// Matrix for x-w plane rotation:
// [ cos(a) 0 0 -sin(a) ]
// [ 0 1 0 0 ]
// [ 0 0 1 0 ]
// [ sin(a) 0 0 cos(a) ]
const matXW = [
[cos(angleXW), 0, 0, -sin(angleXW)],
[0, 1, 0, 0],
[0, 0, 1, 0],
[sin(angleXW), 0, 0, cos(angleXW)]
];
// Matrix for y-z plane rotation:
// [ 1 0 0 0 ]
// [ 0 cos(a) -sin(a) 0 ]
// [ 0 sin(a) cos(a) 0 ]
// [ 0 0 0 1 ]
const matYZ = [
[1, 0, 0, 0],
[0, cos(angleYZ), -sin(angleYZ), 0],
[0, sin(angleYZ), cos(angleYZ), 0],
[0, 0, 0, 1]
];
// Combine rotations by multiplying matrices (order matters!)
// Applying YZ rotation, then XW rotation
const combinedMatrix = multiplyMatrices(matXW, matYZ);
// Apply the combined rotation to each 4D vertex
let rotatedVertices4D = vertices4D.map(v => multiplyMatrixVector(combinedMatrix, v));
// --- 4D to 3D Perspective Projection ---
// Project each rotated 4D vertex to 3D space
// Perspective projection based on 'w' coordinate:
// x_proj = x / (distance - w)
// y_proj = y / (distance - w)
// z_proj = z / (distance - w)
let projectedVertices3D = rotatedVertices4D.map(v => {
// Avoid division by zero or very small numbers
let scaleFactor = 1 / (projectionDistance - v[3]);
return createVector(
v[0] * scaleFactor * hypercubeSize,
v[1] * scaleFactor * hypercubeSize,
v[2] * scaleFactor * hypercubeSize
);
});
// --- Render Hypercube Wireframe ---
// Draw edges with a base stroke
stroke(255); // White stroke
strokeWeight(1);
beginShape(LINES);
for (let edge of edges) {
let v1 = projectedVertices3D[edge[0]];
let v2 = projectedVertices3D[edge[1]];
vertex(v1.x, v1.y, v1.z);
vertex(v2.x, v2.y, v2.z);
}
endShape();
// Draw edges again with additive blend mode for glow effect
blendMode(ADD); // Switch to additive blending
stroke(0, 255, 255, 50); // Cyan stroke with some transparency
strokeWeight(3);
beginShape(LINES);
for (let edge of edges) {
let v1 = projectedVertices3D[edge[0]];
let v2 = projectedVertices3D[edge[1]];
vertex(v1.x, v1.y, v1.z);
vertex(v2.x, v2.y, v2.z);
}
endShape();
blendMode(BLEND); // Reset blend mode to default
// --- Draw UI Text (2D Overlay) ---
// In WEBGL mode, to draw 2D text on top, we switch to 2D projection using ortho()
push();
ortho(); // Switch to 2D orthographic projection
translate(-width / 2, -height / 2); // Move origin to top-left for 2D UI
fill(255); // White text
noStroke(); // No stroke for text
text("Controls:", 20, 30);
text("Drag mouse: Change rotation speed", 20, 50);
text("Click: Toggle auto-rotate (" + (autoRotate ? "ON" : "OFF") + ")", 20, 70);
text("Press 'R': Randomize rotation speeds", 20, 90);
text("Rotation Speed XW: " + rotationSpeedXW.toFixed(4), 20, 120);
text("Rotation Speed YZ: " + rotationSpeedYZ.toFixed(4), 20, 140);
pop(); // Restore 3D projection
}