<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive 3D Hypercube Projection</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="info">Drag to rotate, Scroll to zoom, Right-click to pan</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module" src="script.js"></script>
</body>
</html>
body {
margin: 0;
overflow: hidden;
background-color: #050505; /* Darker background */
color: #ccc;
font-family: Arial, sans-serif;
}
canvas {
display: block; /* Prevent scrollbars */
}
#info {
position: absolute;
top: 10px;
left: 10px;
padding: 5px 10px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 5px;
font-size: 0.9em;
}
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- Three.js Setup ---
let scene, camera, renderer, controls;
let hypercubeLines, hypercubeGeometry;
let ballMeshes = [];
const clock = new THREE.Clock();
// --- Hypercube Configuration ---
const DIMENSIONS = 9;
const CUBE_SIZE = 3; // Size in 3D space units
const NUM_BALLS = 20; // Increase balls?
const BALL_RADIUS = 0.08; // Radius in 3D space units
const BALL_SPEED_SCALE = 1.5; // Speed relative to cube size
// Rotation speeds (radians per second) for 9D planes
const rotationSpeeds = [
{ dims: [0, 1], speed: 0.25 }, { dims: [2, 3], speed: 0.28 },
{ dims: [4, 5], speed: 0.31 }, { dims: [6, 7], speed: 0.34 },
{ dims: [8, 0], speed: 0.37 }, { dims: [1, 3], speed: -0.29 },
{ dims: [4, 6], speed: -0.32 }, { dims: [5, 8], speed: -0.35 },
// Add more planes for more complex rotation
{ dims: [0, 2], speed: 0.15 }, { dims: [1, 4], speed: -0.18 },
{ dims: [3, 5], speed: 0.21 }, { dims: [6, 8], speed: -0.24 },
];
const rotationAngles = new Array(rotationSpeeds.length).fill(0);
// --- Hypercube Data ---
let vertices9D = []; // Store original 9D vertex positions
let edges = []; // Store indices for edges [v1_idx, v2_idx]
let balls = []; // Store ball data { position: [9D], velocity: [9D] }
// --- Initialization ---
function init() {
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x050505);
// Camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = CUBE_SIZE * 2.5; // Adjust camera distance based on size
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Controls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // Smooth camera movement
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false; // Panning relative to camera orientation
controls.minDistance = 1;
controls.maxDistance = CUBE_SIZE * 10;
// Lighting (optional, but good for non-basic materials)
const ambientLight = new THREE.AmbientLight(0x404040); // Soft white light
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
// Generate Hypercube Structure (9D data)
generateHypercubeData();
// Create Three.js Geometry for Hypercube Lines
createHypercubeGeometry();
// Initialize Balls (9D data and 3D meshes)
initializeBalls();
// Handle Window Resize
window.addEventListener('resize', onWindowResize);
// Start Animation Loop
animate();
}
// --- Hypercube Data Generation (9D) ---
function generateHypercubeData() {
const numVertices = 1 << DIMENSIONS;
const halfSize = CUBE_SIZE / 2; // Use 3D CUBE_SIZE for scale reference
// Generate 9D Vertices
vertices9D = [];
for (let i = 0; i < numVertices; i++) {
const vertex = new Array(DIMENSIONS);
for (let d = 0; d < DIMENSIONS; d++) {
vertex[d] = ((i >> d) & 1) ? halfSize : -halfSize;
}
vertices9D.push(vertex);
}
// Generate Edges (Indices)
edges = [];
for (let i = 0; i < numVertices; i++) {
for (let j = i + 1; j < numVertices; j++) {
let diffCount = 0;
for (let d = 0; d < DIMENSIONS; d++) {
if (vertices9D[i][d] !== vertices9D[j][d]) {
diffCount++;
}
}
if (diffCount === 1) {
edges.push([i, j]);
}
}
}
console.log(`Generated ${DIMENSIONS}D Hypercube Data: ${vertices9D.length} vertices, ${edges.length} edges`);
}
// --- Three.js Hypercube Geometry ---
function createHypercubeGeometry() {
const numVertices = vertices9D.length;
const projectedVertices = new Float32Array(numVertices * 3); // x, y, z for each vertex
const colors = new Float32Array(numVertices * 3); // r, g, b for each vertex
// Initialize positions (can be anything, will be updated frame 1)
for (let i = 0; i < numVertices; i++) {
projectedVertices[i * 3] = 0;
projectedVertices[i * 3 + 1] = 0;
projectedVertices[i * 3 + 2] = 0;
// Initial color (white)
colors[i * 3] = 1;
colors[i * 3 + 1] = 1;
colors[i * 3 + 2] = 1;
}
hypercubeGeometry = new THREE.BufferGeometry();
hypercubeGeometry.setAttribute('position', new THREE.BufferAttribute(projectedVertices, 3));
hypercubeGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
// Set indices for the lines
const indices = [];
edges.forEach(edge => {
indices.push(edge[0], edge[1]);
});
hypercubeGeometry.setIndex(indices);
// Material for the lines - Enable vertex colors!
const lineMaterial = new THREE.LineBasicMaterial({
vertexColors: true, // Use colors defined in the geometry attribute
linewidth: 1.5 // Note: linewidth > 1 might not work on all platforms/drivers
});
hypercubeLines = new THREE.LineSegments(hypercubeGeometry, lineMaterial);
scene.add(hypercubeLines);
}
// --- Ball Initialization (9D Data + 3D Mesh) ---
function initializeBalls() {
const halfSize = CUBE_SIZE / 2;
const ballGeometry = new THREE.SphereGeometry(BALL_RADIUS, 16, 8); // Simple sphere
for (let i = 0; i < NUM_BALLS; i++) {
// 9D Physics Data
const position9D = new Array(DIMENSIONS);
const velocity9D = new Array(DIMENSIONS);
for (let d = 0; d < DIMENSIONS; d++) {
position9D[d] = (Math.random() - 0.5) * halfSize * 0.8; // Start near center
velocity9D[d] = (Math.random() - 0.5) * BALL_SPEED_SCALE;
}
balls.push({ position: position9D, velocity: velocity9D });
// 3D Render Mesh
// Use MeshBasicMaterial for bright colors without lighting influence
const ballMaterial = new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff });
const ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);
ballMesh.position.set(0, 0, 0); // Initial position, will be updated
ballMeshes.push(ballMesh);
scene.add(ballMesh);
}
}
// --- Rotation Logic (9D) ---
function rotatePoint9D(point, dim1, dim2, angle) {
const d1 = point[dim1];
const d2 = point[dim2];
const cosA = Math.cos(angle);
const sinA = Math.sin(angle);
point[dim1] = d1 * cosA - d2 * sinA;
point[dim2] = d1 * sinA + d2 * cosA;
}
function apply9DRotations(point9D, deltaTime) {
// Apply incremental rotation based on delta time
// Work on a copy to avoid modifying the original data structure if needed elsewhere
const rotatedPoint = [point9D];
for (let i = 0; i < rotationSpeeds.length; i++) {
const { dims, speed } = rotationSpeeds[i];
const angleIncrement = speed * deltaTime;
// Update total angle (can be useful)
rotationAngles[i] += angleIncrement;
// Rotate the point incrementally
rotatePoint9D(rotatedPoint, dims[0], dims[1], angleIncrement);
}
return rotatedPoint;
// Alternatively: If applying total rotation from original vertices:
// const rotatedPoint = [...point9D]; // Start with original
// for (let i = 0; i < rotationSpeeds.length; i++) {
// rotatePoint9D(rotatedPoint, rotationSpeeds[i].dims[0], rotationSpeeds[i].dims[1], rotationAngles[i]);
// }
// return rotatedPoint;
}
// --- Projection (9D -> 3D) ---
function project9Dto3D(point9D) {
// Simple projection: take the first 3 coordinates
// More complex projections could be used here
return new THREE.Vector3(point9D[0], point9D[1], point9D[2]);
}
// --- Ball Physics (9D) ---
function updateBallsPhysics(deltaTime) {
const halfSize = CUBE_SIZE / 2;
balls.forEach(ball => {
for (let d = 0; d < DIMENSIONS; d++) {
// Update 9D position
ball.position[d] += ball.velocity[d] * deltaTime;
// 9D Boundary Check & Bounce
const effectiveRadius = 0; // Treat as point for collision simplicity
if (ball.position[d] < -halfSize + effectiveRadius) {
ball.position[d] = -halfSize + effectiveRadius;
ball.velocity[d] *= -0.9; // Dampen bounce slightly
} else if (ball.position[d] > halfSize - effectiveRadius) {
ball.position[d] = halfSize - effectiveRadius;
ball.velocity[d] *= -0.9; // Dampen bounce slightly
}
}
});
}
// --- Animation Loop ---
function animate() {
requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
const elapsedTime = clock.getElapsedTime();
// 1. Update Controls (for mouse interaction)
controls.update();
// 2. Update Ball Physics (in 9D)
updateBallsPhysics(deltaTime);
// 3. Update Hypercube Geometry (Rotate 9D -> Project 3D -> Update Buffer)
const positionAttribute = hypercubeGeometry.attributes.position;
const colorAttribute = hypercubeGeometry.attributes.color;
const numVertices = vertices9D.length;
// -- Cool Animation: Color Pulsing --
// Cycle hue over time
const hue = (elapsedTime * 0.1) % 1; // Adjust speed with 0.1
const pulseColor = new THREE.Color();
for (let i = 0; i < numVertices; i++) {
// Rotate original 9D vertex position incrementally
// IMPORTANT: Rotate the *original* vertex data each frame for accuracy
let rotatedVertex9D = [vertices9D[i]]; // Start with original
for(let r=0; r < rotationSpeeds.length; ++r){
// Apply total accumulated angle for this frame to original vertex
rotatePoint9D(rotatedVertex9D, rotationSpeeds[r].dims[0], rotationSpeeds[r].dims[1], rotationAngles[r]);
}
// Or apply incremental (less accurate over time):
// rotatedVertex9D = apply9DRotations(vertices9D[i], deltaTime); // Incorrect if vertices9D is modified
// Project rotated 9D vertex to 3D
const projected3D = project9Dto3D(rotatedVertex9D);
// Update position buffer
positionAttribute.setXYZ(i, projected3D.x, projected3D.y, projected3D.z);
// Update color buffer for pulsing effect
// Use vertex position or index to vary the pulse phase slightly
const phaseOffset = (projected3D.x + projected3D.y + projected3D.z) * 0.1; // Small offset based on position
pulseColor.setHSL((hue + phaseOffset) % 1, 0.8, 0.6); // Saturation, Lightness
colorAttribute.setXYZ(i, pulseColor.r, pulseColor.g, pulseColor.b);
}
// Update total rotation angles AFTER using them for this frame's rotation
for (let i = 0; i < rotationSpeeds.length; i++) {
rotationAngles[i] += rotationSpeeds[i].speed * deltaTime;
}
positionAttribute.needsUpdate = true; // Flag buffers for update
colorAttribute.needsUpdate = true;
// 4. Update Ball Meshes (Rotate 9D Pos -> Project 3D -> Update Mesh)
balls.forEach((ball, index) => {
// Rotate the ball's current 9D position
let rotatedBallPos9D = [ball.position]; // Start with current physics position
for(let r=0; r < rotationSpeeds.length; ++r){
rotatePoint9D(rotatedBallPos9D, rotationSpeeds[r].dims[0], rotationSpeeds[r].dims[1], rotationAngles[r]);
}
// Or apply incremental:
// rotatedBallPos9D = apply9DRotations(ball.position, deltaTime); // Incorrect if ball.position modified by physics
// Project to 3D
const projectedBall3D = project9Dto3D(rotatedBallPos9D);
// Update the 3D mesh position
ballMeshes[index].position.copy(projectedBall3D);
// Optional: Change ball color based on speed/position?
// const speedMag = Math.sqrt(ball.velocity.reduce((sum, v) => sum + v*v, 0));
// ballMeshes[index].material.color.setHSL(speedMag * 0.01 % 1, 1, 0.5);
});
// 5. Render the Scene
renderer.render(scene, camera);
}
// --- Window Resize Handler ---
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// --- Start ---
init();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.