Three.js Physics — Adding Physics Engines (Cannon-es, Ammo.js)
In this tutorial, you will learn about Three.js Physics. We cover key concepts, practical examples, and best practices to help you master this topic.
Three.js physics engines like Cannon-es and Ammo.js add real-world forces such as gravity, friction, and collision response to 3D scenes for realistic simulation.
What You'll Learn
By the end of this guide, you will integrate Cannon-es with Three.js, create physics bodies from meshes, apply forces and impulses, detect collisions, manage compound shapes, and build a physics playground.
Why Physics Matters
Static 3D scenes feel dead. Physics brings them to life. Durga Antivirus Pro uses physics for its particle explosion effect when threats are neutralized — debris scatters with gravity and bounces off walls. Physics also powers the training simulations where users practice defense tactics in realistic environments.
flowchart LR
A[Three.js Scene] --> B[Physics World]
C[Mesh Geometry] --> D[Physics Body]
A --> D
B --> D
D --> E[Forces & Gravity]
E --> F[Physics Step]
F --> G[Sync Body to Mesh]
G --> H[Render Frame]
Setting Up Cannon-es
Cannon-es is a maintained fork of the Cannon.js physics engine, compatible with modern JavaScript.
import * as THREE from 'three';
import * as CANNON from 'cannon-es';
var scene = new THREE.Scene();
var world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
world.broadphase = new CANNON.SAPBroadphase(world);
world.allowSleep = true;
Why gravity: The -9.82 value represents Earth's gravity in meters per second squared. Set to 0 for zero-G or adjust for game-specific physics.
Creating Physics Bodies
Every physics object needs a body with a shape. The shape roughly matches the mesh geometry.
var sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
var sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff6600 });
var sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.y = 5;
scene.add(sphereMesh);
var sphereShape = new CANNON.Sphere(1);
var sphereBody = new CANNON.Body({ mass: 1 });
sphereBody.addShape(sphereShape);
sphereBody.position.set(0, 5, 0);
world.addBody(sphereBody);
Expected output: A 3D sphere hovering at y=5 that drops to the ground on simulation start, bouncing slightly on impact.
Syncing Physics With Render
The physics simulation runs at a fixed timestep. Sync body positions to meshes every frame.
var clock = new THREE.Clock();
function animate() {
var delta = clock.getDelta();
world.step(1 / 60, delta, 3);
sphereMesh.position.copy(sphereBody.position);
sphereMesh.quaternion.copy(sphereBody.quaternion);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
Expected output: The sphere mesh follows the physics simulation exactly, matching position and rotation.
Why fixed timestep: Physics is sensitive to varying frame rates. A fixed 1/60 step ensures deterministic simulation regardless of display refresh rate.
Ground Plane and Collision
var groundGeo = new THREE.PlaneGeometry(20, 20);
var groundMat = new THREE.MeshStandardMaterial({ color: 0x333344, side: THREE.DoubleSide });
var groundMesh = new THREE.Mesh(groundGeo, groundMat);
groundMesh.rotation.x = -Math.PI / 2;
groundMesh.position.y = -1;
scene.add(groundMesh);
var groundShape = new CANNON.Plane();
var groundBody = new CANNON.Body({ mass: 0 });
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(groundBody);
Expected output: A ground plane visible in Three.js with a matching physics body that objects collide with and rest upon.
Why mass = 0: Static bodies (mass = 0) never move. They act as immovable collision surfaces.
Applying Forces and Impulses
sphereBody.applyForce(
new CANNON.Vec3(5, 0, 0),
sphereBody.position
);
sphereBody.applyImpulse(
new CANNON.Vec3(0, 10, 0),
sphereBody.position
);
Expected output: The sphere experiences a continuous force pushing it right and a sudden upward impulse, launching it in an arc.
Force vs Impulse: Forces apply continuously (like wind). Impulses apply instantaneously (like a punch).
Compound Shapes
Complex objects need multiple collision shapes combined.
var compoundBody = new CANNON.Body({ mass: 5 });
// Main body
var boxShape = new CANNON.Box(new CANNON.Vec3(1, 0.5, 0.5));
compoundBody.addShape(boxShape);
// Wheels
var wheelShape = new CANNON.Sphere(0.3);
compoundBody.addShape(wheelShape, new CANNON.Vec3(-0.8, -0.5, 0.6));
compoundBody.addShape(wheelShape, new CANNON.Vec3(0.8, -0.5, 0.6));
compoundBody.addShape(wheelShape, new CANNON.Vec3(-0.8, -0.5, -0.6));
compoundBody.addShape(wheelShape, new CANNON.Vec3(0.8, -0.5, -0.6));
world.addBody(compoundBody);
Expected output: A box body with four wheel-shaped collision spheres attached at the corners, acting as a single physics object.
Common Mistakes
1. Shape and Mesh Mismatch
If the physics shape is much larger or smaller than the visual mesh, objects appear to float above or sink into surfaces. Always match shape dimensions to mesh dimensions.
2. Forgetting to Sync Positions
Without copying body position/quaternion to mesh each frame, the visual mesh stays at its initial position while the physics body moves invisibly.
3. Massless Dynamic Bodies
Dynamic bodies need mass > 0. Mass = 0 means static (immovable). Trying to move a mass = 0 body with forces has no effect.
4. Using step With Variable Delta
Always use a fixed timestep (1/60). Passing the real delta causes physics instability when frame rate fluctuates.
5. Creating Physics Bodies for Every Small Object
For small, visually insignificant objects, skip physics. Use simple animation instead to save CPU.
6. Memory Leaks From Unremoved Bodies
When destroying objects, remove the physics body with world.removeBody(body) and dispose the mesh resources.
Practice Questions
Q1: Why does the physics simulation use a fixed timestep? A: Physics is sensitive to timing variations. A fixed timestep ensures deterministic, stable simulation regardless of frame rate fluctuations.
Q2: What happens when mass is set to 0? A: The body becomes static (immovable). It participates in collisions but gravity and forces do not affect it.
Q3: What is the difference between applyForce and applyImpulse? A: applyForce applies a continuous push over time. applyImpulse applies an instantaneous change in momentum.
Q4: How do you create a compound physics object? A: Create a single Body and call addShape() multiple times with different shapes and offsets.
Q5: Why must body position be copied to the mesh every frame? A: The physics simulation updates body positions internally. Without syncing, the visual mesh stays in its initial position while the invisible physics body moves.
Challenge: Build a physics simulation of a tower of blocks. Stack 10 boxes in alternating orientations, then shoot a sphere at the tower to knock it down. Count how many blocks fall off the platform.
FAQ
Try It Yourself — Physics Playground
Build a complete page with a ground plane, falling shapes of various types, and click-to-throw interaction. Physics runs at 60fps with Cannon-es.
<!DOCTYPE html>
<html>
<head>
<title>Three.js Physics Playground</title>
<style>
body { margin: 0; overflow: hidden; font-family: sans-serif; }
#controls { position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.8); color: white; padding: 15px; border-radius: 8px; }
#controls button { padding: 6px 12px; margin: 4px; cursor: pointer; }
</style>
</head>
<body>
<div id="controls">
<button onclick="dropSphere()">Drop Sphere</button>
<button onclick="dropBox()">Drop Box</button>
<button onclick="resetScene()">Reset</button>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import * as CANNON from "https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js";
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100);
camera.position.set(8, 6, 10);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
var controls = new OrbitControls(camera, renderer.domElement);
var world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
world.broadphase = new CANNON.SAPBroadphase(world);
world.allowSleep = true;
var ambient = new THREE.AmbientLight(0x404060);
scene.add(ambient);
var dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5, 10, 7);
dirLight.castShadow = true;
scene.add(dirLight);
var groundGeo = new THREE.PlaneGeometry(20, 20);
var groundMat = new THREE.MeshStandardMaterial({ color: 0x333355, side: THREE.DoubleSide });
var groundMesh = new THREE.Mesh(groundGeo, groundMat);
groundMesh.rotation.x = -Math.PI / 2;
groundMesh.receiveShadow = true;
scene.add(groundMesh);
var groundShape = new CANNON.Plane();
var groundBody = new CANNON.Body({ mass: 0 });
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(groundBody);
var physicsObjects = [];
function createPhysicsMesh(geo, mat, shape, pos, mass) {
var mesh = new THREE.Mesh(geo, mat);
mesh.position.copy(pos);
mesh.castShadow = true;
scene.add(mesh);
var body = new CANNON.Body({ mass: mass || 1 });
body.addShape(shape);
body.position.set(pos.x, pos.y, pos.z);
world.addBody(body);
physicsObjects.push({ mesh, body });
return { mesh, body };
}
window.dropSphere = function() {
var pos = new THREE.Vector3(
(Math.random() - 0.5) * 4,
8 + Math.random() * 3,
(Math.random() - 0.5) * 4
);
createPhysicsMesh(
new THREE.SphereGeometry(0.5, 32, 32),
new THREE.MeshStandardMaterial({ color: Math.random() * 0xffffff }),
new CANNON.Sphere(0.5),
pos, 1
);
};
window.dropBox = function() {
var pos = new THREE.Vector3(
(Math.random() - 0.5) * 4,
8 + Math.random() * 3,
(Math.random() - 0.5) * 4
);
createPhysicsMesh(
new THREE.BoxGeometry(0.8, 0.8, 0.8),
new THREE.MeshStandardMaterial({ color: Math.random() * 0xffffff }),
new CANNON.Box(new CANNON.Vec3(0.4, 0.4, 0.4)),
pos, 1
);
};
window.resetScene = function() {
physicsObjects.forEach(function(obj) {
scene.remove(obj.mesh);
world.removeBody(obj.body);
});
physicsObjects = [];
};
var clock = new THREE.Clock();
function animate() {
world.step(1 / 60, clock.getDelta(), 3);
physicsObjects.forEach(function(obj) {
obj.mesh.position.copy(obj.body.position);
obj.mesh.quaternion.copy(obj.body.quaternion);
});
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
window.addEventListener("resize", function() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>
What's Next
Add spatial audio to your 3D scenes for immersive experiences.
Audio — Positional audio in Three.js. VR AR — Virtual and augmented reality with Three.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro