Physics V2

The Physics V2 Plugin Architecture

scene.enablePhysics(gravity, plugin) creates a PhysicsEngineV2 (gravity, time step, world queries), and the plugin, an IPhysicsEnginePluginV2, translates every call for the native engine. A simulation consists of PhysicsBody objects (motion type, mass, velocity) on TransformNodes, PhysicsShapes with a material (friction, restitution), which many bodies can share, and PhysicsConstraints joining pairs of bodies. Each scene.render() runs onBeforePhysicsObservable, the optional pre-step (node pose into body), one Havok 516,349 step, the copy of body poses back to their nodes, and onAfterPhysicsObservable, then draws.

Physics V2: from Babylon's physics objects through the engine and plugin to the Havok WASM module
Physics V2: from Babylon 89,603 's physics objects through the engine and plugin to the Havok WASM module

Physics V2 arrived with Babylon 6.0 in April 2023. V1's impostors (mesh.physicsImpostor) still work with plugins for Ammo.js 4,573 (zlib licence), cannon.js 5,002 and Oimo.js 3,175 (MIT, idle since 2023 and 2021), but only V2 has the character controller and shape casts. Three.js 30,815 has no physics API; Rapier and cannon-es synced Rapier by hand.

The Physics V2 layers as a 3D diagram: bodies, shapes and constraints, the engine, the plugin and the Havok moduleHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
  canvas { width: 100%; max-width: 600px; height: 360px; display: block; touch-action: none; outline: none; }
</style>
<canvas id="view"></canvas>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babylonjs-gui@9.28.0/babylon.gui.min.js"></script>
<script>
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#f7f3ecff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2 - 0.1, 1.4, 9.8, new BABYLON.Vector3(0.4, 1.8, 0), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.6), scene);
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui');

const slab = (name, text, width, y, hex, x = 0) => {
  const box = BABYLON.MeshBuilder.CreateBox(name, { width, height: 0.55, depth: 1.6 }, scene);
  box.position.set(x, y, 0);
  box.material = new BABYLON.StandardMaterial(name, scene);
  box.material.diffuseColor = BABYLON.Color3.FromHexString(hex);
  const label = new BABYLON.GUI.TextBlock('', text);
  Object.assign(label, { color: 'white', fontSize: 12, fontWeight: 'bold', resizeToFit: true, outlineWidth: 2, outlineColor: '#333' });
  ui.addControl(label);
  label.linkWithMesh(box);
  return box;
};
// Bottom to top: what you create, what runs it, what translates it, what simulates it
slab('body', 'PhysicsBody\nmass, velocity', 2.6, 0.3, '#1f5f8b', -2.8);
slab('shape', 'PhysicsShape\n+ material', 2.6, 0.3, '#5b3f99', 0);
slab('constraint', 'PhysicsConstraint\njoins two bodies', 2.6, 0.3, '#2a9d8f', 2.8);
slab('engine', 'PhysicsEngineV2 (scene.enablePhysics): gravity, step, queries', 8.2, 1.3, '#3f7d3a');
slab('plugin', 'HavokPlugin translates every call', 8.2, 2.3, '#e09a10');
slab('wasm', 'HavokPhysics.wasm: the native engine', 8.2, 3.3, '#b5452f');

// A marker climbing the stack once per frame: the order of one scene.render()
const STEPS = ['onBeforePhysicsObservable', 'pre-step: node pose → body', 'one Havok step',
  'body poses → nodes', 'onAfterPhysicsObservable', 'draw'];
const token = BABYLON.MeshBuilder.CreateSphere('token', { diameter: 0.35 }, scene);
token.material = new BABYLON.StandardMaterial('tokenMat', scene);
token.material.emissiveColor = BABYLON.Color3.FromHexString('#ffe082');
const stepLabel = new BABYLON.GUI.TextBlock('', '');
Object.assign(stepLabel, { color: '#333', fontSize: 14, height: '26px', top: '-10px',
  verticalAlignment: BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM });
ui.addControl(stepLabel);
const HEIGHTS = [1.3, 2.3, 3.3, 2.3, 1.3, 0.3];
scene.onBeforeRenderObservable.add(() => {
  const t = (performance.now() / 900) % STEPS.length;
  const i = Math.floor(t), f = t - i;
  const y0 = HEIGHTS[i], y1 = HEIGHTS[(i + 1) % STEPS.length];
  token.position.set(4.5,y0 + (y1 - y0) * Math.min(f * 2, 1), 0);
  stepLabel.text = `each scene.render(): ${i + 1}. ${STEPS[i]}`;
});
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>