-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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