How to Add Gravity to Three.js with Cannon.js
Integrating a physics engine like Cannon.js with Three.js allows you to bring realistic gravity, collisions, and rigid body dynamics to your 3D web applications. While Three.js handles the visual rendering of 3D objects, Cannon.js runs the mathematical physics calculations in the background. This article provides a step-by-step guide on how to set up both libraries, synchronize their coordinate systems, apply gravity, and create interactive physics-based elements in your scene.
The Core Concept
To add physics to a Three.js project, you must maintain two parallel worlds: 1. The Visual World (Three.js): Displays the objects (Meshes) on the screen. 2. The Physics World (Cannon.js): Calculates forces, positions, velocities, and collisions (Bodies) in memory.
In your animation loop, you step the physics world forward in time, extract the updated coordinates of the physics bodies, and apply those coordinates directly to your visual meshes before rendering.
Step 1: Initialize the Physics World
First, install and import Cannon.js (the modern maintained fork is
cannon-es). Create a physical world and set its gravity
vector.
import * as CANNON from 'cannon-es';
// Create a physics world
const world = new CANNON.World();
// Set gravity (Earth gravity is -9.82 m/s² on the Y-axis)
world.gravity.set(0, -9.82, 0);Step 2: Create a Static Ground
To prevent objects from falling infinitely into the void, you need a solid ground. You must create this ground in both the Three.js scene and the Cannon.js world.
import * as THREE from 'three';
// 1. Three.js Visual Ground
const floorGeometry = new THREE.PlaneGeometry(10, 10);
const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
const floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
floorMesh.rotation.x = -Math.PI * 0.5; // Rotate flat
scene.add(floorMesh);
// 2. Cannon.js Physics Ground
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body({
mass: 0, // A mass of 0 makes the body static (unmovable)
shape: floorShape
});
// Rotate the physics plane to match the visual plane
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI * 0.5);
world.addBody(floorBody);Step 3: Create a Falling Sphere
Now, create a dynamic object that will be affected by gravity, such as a sphere.
// 1. Three.js Visual Sphere
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphereMesh);
// 2. Cannon.js Physics Sphere
const sphereShape = new CANNON.Sphere(1); // Radius matches Three.js sphere
const sphereBody = new CANNON.Body({
mass: 1, // Positive mass means it is affected by gravity
shape: sphereShape,
position: new CANNON.Vec3(0, 10, 0) // Start 10 units high in the air
});
world.addBody(sphereBody);Step 4: Synchronize the Worlds in the Animation Loop
To make the sphere fall, update the Cannon.js world using a time step inside your render loop. Once the physics engine calculates the new positions, copy those coordinates over to the Three.js mesh.
const clock = new THREE.Clock();
let oldElapsedTime = 0;
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
const deltaTime = elapsedTime - oldElapsedTime;
oldElapsedTime = elapsedTime;
// Step the physics world
// Arguments: fixed time step, time passed since last frame, max sub-steps
world.step(1 / 60, deltaTime, 3);
// Copy position and rotation from Cannon.js to Three.js
sphereMesh.position.copy(sphereBody.position);
sphereMesh.quaternion.copy(sphereBody.quaternion);
// Render the visual scene
renderer.render(scene, camera);
}
animate();By linking the physical body to the visual mesh, the sphere will fall under the influence of gravity and collide realistically with the ground plane.