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.

Article illustration 1

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:

  1. Rounded Corners: We start with THREE.BoxGeometry with high segment counts, then modify vertices:
const boxGeometry = new THREE.BoxGeometry(1, 1, 1, 50, 50, 50);
  1. 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
}
  1. 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);
};
Article illustration 2

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:

  1. Sleep Detection: Use physics engine events to identify settled dice:
dice.body.addEventListener('sleep', (e) => {
  // Calculate result
});
  1. Euler Angle Analysis: Convert quaternions to determine top face:
const euler = new CANNON.Vec3();
dice.body.quaternion.toEuler(euler);
  1. 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