Physics in Babylon.js
Babylon.js supports realistic physics simulation through multiple physics engines. This allows you to create interactive environments with gravity, collisions, forces, and constraints that behave according to physical laws.
Babylon.js provides several physics engine options through a plugin architecture:
- Havok (Recommended): The default physics engine since Babylon.js 6.0. A WebAssembly-based, commercial-grade physics engine that is free and MIT-licensed for web use. Offers up to 20x faster performance over legacy engines and is the only engine supported by the modern Physics V2 API.
- Cannon.js (Legacy V1 only): Lightweight physics engine with good performance-to-accuracy balance
- Oimo.js (Legacy V1 only): Fast physics engine optimized for performance over accuracy
- Ammo.js (Legacy V1 only): WebAssembly port of Bullet Physics with advanced features
Important: The Physics V1 API (PhysicsImpostor) is deprecated. The current recommended approach uses the Physics V2 API (PhysicsAggregate, PhysicsBody, PhysicsShape) with the Havok engine. All examples below use the modern Physics V2 API.
// Install Havok: npm install @babylonjs/havok
import HavokPhysics from '@babylonjs/havok';
// Initialize Havok (async)
const havokInstance = await HavokPhysics();
// Enable physics with Havok
const gravityVector = new BABYLON.Vector3(0, -9.81, 0);
const havokPlugin = new BABYLON.HavokPlugin(true, havokInstance);
scene.enablePhysics(gravityVector, havokPlugin);Configure the physics environment for your scene:
// Setup physics with Havok (recommended)
import HavokPhysics from '@babylonjs/havok';
// Initialize Havok WASM module
const havokInstance = await HavokPhysics();
// Create physics plugin
const gravityVector = new BABYLON.Vector3(0, -9.81, 0);
const havokPlugin = new BABYLON.HavokPlugin(true, havokInstance);
// Enable physics in the scene
scene.enablePhysics(gravityVector, havokPlugin);
// Configure physics engine properties
const physicsEngine = scene.getPhysicsEngine();
physicsEngine.setTimeStep(1/60); // Fixed timestep for stable simulationAdd physics properties to meshes to make them interact with the physics world:
// Physics V2 uses PhysicsAggregate for convenience (combines body + shape)
// Create a static ground plane
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 50, height: 50}, scene);
const groundAggregate = new BABYLON.PhysicsAggregate(
ground,
BABYLON.PhysicsShapeType.BOX, // Collision shape type
{ mass: 0, restitution: 0.3, friction: 0.3 }, // mass 0 = static object
scene
);
// Create a dynamic sphere
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2}, scene);
sphere.position.y = 10; // Start above ground
const sphereAggregate = new BABYLON.PhysicsAggregate(
sphere,
BABYLON.PhysicsShapeType.SPHERE,
{ mass: 1, restitution: 0.7, friction: 0.1 }, // mass > 0 = dynamic object
scene
);
// Create a dynamic box
const box = BABYLON.MeshBuilder.CreateBox("box", {size: 2}, scene);
box.position = new BABYLON.Vector3(0, 15, 0);
const boxAggregate = new BABYLON.PhysicsAggregate(
box,
BABYLON.PhysicsShapeType.BOX,
{ mass: 2, restitution: 0.2, friction: 0.4 },
scene
);Different collision shape types for different needs:
Physics V2 provides several shape types via PhysicsShapeType:
- BOX: Simple box collision - efficient for rectangular objects
- SPHERE: Spherical collision - most efficient, good for round objects
- CYLINDER: Cylindrical collision - for tubes, pillars, etc.
- CAPSULE: Capsule collision - ideal for character controllers
- MESH: Uses actual mesh geometry - most accurate but computationally expensive
- HEIGHTFIELD: Efficient for terrain - uses height data for collision
- CONVEX_HULL: Creates simplified collision from mesh vertices - good balance
- CONTAINER: For compound shapes with child shapes
For performance, always use the simplest collision shape that adequately represents your object.
// Complex mesh with accurate collision
const complexMesh = BABYLON.MeshBuilder.CreateTorusKnot("tk", {}, scene);
complexMesh.position.y = 10;
// Options for different collision types
// 1. Exact mesh collision (expensive)
new BABYLON.PhysicsAggregate(
complexMesh, BABYLON.PhysicsShapeType.MESH,
{ mass: 3, restitution: 0.4 }, scene
);
// 2. Convex hull (better performance)
new BABYLON.PhysicsAggregate(
complexMesh, BABYLON.PhysicsShapeType.CONVEX_HULL,
{ mass: 3, restitution: 0.4 }, scene
);
// 3. Simple approximation (best performance)
new BABYLON.PhysicsAggregate(
complexMesh, BABYLON.PhysicsShapeType.SPHERE,
{ mass: 3, restitution: 0.4 }, scene
);Create complex physics objects from multiple shapes using PhysicsShapeContainer in Physics V2:
// Create a compound shape (e.g., a dumbbell) using Physics V2
const dumbbell = new BABYLON.Mesh("dumbbell", scene);
dumbbell.position.y = 10;
// Create child meshes for visualization
const bar = BABYLON.MeshBuilder.CreateCylinder("bar", {
height: 5, diameter: 0.5
}, scene);
bar.parent = dumbbell;
const leftWeight = BABYLON.MeshBuilder.CreateSphere("leftWeight", { diameter: 2 }, scene);
leftWeight.position.x = -2.5;
leftWeight.parent = dumbbell;
const rightWeight = BABYLON.MeshBuilder.CreateSphere("rightWeight", { diameter: 2 }, scene);
rightWeight.position.x = 2.5;
rightWeight.parent = dumbbell;
// Create a compound shape using PhysicsShapeContainer
const containerShape = new BABYLON.PhysicsShapeContainer(scene);
// Add child shapes with local transforms
const barShape = new BABYLON.PhysicsShapeCylinder(
new BABYLON.Vector3(0, -2.5, 0),
new BABYLON.Vector3(0, 2.5, 0),
0.25, scene
);
containerShape.addChild(barShape);
const leftSphereShape = new BABYLON.PhysicsShapeSphere(
new BABYLON.Vector3(-2.5, 0, 0), 1, scene
);
containerShape.addChild(leftSphereShape);
const rightSphereShape = new BABYLON.PhysicsShapeSphere(
new BABYLON.Vector3(2.5, 0, 0), 1, scene
);
containerShape.addChild(rightSphereShape);
// Create body with the compound shape
const body = new BABYLON.PhysicsBody(dumbbell, BABYLON.PhysicsMotionType.DYNAMIC, false, scene);
body.shape = containerShape;
body.setMassProperties({ mass: 10 });Apply physics forces to objects in your scene:
// In Physics V2, access the PhysicsBody via the aggregate
// Apply a continuous force (newtons)
sphereAggregate.body.applyForce(
new BABYLON.Vector3(0, 0, 10), // Force direction and magnitude
sphere.getAbsolutePosition() // Application point
);
// Apply an impulse (instantaneous force)
boxAggregate.body.applyImpulse(
new BABYLON.Vector3(5, 0, 0), // Impulse direction and magnitude
box.getAbsolutePosition() // Application point
);
// Set linear velocity directly
sphereAggregate.body.setLinearVelocity(new BABYLON.Vector3(0, 5, 0));
// Set angular velocity (rotation)
boxAggregate.body.setAngularVelocity(new BABYLON.Vector3(0, 2, 0));Create user-controlled physics interactions:
// Add force on key press (Physics V2)
scene.onKeyboardObservable.add((kbInfo) => {
if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
const body = sphereAggregate.body;
const pos = sphere.getAbsolutePosition();
switch (kbInfo.event.key) {
case "ArrowUp":
body.applyImpulse(new BABYLON.Vector3(0, 0, -10), pos);
break;
case "ArrowDown":
body.applyImpulse(new BABYLON.Vector3(0, 0, 10), pos);
break;
case "ArrowLeft":
body.applyImpulse(new BABYLON.Vector3(-10, 0, 0), pos);
break;
case "ArrowRight":
body.applyImpulse(new BABYLON.Vector3(10, 0, 0), pos);
break;
case " ": // Space key
body.applyImpulse(new BABYLON.Vector3(0, 10, 0), pos);
break;
}
}
});Connect physics objects with constraints (Physics V2 replaces joints with PhysicsConstraint):
// Create a hinge constraint (like a door hinge) using Physics V2
// Create a fixed anchor point
const anchor = BABYLON.MeshBuilder.CreateBox("anchor", {size: 0.5}, scene);
anchor.position = new BABYLON.Vector3(0, 5, 0);
const anchorAggregate = new BABYLON.PhysicsAggregate(
anchor, BABYLON.PhysicsShapeType.BOX,
{ mass: 0 }, scene // mass 0 = static
);
// Create a panel to act as a door
const door = BABYLON.MeshBuilder.CreateBox("door", {
width: 0.5, height: 6, depth: 4
}, scene);
door.position = new BABYLON.Vector3(2, 5, 0);
const doorAggregate = new BABYLON.PhysicsAggregate(
door, BABYLON.PhysicsShapeType.BOX,
{ mass: 5, friction: 0.5 }, scene
);
// Create a hinge constraint between anchor and door
const hingeConstraint = new BABYLON.HingeConstraint(
new BABYLON.Vector3(0, 0, 0), // pivot on anchor
new BABYLON.Vector3(-2, 0, 0), // pivot on door
new BABYLON.Vector3(0, 1, 0), // hinge axis on anchor
new BABYLON.Vector3(0, 1, 0), // hinge axis on door
scene
);
anchorAggregate.body.addConstraint(doorAggregate.body, hingeConstraint);Physics V2 constraint types available with Havok:
// Ball-and-socket constraint (allows rotation in all directions)
const ballConstraint = new BABYLON.BallAndSocketConstraint(
new BABYLON.Vector3(0, 0, 0), // pivot on body A
new BABYLON.Vector3(0, 2, 0), // pivot on body B
scene
);
bodyA.addConstraint(bodyB, ballConstraint);
// Distance constraint (maintains fixed distance)
const distConstraint = new BABYLON.DistanceConstraint(
5, // distance to maintain
scene
);
bodyA.addConstraint(bodyB, distConstraint);
// Prismatic constraint (allows movement along one axis)
const prismaticConstraint = new BABYLON.PrismaticConstraint(
new BABYLON.Vector3(0, 0, 0), // pivot on body A
new BABYLON.Vector3(0, 0, 0), // pivot on body B
new BABYLON.Vector3(1, 0, 0), // axis on body A
new BABYLON.Vector3(1, 0, 0), // axis on body B
scene
);
bodyA.addConstraint(bodyB, prismaticConstraint);
// Lock constraint (completely fixes two bodies together)
const lockConstraint = new BABYLON.LockConstraint(
new BABYLON.Vector3(0, 0, 0),
new BABYLON.Vector3(0, 2, 0),
new BABYLON.Vector3(0, 1, 0),
new BABYLON.Vector3(0, 1, 0),
scene
);
bodyA.addConstraint(bodyB, lockConstraint);Create spring-like behavior with Physics V2 constraints:
// Create spring-like behavior using Physics V2 with a 6DoF constraint
// Create anchor point (static)
const springAnchor = BABYLON.MeshBuilder.CreateSphere("anchor", {diameter: 1}, scene);
springAnchor.position.y = 15;
const anchorAgg = new BABYLON.PhysicsAggregate(
springAnchor, BABYLON.PhysicsShapeType.SPHERE,
{ mass: 0 }, scene
);
// Create suspended object (dynamic)
const bob = BABYLON.MeshBuilder.CreateSphere("bob", {diameter: 2}, scene);
bob.position.y = 10;
const bobAgg = new BABYLON.PhysicsAggregate(
bob, BABYLON.PhysicsShapeType.SPHERE,
{ mass: 2, restitution: 0.6 }, scene
);
// Create a 6 degrees of freedom constraint with spring-like limits
const constraint = new BABYLON.Physics6DoFConstraint(
{ pivotA: new BABYLON.Vector3(0, 0, 0), pivotB: new BABYLON.Vector3(0, 5, 0) },
[{ axis: BABYLON.PhysicsConstraintAxis.LINEAR_DISTANCE,
minLimit: 0, maxLimit: 8 }],
scene
);
anchorAgg.body.addConstraint(bobAgg.body, constraint);
// Visualize the spring connection with a line
let springLine = BABYLON.MeshBuilder.CreateLines("springLine", {
points: [springAnchor.position, bob.position],
updatable: true
}, scene);
// Update the line in the render loop
scene.onBeforeRenderObservable.add(() => {
springLine = BABYLON.MeshBuilder.CreateLines("springLine", {
points: [springAnchor.position, bob.position],
instance: springLine
});
});Respond to physics collisions in your scene:
// Physics V2 collision observables
sphereAggregate.body.setCollisionCallbackEnabled(true);
// Listen for collision events
const observable = sphereAggregate.body.getCollisionObservable();
observable.add((collisionEvent) => {
// collisionEvent contains collision info
const collidedBody = collisionEvent.collidedAgainst;
const point = collisionEvent.point;
const normal = collisionEvent.normal;
// Change material on collision
const material = sphere.material;
material.diffuseColor = new BABYLON.Color3(1, 0, 0); // Turn red
// Reset color after delay
setTimeout(() => {
material.diffuseColor = new BABYLON.Color3(1, 1, 1); // Back to white
}, 500);
});Create non-solid volumes that detect when objects enter them:
// Create a trigger zone
const triggerZone = BABYLON.MeshBuilder.CreateBox("trigger", {
width: 10,
height: 5,
depth: 10
}, scene);
triggerZone.position.y = 2.5;
triggerZone.visibility = 0.2; // Semi-transparent
triggerZone.material = new BABYLON.StandardMaterial("triggerMat", scene);
triggerZone.material.diffuseColor = new BABYLON.Color3(0, 1, 0); // Green
triggerZone.material.alpha = 0.2;
// Make it a trigger volume (non-solid)
triggerZone.physicsImpostor = new BABYLON.PhysicsImpostor(
triggerZone,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0, isTrigger: true },
scene
);
// Track objects in the trigger zone
const objectsInZone = new Set();
// Handle collisions with the trigger
triggerZone.physicsImpostor.onCollideEvent = (collider, collidedWith) => {
// Get the mesh associated with the colliding impostor
const collidingMesh = collidedWith.object;
// Track object entering the zone
if (!objectsInZone.has(collidingMesh)) {
objectsInZone.add(collidingMesh);
console.log(`${collidingMesh.name} entered the trigger zone!`);
// Change trigger color
triggerZone.material.diffuseColor = new BABYLON.Color3(1, 0, 0);
}
};
// Check if objects have left the zone
scene.onBeforeRenderObservable.add(() => {
for (const obj of objectsInZone) {
// Check if still intersecting
if (!triggerZone.intersectsMesh(obj)) {
objectsInZone.delete(obj);
console.log(`${obj.name} left the trigger zone!`);
// Change back to green if empty
if (objectsInZone.size === 0) {
triggerZone.material.diffuseColor = new BABYLON.Color3(0, 1, 0);
}
}
}
});Use raycasting to detect physics objects:
// Cast a ray through the physics world
const origin = new BABYLON.Vector3(0, 10, -10);
const direction = new BABYLON.Vector3(0, -1, 1).normalize();
const length = 20;
const raycastResult = scene.getPhysicsEngine().raycast(
origin,
direction,
length
);
// Check if we hit something
if (raycastResult.hasHit) {
console.log(`Hit object: ${raycastResult.body.object.name}`);
console.log(`Hit point: ${raycastResult.hitPointWorld}`);
console.log(`Hit normal: ${raycastResult.hitNormalWorld}`);
console.log(`Hit distance: ${raycastResult.hitDistance}`);
}
// Visualize the ray
const rayHelper = new BABYLON.RayHelper(new BABYLON.Ray(origin, direction, length));
rayHelper.show(scene);
// Interactive raycasting with mouse click
scene.onPointerDown = (evt, pickResult) => {
if (pickResult.hit) {
// Cast ray from camera through click point
const ray = scene.createPickingRay(
scene.pointerX,
scene.pointerY,
BABYLON.Matrix.Identity(),
camera
);
const raycastResult = scene.getPhysicsEngine().raycast(
ray.origin,
ray.direction,
100
);
if (raycastResult.hasHit) {
// Apply force at hit point
const hitBody = raycastResult.body;
const hitPoint = raycastResult.hitPointWorld;
const force = ray.direction.scale(50); // Force strength
hitBody.applyForce(force, hitPoint);
}
}
};Implement physics-based character movement:
// Create a character with capsule physics
const characterHeight = 3;
const characterRadius = 0.5;
// Create body parts
const body = BABYLON.MeshBuilder.CreateCylinder("body", {
height: characterHeight - 2 * characterRadius,
diameter: characterRadius * 2
}, scene);
const head = BABYLON.MeshBuilder.CreateSphere("head", {
diameter: characterRadius * 2
}, scene);
head.position.y = (characterHeight - 2 * characterRadius) / 2;
head.parent = body;
const feet = BABYLON.MeshBuilder.CreateSphere("feet", {
diameter: characterRadius * 2
}, scene);
feet.position.y = -(characterHeight - 2 * characterRadius) / 2;
feet.parent = body;
// Position the character
body.position.y = characterHeight / 2;
// Add physics with capsule shape (cylinder + sphere caps)
body.physicsImpostor = new BABYLON.PhysicsImpostor(
body,
BABYLON.PhysicsImpostor.CylinderImpostor,
{ mass: 1, friction: 0.5, restitution: 0.3 },
scene
);
// Variables for movement control
const moveDirection = new BABYLON.Vector3();
let isGrounded = false;
const jumpForce = 10;
const moveSpeed = 0.5;
// Input handling for character movement
const keyStatus = {};
scene.onKeyboardObservable.add((kbInfo) => {
const key = kbInfo.event.key.toLowerCase();
if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
keyStatus[key] = true;
} else if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYUP) {
keyStatus[key] = false;
}
});
// Ground detection using raycasting
function checkGrounded() {
const origin = body.getAbsolutePosition();
const direction = new BABYLON.Vector3(0, -1, 0);
const length = characterHeight / 2 + 0.1; // Slightly longer than character radius
const raycastResult = scene.getPhysicsEngine().raycast(
origin,
direction,
length
);
return raycastResult.hasHit;
}
// Character controller update
scene.onBeforeRenderObservable.add(() => {
// Check if character is on ground
isGrounded = checkGrounded();
// Reset movement direction
moveDirection.setAll(0);
// Calculate move direction from input
if (keyStatus["w"]) moveDirection.z += 1;
if (keyStatus["s"]) moveDirection.z -= 1;
if (keyStatus["a"]) moveDirection.x -= 1;
if (keyStatus["d"]) moveDirection.x += 1;
// Jump if on ground
if (keyStatus[" "] && isGrounded) {
body.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, jumpForce, 0),
body.getAbsolutePosition()
);
}
// Normalize and apply movement
if (moveDirection.length() > 0) {
moveDirection.normalize();
// Align with camera direction
const cameraDirection = camera.getTarget().subtract(camera.position);
cameraDirection.y = 0;
cameraDirection.normalize();
// Create rotation matrix from camera direction
const rotationMatrix = BABYLON.Matrix.RotationY(
Math.atan2(cameraDirection.x, cameraDirection.z)
);
// Transform movement direction relative to camera
const transformedDirection = BABYLON.Vector3.TransformNormal(
moveDirection,
rotationMatrix
);
// Apply movement as impulse (only when on ground)
if (isGrounded) {
body.physicsImpostor.applyImpulse(
transformedDirection.scale(moveSpeed),
body.getAbsolutePosition()
);
}
}
});Babylon.js includes support for advanced physics simulations:
Create physics-based vehicles with wheel constraints:
// This example uses Ammo.js which supports advanced vehicle physics
// Note: Vehicle physics implementation depends on the physics engine used
// Create a vehicle chassis
const chassisWidth = 2;
const chassisHeight = 0.6;
const chassisLength = 4;
const chassis = BABYLON.MeshBuilder.CreateBox("chassis", {
width: chassisWidth,
height: chassisHeight,
depth: chassisLength
}, scene);
chassis.position.y = 4;
// Create wheels
const wheelRadius = 0.4;
const wheelWidth = 0.3;
const wheelPositions = [
new BABYLON.Vector3(chassisWidth/2, -chassisHeight/2, chassisLength/2 - wheelRadius), // front right
new BABYLON.Vector3(-chassisWidth/2, -chassisHeight/2, chassisLength/2 - wheelRadius), // front left
new BABYLON.Vector3(chassisWidth/2, -chassisHeight/2, -chassisLength/2 + wheelRadius), // rear right
new BABYLON.Vector3(-chassisWidth/2, -chassisHeight/2, -chassisLength/2 + wheelRadius) // rear left
];
const wheels = [];
for (let i = 0; i < 4; i++) {
const wheel = BABYLON.MeshBuilder.CreateCylinder(`wheel${i}`, {
diameter: wheelRadius * 2,
height: wheelWidth,
tessellation: 24
}, scene);
wheel.rotation.z = Math.PI / 2; // Rotate to correct orientation
wheel.position = chassis.position.add(wheelPositions[i]);
wheels.push(wheel);
}
// Add physics
chassis.physicsImpostor = new BABYLON.PhysicsImpostor(
chassis,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 800, friction: 0.5, restitution: 0.2 },
scene
);
// Configure vehicle physics (Ammo.js specific)
// Note: Implementation details vary by physics engine
if (scene.getPhysicsEngine().getPhysicsPluginName() === "AmmoJSPlugin") {
const vehicle = new BABYLON.AmmoJSPlugin.Vehicle(chassis, scene);
// Add wheels to vehicle
for (let i = 0; i < 4; i++) {
const isFront = i < 2;
vehicle.addWheel(
wheels[i],
wheelPositions[i],
new BABYLON.Vector3(0, -1, 0), // Down direction
new BABYLON.Vector3(0, 0, 1), // Forward direction
0.6, // Suspension rest length
wheelRadius,
isFront // Steering wheel?
);
}
// Configure vehicle properties
vehicle.setSteeringValue(0.0); // Initial steering
vehicle.applyEngineForce(0.0); // Initial engine force
vehicle.setBrake(0.0); // Initial brake force
// Vehicle control with keyboard
scene.onKeyboardObservable.add((kbInfo) => {
const key = kbInfo.event.key;
const isDown = kbInfo.type === BABYLON.KeyboardEventTypes.KEYDOWN;
switch (key) {
case "w":
vehicle.applyEngineForce(isDown ? 1000 : 0);
break;
case "s":
vehicle.applyEngineForce(isDown ? -500 : 0);
break;
case "a":
vehicle.setSteeringValue(isDown ? 0.3 : 0);
break;
case "d":
vehicle.setSteeringValue(isDown ? -0.3 : 0);
break;
case " ":
vehicle.setBrake(isDown ? 50 : 0);
break;
}
});
}Create articulated character physics for realistic reactions:
// Create a simple ragdoll character
function createRagdoll(position) {
// Create body parts
const parts = {};
// Torso
parts.torso = BABYLON.MeshBuilder.CreateBox("torso", {
width: 1, height: 1.5, depth: 0.5
}, scene);
parts.torso.position = position.clone();
// Head
parts.head = BABYLON.MeshBuilder.CreateSphere("head", {
diameter: 0.7
}, scene);
parts.head.position = position.clone();
parts.head.position.y += 1;
// Upper arms
parts.leftUpperArm = BABYLON.MeshBuilder.CreateCylinder("leftUpperArm", {
height: 0.8, diameter: 0.25
}, scene);
parts.leftUpperArm.rotation.z = Math.PI / 2;
parts.leftUpperArm.position = position.clone();
parts.leftUpperArm.position.x -= 0.8;
parts.leftUpperArm.position.y += 0.4;
parts.rightUpperArm = BABYLON.MeshBuilder.CreateCylinder("rightUpperArm", {
height: 0.8, diameter: 0.25
}, scene);
parts.rightUpperArm.rotation.z = Math.PI / 2;
parts.rightUpperArm.position = position.clone();
parts.rightUpperArm.position.x += 0.8;
parts.rightUpperArm.position.y += 0.4;
// Lower arms
parts.leftLowerArm = BABYLON.MeshBuilder.CreateCylinder("leftLowerArm", {
height: 0.8, diameter: 0.2
}, scene);
parts.leftLowerArm.rotation.z = Math.PI / 2;
parts.leftLowerArm.position = position.clone();
parts.leftLowerArm.position.x -= 1.6;
parts.leftLowerArm.position.y += 0.4;
parts.rightLowerArm = BABYLON.MeshBuilder.CreateCylinder("rightLowerArm", {
height: 0.8, diameter: 0.2
}, scene);
parts.rightLowerArm.rotation.z = Math.PI / 2;
parts.rightLowerArm.position = position.clone();
parts.rightLowerArm.position.x += 1.6;
parts.rightLowerArm.position.y += 0.4;
// Upper legs
parts.leftUpperLeg = BABYLON.MeshBuilder.CreateCylinder("leftUpperLeg", {
height: 1, diameter: 0.3
}, scene);
parts.leftUpperLeg.position = position.clone();
parts.leftUpperLeg.position.x -= 0.3;
parts.leftUpperLeg.position.y -= 1;
parts.rightUpperLeg = BABYLON.MeshBuilder.CreateCylinder("rightUpperLeg", {
height: 1, diameter: 0.3
}, scene);
parts.rightUpperLeg.position = position.clone();
parts.rightUpperLeg.position.x += 0.3;
parts.rightUpperLeg.position.y -= 1;
// Lower legs
parts.leftLowerLeg = BABYLON.MeshBuilder.CreateCylinder("leftLowerLeg", {
height: 1, diameter: 0.25
}, scene);
parts.leftLowerLeg.position = position.clone();
parts.leftLowerLeg.position.x -= 0.3;
parts.leftLowerLeg.position.y -= 2;
parts.rightLowerLeg = BABYLON.MeshBuilder.CreateCylinder("rightLowerLeg", {
height: 1, diameter: 0.25
}, scene);
parts.rightLowerLeg.position = position.clone();
parts.rightLowerLeg.position.x += 0.3;
parts.rightLowerLeg.position.y -= 2;
// Add physics impostors to all parts
for (const [name, part] of Object.entries(parts)) {
part.physicsImpostor = new BABYLON.PhysicsImpostor(
part,
BABYLON.PhysicsImpostor.BoxImpostor, // Use box for better performance
{ mass: name === "torso" ? 3 : 1, friction: 0.5, restitution: 0.3 },
scene
);
}
// Create joints to connect the body parts
// Neck joint
const neckJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(0, 0.75, 0),
connectedPivot: new BABYLON.Vector3(0, -0.35, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.head.physicsImpostor, neckJoint);
// Shoulder joints
const leftShoulderJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(-0.5, 0.5, 0),
connectedPivot: new BABYLON.Vector3(0.4, 0, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.leftUpperArm.physicsImpostor, leftShoulderJoint);
const rightShoulderJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(0.5, 0.5, 0),
connectedPivot: new BABYLON.Vector3(-0.4, 0, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.rightUpperArm.physicsImpostor, rightShoulderJoint);
// Elbow joints
const leftElbowJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: new BABYLON.Vector3(-0.4, 0, 0),
connectedPivot: new BABYLON.Vector3(0.4, 0, 0),
mainAxis: new BABYLON.Vector3(0, 0, 1),
connectedAxis: new BABYLON.Vector3(0, 0, 1)
}
);
parts.leftUpperArm.physicsImpostor.addJoint(parts.leftLowerArm.physicsImpostor, leftElbowJoint);
const rightElbowJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: new BABYLON.Vector3(0.4, 0, 0),
connectedPivot: new BABYLON.Vector3(-0.4, 0, 0),
mainAxis: new BABYLON.Vector3(0, 0, 1),
connectedAxis: new BABYLON.Vector3(0, 0, 1)
}
);
parts.rightUpperArm.physicsImpostor.addJoint(parts.rightLowerArm.physicsImpostor, rightElbowJoint);
// Hip joints
const leftHipJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(-0.3, -0.75, 0),
connectedPivot: new BABYLON.Vector3(0, 0.5, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.leftUpperLeg.physicsImpostor, leftHipJoint);
const rightHipJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(0.3, -0.75, 0),
connectedPivot: new BABYLON.Vector3(0, 0.5, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.rightUpperLeg.physicsImpostor, rightHipJoint);
// Knee joints
const leftKneeJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: new BABYLON.Vector3(0, -0.5, 0),
connectedPivot: new BABYLON.Vector3(0, 0.5, 0),
mainAxis: new BABYLON.Vector3(1, 0, 0),
connectedAxis: new BABYLON.Vector3(1, 0, 0)
}
);
parts.leftUpperLeg.physicsImpostor.addJoint(parts.leftLowerLeg.physicsImpostor, leftKneeJoint);
const rightKneeJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: new BABYLON.Vector3(0, -0.5, 0),
connectedPivot: new BABYLON.Vector3(0, 0.5, 0),
mainAxis: new BABYLON.Vector3(1, 0, 0),
connectedAxis: new BABYLON.Vector3(1, 0, 0)
}
);
parts.rightUpperLeg.physicsImpostor.addJoint(parts.rightLowerLeg.physicsImpostor, rightKneeJoint);
return parts;
}
// Create a ragdoll
const ragdoll = createRagdoll(new BABYLON.Vector3(0, 10, 0));
// Add button to apply explosion force
const explodeButton = document.createElement("button");
explodeButton.textContent = "Apply Force";
explodeButton.style.position = "absolute";
explodeButton.style.top = "10px";
explodeButton.style.left = "10px";
document.body.appendChild(explodeButton);
explodeButton.addEventListener("click", () => {
// Apply random impulse to torso
ragdoll.torso.physicsImpostor.applyImpulse(
new BABYLON.Vector3(
(Math.random() - 0.5) * 20,
Math.random() * 10,
(Math.random() - 0.5) * 20
),
ragdoll.torso.getAbsolutePosition()
);
});Optimize physics for better performance:
// Configure physics engine for performance
const physicsEngine = scene.getPhysicsEngine();
// Set physics timestep (smaller = more accurate but slower)
physicsEngine.setTimeStep(1/60);
// Enable sub-stepping for more stable simulation
physicsEngine.setSubTimeStep(2); // 2 sub-steps per frame
// Performance techniques
// 1. Use simpler collision shapes
// Sphere and box impostors are much faster than mesh impostors
const complexMesh = BABYLON.MeshBuilder.CreateTorusKnot("complex", {}, scene);
complexMesh.position.y = 5;
// Use a bounding box instead of exact mesh shape
const boundingInfo = complexMesh.getBoundingInfo();
const dimensions = boundingInfo.boundingBox.extendSize.scale(2);
complexMesh.physicsImpostor = new BABYLON.PhysicsImpostor(
complexMesh,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 1, friction: 0.5, restitution: 0.3 },
scene
);
// 2. Use sleep states for static objects
ground.physicsImpostor.sleep(); // Put a static object to sleep
// 3. Group small objects into compound objects
const smallObjectsParent = new BABYLON.Mesh("smallObjects", scene);
for (let i = 0; i < 10; i++) {
const small = BABYLON.MeshBuilder.CreateBox(`small${i}`, {size: 0.2}, scene);
small.position.x = i * 0.3 - 1.5;
small.position.y = 2;
small.parent = smallObjectsParent;
}
// Treat them as one physics object
smallObjectsParent.physicsImpostor = new BABYLON.PhysicsImpostor(
smallObjectsParent,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 2, friction: 0.5 },
scene
);
// 4. Disable physics for distant objects
// Create a function to check distance from camera
const maxPhysicsDistance = 50; // Maximum distance for active physics
scene.onBeforeRenderObservable.add(() => {
// Get active camera position
const cameraPosition = scene.activeCamera.position;
// Check all physics objects
for (const mesh of scene.meshes) {
if (mesh.physicsImpostor) {
const distance = BABYLON.Vector3.Distance(mesh.position, cameraPosition);
// Enable/disable physics based on distance
if (distance > maxPhysicsDistance) {
if (!mesh.physicsImpostor.isDisposed) {
// Store current state before disabling
mesh._physicsEnabled = false;
mesh._linearVelocity = mesh.physicsImpostor.getLinearVelocity().clone();
mesh._angularVelocity = mesh.physicsImpostor.getAngularVelocity().clone();
mesh.physicsImpostor.sleep(); // Put to sleep to reduce computation
}
} else if (mesh.hasOwnProperty("_physicsEnabled") && mesh._physicsEnabled === false) {
// Re-enable physics and restore velocity
mesh._physicsEnabled = true;
mesh.physicsImpostor.wakeUp();
if (mesh._linearVelocity) {
mesh.physicsImpostor.setLinearVelocity(mesh._linearVelocity);
}
if (mesh._angularVelocity) {
mesh.physicsImpostor.setAngularVelocity(mesh._angularVelocity);
}
}
}
}
});Some physics engines like Ammo.js support soft body physics for cloth, jelly-like objects, and deformable surfaces:
// Note: Soft body support depends on the physics engine
// This example uses Ammo.js which has soft body capabilities
// First ensure Ammo.js is properly loaded with soft body support
// <script src="https://cdn.babylonjs.com/ammo.js"></script>
// Create a soft body cloth
async function createSoftBodyCloth() {
// Wait for Ammo initialization
await Ammo();
// Create a cloth mesh (plane with subdivisions)
const cloth = BABYLON.MeshBuilder.CreateGround("cloth", {
width: 5,
height: 5,
subdivisions: 20
}, scene);
cloth.position.y = 5;
// Create material for visualization
const clothMaterial = new BABYLON.StandardMaterial("clothMat", scene);
clothMaterial.diffuseTexture = new BABYLON.Texture("textures/fabric.jpg", scene);
clothMaterial.backFaceCulling = false;
cloth.material = clothMaterial;
// Physics initialization with Ammo.js
scene.enablePhysics(new BABYLON.Vector3(0, -9.81, 0), new BABYLON.AmmoJSPlugin());
// Configure soft body parameters
const softBodyHelperOptions = {
fixedPoints: [0, 1, 20, 21], // Fix the top corners (depends on subdivision)
mass: 1,
pressure: 100,
stiffness: 0.1,
friction: 0.8
};
// Create the soft body impostor with Ammo.js
cloth.physicsImpostor = new BABYLON.PhysicsImpostor(
cloth,
BABYLON.PhysicsImpostor.SoftImpostor,
softBodyHelperOptions,
scene
);
// Add collision with other objects
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {
diameter: 1
}, scene);
sphere.position = new BABYLON.Vector3(0, 2, 0);
sphere.physicsImpostor = new BABYLON.PhysicsImpostor(
sphere,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 2, restitution: 0.7 },
scene
);
// Apply wind force to the cloth
let time = 0;
scene.onBeforeRenderObservable.add(() => {
time += scene.getEngine().getDeltaTime() / 1000;
// Oscillating wind force
const windStrength = 5 + Math.sin(time * 0.5) * 4;
const windDirection = new BABYLON.Vector3(Math.sin(time * 0.2), 0, Math.cos(time * 0.2));
// Apply wind to soft body
cloth.physicsImpostor.applyForce(
windDirection.scale(windStrength),
cloth.position
);
});
}
// Call the function to create the soft body
createSoftBodyCloth();Visualize and debug physics to diagnose issues:
// Enable physics debug visualization
const physicsViewer = new BABYLON.PhysicsViewer(scene);
// Show impostors for specific objects
physicsViewer.showImpostor(box.physicsImpostor, box);
physicsViewer.showImpostor(sphere.physicsImpostor, sphere);
// Visualize all physics impostors in the scene
scene.meshes.forEach(mesh => {
if (mesh.physicsImpostor) {
physicsViewer.showImpostor(mesh.physicsImpostor, mesh);
}
});
// Add debugging UI
const debugLayer = new BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
const panel = new BABYLON.GUI.StackPanel();
panel.width = "220px";
panel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
panel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
debugLayer.addControl(panel);
// Button to toggle physics debug visibility
const showPhysicsButton = BABYLON.GUI.Button.CreateSimpleButton("showPhysics", "Toggle Physics Debug");
showPhysicsButton.width = "200px";
showPhysicsButton.height = "40px";
showPhysicsButton.color = "white";
showPhysicsButton.background = "green";
showPhysicsButton.onPointerUpObservable.add(() => {
// Toggle all impostor renderers
physicsViewer.isEnabled = !physicsViewer.isEnabled;
});
panel.addControl(showPhysicsButton);
// Button to pause/resume physics
const pausePhysicsButton = BABYLON.GUI.Button.CreateSimpleButton("pausePhysics", "Pause Physics");
pausePhysicsButton.width = "200px";
pausePhysicsButton.height = "40px";
pausePhysicsButton.color = "white";
pausePhysicsButton.background = "blue";
let physicsEnabled = true;
pausePhysicsButton.onPointerUpObservable.add(() => {
if (physicsEnabled) {
// Pause physics
scene.getPhysicsEngine().setTimeStep(0);
pausePhysicsButton.textBlock.text = "Resume Physics";
} else {
// Resume physics
scene.getPhysicsEngine().setTimeStep(1/60);
pausePhysicsButton.textBlock.text = "Pause Physics";
}
physicsEnabled = !physicsEnabled;
});
panel.addControl(pausePhysicsButton);
// Button to reset all physics objects
const resetPhysicsButton = BABYLON.GUI.Button.CreateSimpleButton("resetPhysics", "Reset Physics");
resetPhysicsButton.width = "200px";
resetPhysicsButton.height = "40px";
resetPhysicsButton.color = "white";
resetPhysicsButton.background = "red";
resetPhysicsButton.onPointerUpObservable.add(() => {
// Reset all dynamic objects to their original positions
scene.meshes.forEach(mesh => {
if (mesh.physicsImpostor && mesh.physicsImpostor.mass > 0) {
// Reset position and rotation
if (mesh._originalPosition) {
mesh.position = mesh._originalPosition.clone();
mesh.rotationQuaternion = mesh._originalRotation ?
mesh._originalRotation.clone() : new BABYLON.Quaternion();
// Reset velocities
mesh.physicsImpostor.setLinearVelocity(BABYLON.Vector3.Zero());
mesh.physicsImpostor.setAngularVelocity(BABYLON.Vector3.Zero());
}
}
});
});
panel.addControl(resetPhysicsButton);
// Store original positions for reset functionality
scene.meshes.forEach(mesh => {
if (mesh.physicsImpostor) {
mesh._originalPosition = mesh.position.clone();
mesh._originalRotation = mesh.rotationQuaternion ?
mesh.rotationQuaternion.clone() : null;
}
});
// Log physics activity
let collisionCount = 0;
scene.meshes.forEach(mesh => {
if (mesh.physicsImpostor) {
mesh.physicsImpostor.onCollideEvent = (collider, collidedWith) => {
collisionCount++;
console.log(`Collision #${collisionCount}: ${collider.object.name} hit ${collidedWith.object.name}`);
// Get collision velocity
const relVelocity = collider.getLinearVelocity().subtract(
collidedWith.getLinearVelocity()
).length();
console.log(`Impact velocity: ${relVelocity.toFixed(2)}`);
};
}
});Avoid common issues when working with physics in Babylon.js:
1. Scale Issues
Problem: Non-uniform scaling can cause physics behavior to be unpredictable.
Solution: Create meshes at their intended size rather than scaling after creation. If scaling is needed, create a properly sized collision shape separately.
// Bad practice: Non-uniform scaling with physics
box.scaling = new BABYLON.Vector3(1, 2, 1);
new BABYLON.PhysicsAggregate(box, BABYLON.PhysicsShapeType.BOX, { mass: 1 }, scene);
// Good practice: Create at intended size
const box = BABYLON.MeshBuilder.CreateBox("box", {width: 1, height: 2, depth: 1}, scene);
new BABYLON.PhysicsAggregate(box, BABYLON.PhysicsShapeType.BOX, { mass: 1 }, scene);
2. Mesh Parenting Issues
Problem: Physics impostors may not work correctly with parented meshes.
Solution: Apply physics to the root mesh or use compound bodies for complex hierarchies.
3. Tunneling (Objects Passing Through Each Other)
Problem: Fast-moving objects may pass through thin objects due to discrete time stepping.
Solution: Use continuous collision detection or increase the number of substeps.
Solution: Havok handles continuous collision detection automatically for fast-moving objects. You can also increase the physics substep count for more accurate simulation.
4. Performance Issues with Many Objects
Problem: Too many physics objects can severely impact performance.
Solution: Use instancing, compound bodies, and LOD for physics.
5. Stability Issues with Constraints
Problem: Joints and constraints can become unstable, causing objects to jitter or explode.
Solution: Use appropriate constraint limits, damping, and ensure proper initialization.
// Add damping to stabilize physics objects (Physics V2)
boxAggregate.body.setLinearDamping(0.1);
boxAggregate.body.setAngularDamping(0.1);
6. Collision Filtering Issues
Problem: Objects collide with things they shouldn't, or don't collide with things they should.
Solution: Use collision groups and masks to control what collides with what.
// Set collision filtering with Physics V2
// Use PhysicsShape filter groups and masks
const playerShape = playerAggregate.shape;
playerShape.filterMembershipMask = 2; // Player group
playerShape.filterCollideMask = 1; // Collide only with environment
const enemyShape = enemyAggregate.shape;
enemyShape.filterMembershipMask = 4; // Enemy group
enemyShape.filterCollideMask = 1; // Collide only with environment
7. Initialization Order Issues
Problem: Physics behaviors depend on proper initialization order.
Solution: Always ensure the Havok plugin is initialized (async) before creating aggregates, and aggregates are created before constraints.
8. Garbage Collection Pauses
Problem: Creating and destroying many physics objects can trigger garbage collection, causing stutters.
Solution: Reuse physics objects using object pooling, and dispose of unused objects properly.
// Proper disposal of physics objects (Physics V2)
function cleanupPhysicsObject(aggregate) {
aggregate.dispose(); // Disposes both body and shape
aggregate.transformNode.dispose(); // Dispose the mesh
}