Crafting a Physics-Driven 3D Dice Roller with Three.js and Cannon-es
Share this article
The Art of Digital Dice: Building Physics-Driven Interactions
Creating a realistic 3D dice roller requires fusing two critical web technologies: Three.js for rendering and cannon-es for physics simulation. Unlike simple CSS animations, this approach delivers authentic physics behavior—spin, bounce, and collision detection—that mimics real-world dice dynamics.
The final interactive dice roller with physics-based movement (Source: Codrops)
Engineering Custom Dice Geometry
While pre-made 3D models are convenient, building dice programmatically unlocks deeper control:
- Rounded Corners: We start with
THREE.BoxGeometrywith high segment counts, then modify vertices:
const boxGeometry = new THREE.BoxGeometry(1, 1, 1, 50, 50, 50);
- Vertex Manipulation: For corner rounding, we categorize vertices by proximity to edges and apply spherical interpolation:
if (Math.abs(position.x) > threshold &&
Math.abs(position.y) > threshold &&
Math.abs(position.z) > threshold) {
// Round vertex using vector normalization
}
- Notch Creation: Dice pips are crafted using a cosine-based wave function projected onto each face:
const notchWave = (v) => {
v = (1 / radius) * v;
return depth * (Math.cos(Math.PI * v) + 1);
};
RoundedBoxGeometry provides pre-made rounded corners but lacks notch customization (Source: Codrops)
Physics Integration Challenges
Three.js renders visuals—but needs cannon-es for realistic motion:
- World Setup: Initialize physics with gravity and materials:
const physicsWorld = new CANNON.World({
gravity: new CANNON.Vec3(0, -50, 0),
allowSleep: true
});
- Body-Mesh Sync: Physics bodies mirror visual meshes:
function render() {
physicsWorld.fixedStep();
dice.mesh.position.copy(dice.body.position);
dice.mesh.quaternion.copy(dice.body.quaternion);
}
- Realistic Throws: Apply randomized impulses with torque:
dice.body.applyImpulse(
new CANNON.Vec3(-force, force, 0),
new CANNON.Vec3(0, 0, 0.2) // Off-center for spin
);
Determining Dice Results
The true challenge lies in detecting final positions:
- Sleep Detection: Use physics engine events to identify settled dice:
dice.body.addEventListener('sleep', (e) => {
// Calculate result
});
- Euler Angle Analysis: Convert quaternions to determine top face:
const euler = new CANNON.Vec3();
dice.body.quaternion.toEuler(euler);
- Orientation Logic: Map rotations to dice values using angular thresholds:
if (isHalfPi(euler.z)) return 2;
if (isMinusHalfPi(euler.z)) return 5;
// Additional face checks...
Why This Matters for Developers
Beyond gaming, this technique demonstrates:
- Advanced Geometry Manipulation: Transforming primitive shapes
- Physics-Renderer Synchronization: Critical for interactive simulations
- Real-World Math Applications: Vector math for collision and rotation
The complete project available on GitHub showcases these techniques in action. As web-based 3D experiences grow more sophisticated, mastering these foundational skills becomes essential for creating immersive interactions.
Adapted from Codrops tutorial: Crafting a Dice Roller with Three.js and Cannon-es