⚛️ THREE.js + cannon-es Physics Lesson
Explore a 3D physics simulation with a box falling onto a plane using cannon-es
. Reset the physics anytime!
Lesson: Building a Physics Simulation
In this lesson, we will create a simple physics simulation using THREE.js for rendering and cannon-es for physics calculations.
Step 1: Setting Up the Scene
We start by creating a THREE.js scene, adding a camera, and setting up lighting. This provides the foundation for rendering 3D objects.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 6, 12);
camera.lookAt(0, 0, 0);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 15, 10);
scene.add(directionalLight);
Step 2: Adding Physics with cannon-es
Next, we set up a physics world using cannon-es. This includes defining gravity, materials, and creating physics bodies for the ground and a box.
const world = new CANNON.World({
gravity: new CANNON.Vec3(0, -9.82, 0)
});
const groundBody = new CANNON.Body({
mass: 0,
shape: new CANNON.Plane()
});
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
world.addBody(groundBody);
const boxBody = new CANNON.Body({
mass: 5,
shape: new CANNON.Box(new CANNON.Vec3(1, 1, 1)),
position: new CANNON.Vec3(0, 10, 0)
});
world.addBody(boxBody);
Step 3: Synchronizing Physics and Rendering
We synchronize the physics world with the THREE.js scene by updating the positions and rotations of the 3D objects based on the physics bodies.
function animate() {
requestAnimationFrame(animate);
world.step(1 / 60);
boxMesh.position.copy(boxBody.position);
boxMesh.quaternion.copy(boxBody.quaternion);
renderer.render(scene, camera);
}
animate();
Step 4: Adding Interactivity
We add a button to reset the simulation, allowing users to restart the physics world and reposition the box.
document.getElementById('restartBtn').addEventListener('click', () => {
world.removeBody(boxBody);
boxBody.position.set(0, 10, 0);
world.addBody(boxBody);
});
Step 5: Enhancing the Scene
Finally, we add materials, textures, and additional objects to make the scene more visually appealing.
Step 6: Collision Detection
We can detect collisions between objects in the physics world using event listeners. For example, we can log a message when the box hits the ground:
boxBody.addEventListener('collide', (event) => {
console.log('Box collided with:', event.body);
});
This is useful for triggering game logic or visual effects when objects interact.
Step 7: Debugging the Physics World
To debug the physics world, we can use a helper library like cannon-es-debugger
to visualize physics bodies:
import CannonDebugger from 'https://cdn.jsdelivr.net/npm/cannon-es-debugger/dist/cannon-es-debugger.js';
const cannonDebugger = new CannonDebugger(scene, world);
function animate() {
requestAnimationFrame(animate);
world.step(1 / 60);
cannonDebugger.update(); // Update the debugger
renderer.render(scene, camera);
}
This helps identify issues like incorrect body positions or shapes.
Step 8: Adding More Objects
We can add more objects to the scene and physics world, such as spheres or custom shapes:
const sphereShape = new CANNON.Sphere(1);
const sphereBody = new CANNON.Body({
mass: 3,
shape: sphereShape,
position: new CANNON.Vec3(2, 8, 0),
});
world.addBody(sphereBody);
const sphereGeo = new THREE.SphereGeometry(1, 32, 32);
const sphereMat = new THREE.MeshStandardMaterial({ color: 0xff5733 });
const sphereMesh = new THREE.Mesh(sphereGeo, sphereMat);
scene.add(sphereMesh);
function animate() {
requestAnimationFrame(animate);
world.step(1 / 60);
sphereMesh.position.copy(sphereBody.position);
sphereMesh.quaternion.copy(sphereBody.quaternion);
renderer.render(scene, camera);
}
Step 9: Optimizing Performance
For better performance, we can reduce the number of physics steps or use simpler shapes for collision detection:
world.step(1 / 30); // Reduce physics update frequency
Additionally, avoid adding too many high-polygon meshes to the scene.
Step 10: Exporting the Scene
We can export the scene or physics data for use in other applications:
// Export scene to JSON
const sceneData = scene.toJSON();
console.log(JSON.stringify(sceneData));
// Export physics world
const physicsData = world.bodies.map(body => ({
position: body.position,
velocity: body.velocity,
shape: body.shapes[0].type,
}));
console.log(JSON.stringify(physicsData));
This allows us to save and reload simulations.