Skip to content

Instantly share code, notes, and snippets.

@Aakash67
Last active March 28, 2025 12:32
Show Gist options
  • Select an option

  • Save Aakash67/646b0e42bd071436ff271d1b913ccfdf to your computer and use it in GitHub Desktop.

Select an option

Save Aakash67/646b0e42bd071436ff271d1b913ccfdf to your computer and use it in GitHub Desktop.
Interactive Bouncing Balls in a 3D Cube by Claude 3.7 Sonnet (Thinking)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bouncing Balls in Transparent Cube</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000;
}
canvas {
display: block;
}
</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script>
// Initialize scene, camera, renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 15;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
// Add OrbitControls
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Create a transparent cube
const cubeSize = 10;
const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
const cubeMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.2,
side: THREE.DoubleSide
});
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);
// Add wireframe to the cube for better visibility
const wireframeGeometry = new THREE.EdgesGeometry(cubeGeometry);
const wireframeMaterial = new THREE.LineBasicMaterial({ color: 0xaaaaaa });
const wireframe = new THREE.LineSegments(wireframeGeometry, wireframeMaterial);
cube.add(wireframe);
// Create 10 balls with random colors and positions
const balls = [];
const ballRadius = 0.5;
const ballGeometry = new THREE.SphereGeometry(ballRadius, 32, 32);
// Half the cube size minus the ball radius to keep balls inside the cube initially
const positionLimit = cubeSize / 2 - ballRadius;
// Function to generate random vibrant colors
function getRandomColor() {
const hue = Math.random() * 360;
return new THREE.Color(`hsl(${hue}, 100%, 50%)`);
}
// Function to check if a position is valid (not too close to existing balls)
function isPositionValid(position) {
for (let i = 0; i < balls.length; i++) {
const dist = position.distanceTo(balls[i].position);
if (dist < ballRadius * 2.5) { // Keep some space between balls initially
return false;
}
}
return true;
}
// Create 10 balls
for (let i = 0; i < 10; i++) {
const ballMaterial = new THREE.MeshPhongMaterial({
color: getRandomColor(),
specular: 0xffffff,
shininess: 100
});
const ball = new THREE.Mesh(ballGeometry, ballMaterial);
// Keep trying until we find a valid position
let position;
do {
position = new THREE.Vector3(
(Math.random() * 2 - 1) * positionLimit,
(Math.random() * 2 - 1) * positionLimit,
(Math.random() * 2 - 1) * positionLimit
);
} while (balls.length > 0 && !isPositionValid(position));
ball.position.copy(position);
// Give each ball a random velocity
ball.velocity = new THREE.Vector3(
(Math.random() * 2 - 1) * 0.05,
(Math.random() * 2 - 1) * 0.05,
(Math.random() * 2 - 1) * 0.05
);
// Additional properties for physics
ball.userData = {
mass: Math.random() * 0.5 + 0.5, // Random mass between 0.5 and 1
};
scene.add(ball);
balls.push(ball);
}
// Add ambient light
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
// Add directional light for better visibility
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Add point lights for more dynamic lighting
const pointLight1 = new THREE.PointLight(0x0066ff, 1, 50);
pointLight1.position.set(10, 10, 10);
scene.add(pointLight1);
const pointLight2 = new THREE.PointLight(0xff6600, 1, 50);
pointLight2.position.set(-10, -10, -10);
scene.add(pointLight2);
// Mouse movement tracking
const mouse = new THREE.Vector2();
const raycaster = new THREE.Raycaster();
const mouseInfluencePoint = new THREE.Vector3();
let mouseHasInfluence = false;
function onMouseMove(event) {
// Convert mouse position to normalized device coordinates (-1 to +1)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// Update the mouse influence point in 3D space
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObject(cube);
mouseHasInfluence = intersects.length > 0;
if (mouseHasInfluence) {
mouseInfluencePoint.copy(intersects[0].point);
}
}
window.addEventListener('mousemove', onMouseMove, false);
// Handle window resize
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}, false);
// Physics constants
const gravity = new THREE.Vector3(0, -0.001, 0);
const damping = 0.99;
const bounceFactorWall = 0.7;
const bounceFactorBall = 0.8;
// Detect and handle collisions between balls
function handleBallCollisions() {
for (let i = 0; i < balls.length; i++) {
const ballA = balls[i];
for (let j = i + 1; j < balls.length; j++) {
const ballB = balls[j];
// Calculate distance between ball centers
const distance = ballA.position.distanceTo(ballB.position);
// If balls are colliding
if (distance < ballRadius * 2) {
// Calculate collision normal vector
const normal = new THREE.Vector3()
.subVectors(ballB.position, ballA.position)
.normalize();
// Calculate relative velocity
const relativeVelocity = new THREE.Vector3()
.subVectors(ballB.velocity, ballA.velocity);
// Calculate impact speed (dot product of velocity and normal)
const impactSpeed = relativeVelocity.dot(normal);
// Skip if balls are moving away from each other
if (impactSpeed > 0) continue;
// Calculate mass factors (for conservation of momentum)
const massFactorA = 2 * ballB.userData.mass / (ballA.userData.mass + ballB.userData.mass);
const massFactorB = 2 * ballA.userData.mass / (ballA.userData.mass + ballB.userData.mass);
// Apply collision impulse to velocities
ballA.velocity.add(
normal.clone().multiplyScalar(impactSpeed * massFactorA * bounceFactorBall)
);
ballB.velocity.sub(
normal.clone().multiplyScalar(impactSpeed * massFactorB * bounceFactorBall)
);
// Move balls apart to prevent sticking
const overlap = ballRadius * 2 - distance;
const correction = normal.clone().multiplyScalar(overlap * 0.5);
ballA.position.sub(correction);
ballB.position.add(correction);
}
}
}
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
// Update controls
controls.update();
// Handle ball-to-ball collisions
handleBallCollisions();
// Move balls and handle collisions with walls
const halfCubeSize = cubeSize / 2;
balls.forEach(ball => {
// Apply gravity
ball.velocity.add(gravity);
// Apply mouse influence if mouse is intersecting the cube
if (mouseHasInfluence) {
const mouseInfluence = 0.0003;
const toMouse = new THREE.Vector3().subVectors(mouseInfluencePoint, ball.position);
const distance = toMouse.length();
if (distance > 0.1) { // Prevent extreme acceleration when too close
const strength = Math.min(5 / distance, 0.03); // Stronger influence when closer
toMouse.normalize().multiplyScalar(strength * mouseInfluence);
ball.velocity.add(toMouse);
}
}
// Apply camera influence - balls are slightly attracted to where camera is looking
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
const cameraPullPoint = new THREE.Vector3().copy(camera.position).add(
cameraDirection.multiplyScalar(10)
);
const toCameraPull = new THREE.Vector3().subVectors(cameraPullPoint, ball.position);
const cameraInfluence = 0.00005;
ball.velocity.add(toCameraPull.normalize().multiplyScalar(cameraInfluence));
// Apply damping
ball.velocity.multiplyScalar(damping);
// Update position based on velocity
ball.position.add(ball.velocity);
// Check for collisions with cube walls
if (Math.abs(ball.position.x) > halfCubeSize - ballRadius) {
ball.velocity.x *= -bounceFactorWall;
ball.position.x = Math.sign(ball.position.x) * (halfCubeSize - ballRadius);
}
if (Math.abs(ball.position.y) > halfCubeSize - ballRadius) {
ball.velocity.y *= -bounceFactorWall;
ball.position.y = Math.sign(ball.position.y) * (halfCubeSize - ballRadius);
}
if (Math.abs(ball.position.z) > halfCubeSize - ballRadius) {
ball.velocity.z *= -bounceFactorWall;
ball.position.z = Math.sign(ball.position.z) * (halfCubeSize - ballRadius);
}
});
// Rotate the point lights for dynamic lighting effects
const time = Date.now() * 0.001;
pointLight1.position.x = Math.sin(time * 0.7) * 10;
pointLight1.position.y = Math.cos(time * 0.5) * 10;
pointLight1.position.z = Math.cos(time * 0.3) * 10;
pointLight2.position.x = Math.cos(time * 0.3) * 10;
pointLight2.position.y = Math.sin(time * 0.5) * 10;
pointLight2.position.z = Math.sin(time * 0.7) * 10;
// Render scene
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment