<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive 30D Ball Physics Projection</title> <!-- Title updated -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="info">30D Ball Physics | Drag to rotate, Scroll to zoom, Right-click to pan</div> <!-- Info updated -->
    <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 ballMeshes = []; // Only ball meshes now
const clock = new THREE.Clock();

// --- Hypercube Configuration ---
const DIMENSIONS = 30; // <<<<<<< SET TO 30 DIMENSIONS
const CUBE_SIZE = 5;   // Size boundaries for bouncing (in 3D space units for reference)
const NUM_BALLS = 50;  // More balls might be okay without the hypercube rendering load
const BALL_RADIUS = 0.06; // Smaller radius perhaps
const BALL_SPEED_SCALE = 2.0; // Speed relative to cube size

// Rotation speeds (radians per second) for *some* 30D planes
// N*(N-1)/2 = 435 possible planes. Choose a few interesting ones.
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, 9], speed: 0.37 }, { dims: [10, 11], speed: 0.40 },
    { dims: [12, 13], speed: -0.29 }, { dims: [14, 15], speed: -0.32 },
    { dims: [16, 17], speed: -0.35 }, { dims: [18, 19], speed: -0.38 },
    { dims: [20, 21], speed: 0.41 }, { dims: [22, 23], speed: 0.44 },
    { dims: [24, 25], speed: -0.47 }, { dims: [26, 27], speed: -0.50 },
    { dims: [28, 29], speed: 0.53 },
    // Add some cross-dimensional rotations
    { dims: [0, 10], speed: 0.15 }, { dims: [1, 15], speed: -0.18 },
    { dims: [5, 20], speed: 0.21 }, { dims: [10, 25], speed: -0.24 },
    { dims: [15, 29], speed: 0.27 },
];
const rotationAngles = new Array(rotationSpeeds.length).fill(0);

// --- Ball Data (Physics) ---
let balls = [];    // Store ball data { position: [30D], velocity: [30D] }

// --- Initialization ---

function init() {
    // Scene
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x050505);
    // Add faint grid helper for reference
    const gridHelper = new THREE.GridHelper(CUBE_SIZE * 2, 20, 0x444444, 0x222222);
    gridHelper.position.y = -CUBE_SIZE / 2; // Place it roughly at the bottom face
    scene.add(gridHelper);
    const gridHelperXZ = new THREE.GridHelper(CUBE_SIZE * 2, 20, 0x444444, 0x222222);
    gridHelperXZ.rotation.x = Math.PI / 2;
    gridHelperXZ.position.z = -CUBE_SIZE / 2; // Place it roughly at the back face
    scene.add(gridHelperXZ);


    // Camera
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = CUBE_SIZE * 1.8; // Adjust camera distance
    camera.position.y = CUBE_SIZE * 0.5; // Look slightly down

    // 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;
    controls.dampingFactor = 0.05;
    controls.maxDistance = CUBE_SIZE * 15;
    controls.target.set(0, 0, 0); // Focus on the origin

    // Lighting
    const ambientLight = new THREE.AmbientLight(0x606060);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(1, 1.5, 1).normalize();
    scene.add(directionalLight);

    // Initialize Balls (30D data and 3D meshes)
    initializeBalls();

    // Handle Window Resize
    window.addEventListener('resize', onWindowResize);

    // Start Animation Loop
    animate();
}


// --- Ball Initialization (30D Data + 3D Mesh) ---

function initializeBalls() {
    const halfSize = CUBE_SIZE / 2;
    // More detailed sphere for smoother look
    const ballGeometry = new THREE.SphereGeometry(BALL_RADIUS, 24, 12);

    for (let i = 0; i < NUM_BALLS; i++) {
        // 30D Physics Data
        const position30D = new Array(DIMENSIONS);
        const velocity30D = new Array(DIMENSIONS);
        for (let d = 0; d < DIMENSIONS; d++) {
            position30D[d] = (Math.random() - 0.5) * halfSize * 0.9; // Start near center
            velocity30D[d] = (Math.random() - 0.5) * BALL_SPEED_SCALE;
        }
        balls.push({ position: position30D, velocity: velocity30D });

        // 3D Render Mesh
        // Use MeshPhongMaterial for shininess with lighting
        const ballMaterial = new THREE.MeshPhongMaterial({
            color: Math.random() * 0xffffff,
            shininess: 80,
        });
        const ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);
        ballMesh.position.set(0, 0, 0); // Initial position, will be updated
        ballMeshes.push(ballMesh);
        scene.add(ballMesh);
    }
    console.log(`Initialized ${NUM_BALLS} balls with ${DIMENSIONS}D physics.`);
}

// --- Rotation Logic (30D) ---

function rotatePointND(point, dim1, dim2, angle) { // Renamed for clarity
    const d1 = point[dim1];
    const d2 = point[dim2];
    // Check if dimensions exist (safety for potential array access errors)
    if (d1 === undefined || d2 === undefined) {
        console.warn(`Attempted to rotate invalid dimensions: ${dim1}, ${dim2}`);
        return;
    }
    const cosA = Math.cos(angle);
    const sinA = Math.sin(angle);
    point[dim1] = d1 * cosA - d2 * sinA;
    point[dim2] = d1 * sinA + d2 * cosA;
}


// --- Projection (30D -> 3D) ---

function projectNDto3D(pointND) { // Renamed for clarity
    // Simple projection: take the first 3 coordinates
    return new THREE.Vector3(pointND[0], pointND[1], pointND[2]);
}

// --- Ball Physics (30D) ---

function updateBallsPhysics(deltaTime) {
    const halfSize = CUBE_SIZE / 2;
    balls.forEach(ball => {
        for (let d = 0; d < DIMENSIONS; d++) { // Iterate through all 30 dimensions
            // Update 30D position
            ball.position[d] += ball.velocity[d] * deltaTime;

            // 30D Boundary Check & Bounce
            const effectiveRadius = 0; // Point collision
            if (ball.position[d] < -halfSize + effectiveRadius) {
                ball.position[d] = -halfSize + effectiveRadius;
                ball.velocity[d] *= -0.85; // Slightly more damping on bounce
            } else if (ball.position[d] > halfSize - effectiveRadius) {
                ball.position[d] = halfSize - effectiveRadius;
                ball.velocity[d] *= -0.85; // Slightly more damping on bounce
            }
        }
    });
}

// --- Animation Loop ---

function animate() {
    requestAnimationFrame(animate);

    const deltaTime = Math.min(0.05, clock.getDelta()); // Clamp delta time
    // const elapsedTime = clock.getElapsedTime(); // Can be used for effects

    // 1. Update Controls
    controls.update();

    // 2. Update Ball Physics (in 30D)
    updateBallsPhysics(deltaTime);

    // 3. Update Ball Meshes (Rotate 30D Pos -> Project 3D -> Update Mesh)
    balls.forEach((ball, index) => {
        // Rotate the ball's current 30D position using accumulated angles
        let rotatedBallPosND = [...ball.position]; // Start with current physics position
        for(let r=0; r < rotationSpeeds.length; ++r){
             // Use total accumulated angle for consistency
             rotatePointND(rotatedBallPosND, rotationSpeeds[r].dims[0], rotationSpeeds[r].dims[1], rotationAngles[r]);
        }

        // Project the first 3 dimensions to 3D space
        const projectedBall3D = projectNDto3D(rotatedBallPosND);

        // Update the 3D mesh position
        ballMeshes[index].position.copy(projectedBall3D);

        // Optional: Make balls fade slightly when near the 3D projection boundaries?
        // const distFromCenterSq = projectedBall3D.lengthSq();
        // const maxDistSq = (CUBE_SIZE/2)**2;
        // ballMeshes[index].material.opacity = THREE.MathUtils.smoothstep(distFromCenterSq, maxDistSq, maxDistSq*0.8);
        // ballMeshes[index].material.transparent = true; // Need this for opacity
    });

    // 4. Update total rotation angles AFTER applying them for this frame
    for (let i = 0; i < rotationSpeeds.length; i++) {
       rotationAngles[i] += rotationSpeeds[i].speed * deltaTime;
    }

    // 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();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.