<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();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.