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:
- Cannon.js: Lightweight physics engine with good performance-to-accuracy balance
- Oimo.js: Fast physics engine optimized for performance over accuracy
- Ammo.js: Powerful engine (WebAssembly port of Bullet Physics) with advanced features
- Havok: Commercial-grade physics with premium performance (requires license)
Each engine offers different trade-offs between performance, accuracy, feature set, and ease of use. For most projects, Cannon.js provides a good balance and is the recommended starting point.
// Import the physics engine (Cannon.js example)
// In HTML: <script src="https://cdn.babylonjs.com/cannon.js"></script>
// Or in modules: import * as CANNON from 'cannon';
// Enable physics with Cannon.js
const gravityVector = new BABYLON.Vector3(0, -9.81, 0);
const physicsPlugin = new BABYLON.CannonJSPlugin();
scene.enablePhysics(gravityVector, physicsPlugin);
Configure the physics environment for your scene:
// Setup physics with Ammo.js
// First, ensure Ammo.js is loaded
// <script src="https://cdn.babylonjs.com/ammo.js"></script>
// Initialize Ammo
await Ammo();
// Create physics plugin
const gravityVector = new BABYLON.Vector3(0, -9.81, 0);
const physicsPlugin = new BABYLON.AmmoJSPlugin();
// Enable physics in the scene
scene.enablePhysics(gravityVector, physicsPlugin);
// Configure physics engine properties
scene.getPhysicsEngine().setSubTimeStep(0.01); // For more accurate simulation
// Set global collision margin (Ammo.js specific)
physicsPlugin.setDefaultNonContactCustomMaterial({
friction: 0.3,
restitution: 0.3
});
Add physics properties to meshes to make them interact with the physics world:
// Create a ground plane with physics
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 50, height: 50}, scene);
ground.physicsImpostor = new BABYLON.PhysicsImpostor(
ground,
BABYLON.PhysicsImpostor.BoxImpostor, // Use box collision for ground
{ 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
sphere.physicsImpostor = new BABYLON.PhysicsImpostor(
sphere,
BABYLON.PhysicsImpostor.SphereImpostor,
{ 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);
box.physicsImpostor = new BABYLON.PhysicsImpostor(
box,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 2, restitution: 0.2, friction: 0.4 },
scene
);
Different collision shapes for different needs:
Babylon.js provides several impostor types for different collision needs:
- BoxImpostor: Simple box collision - efficient for rectangular objects
- SphereImpostor: Spherical collision - most efficient, good for round objects
- CylinderImpostor: Cylindrical collision - for tubes, pillars, etc.
- MeshImpostor: Uses actual mesh geometry - most accurate but computationally expensive
- HeightmapImpostor: Efficient for terrain - uses height data for collision
- ConvexHullImpostor: Creates simplified collision from mesh vertices - good balance
- ParticleImpostor: Point with mass but no rotation - for simple particles
- NoImpostor: For compound objects with child impostors
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)
complexMesh.physicsImpostor = new BABYLON.PhysicsImpostor(
complexMesh,
BABYLON.PhysicsImpostor.MeshImpostor,
{ mass: 3, restitution: 0.4 },
scene
);
// 2. Convex hull (better performance)
complexMesh.physicsImpostor = new BABYLON.PhysicsImpostor(
complexMesh,
BABYLON.PhysicsImpostor.ConvexHullImpostor,
{ mass: 3, restitution: 0.4 },
scene
);
// 3. Simple approximation (best performance)
complexMesh.physicsImpostor = new BABYLON.PhysicsImpostor(
complexMesh,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 3, restitution: 0.4 },
scene
);
Create complex physics objects from multiple shapes:
// Create a compound object (e.g., a dumbbell)
const dumbbell = new BABYLON.Mesh("dumbbell", scene);
// Create the middle bar
const bar = BABYLON.MeshBuilder.CreateCylinder("bar", {
height: 5,
diameter: 0.5
}, scene);
// Create the weights
const leftWeight = BABYLON.MeshBuilder.CreateSphere("leftWeight", {
diameter: 2
}, scene);
leftWeight.position.x = -2.5;
const rightWeight = BABYLON.MeshBuilder.CreateSphere("rightWeight", {
diameter: 2
}, scene);
rightWeight.position.x = 2.5;
// Make the parts children of the main mesh
bar.parent = dumbbell;
leftWeight.parent = dumbbell;
rightWeight.parent = dumbbell;
// Position the dumbbell
dumbbell.position.y = 10;
// Create compound physics impostor
dumbbell.physicsImpostor = new BABYLON.PhysicsImpostor(
dumbbell,
BABYLON.PhysicsImpostor.NoImpostor, // Parent has no direct impostor
{ mass: 10 },
scene
);
// Add child impostors
bar.physicsImpostor = new BABYLON.PhysicsImpostor(
bar,
BABYLON.PhysicsImpostor.CylinderImpostor,
{ mass: 0 }, // Child masses are ignored when part of compound
scene
);
leftWeight.physicsImpostor = new BABYLON.PhysicsImpostor(
leftWeight,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 0 },
scene
);
rightWeight.physicsImpostor = new BABYLON.PhysicsImpostor(
rightWeight,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 0 },
scene
);
Apply physics forces to objects in your scene:
// Apply a continuous force (newtons)
sphere.physicsImpostor.applyForce(
new BABYLON.Vector3(0, 0, 10), // Force direction and magnitude
sphere.getAbsolutePosition() // Application point
);
// Apply an impulse (instantaneous force)
box.physicsImpostor.applyImpulse(
new BABYLON.Vector3(5, 0, 0), // Impulse direction and magnitude
box.getAbsolutePosition() // Application point
);
// Set linear velocity directly
sphere.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0, 5, 0));
// Set angular velocity (rotation)
box.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0, 2, 0));
// Apply a torque (rotational force)
box.physicsImpostor.applyTorque(new BABYLON.Vector3(0, 10, 0));
Create user-controlled physics interactions:
// Add force on key press
scene.onKeyboardObservable.add((kbInfo) => {
if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
switch (kbInfo.event.key) {
case "ArrowUp":
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, 0, -10),
sphere.getAbsolutePosition()
);
break;
case "ArrowDown":
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, 0, 10),
sphere.getAbsolutePosition()
);
break;
case "ArrowLeft":
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(-10, 0, 0),
sphere.getAbsolutePosition()
);
break;
case "ArrowRight":
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(10, 0, 0),
sphere.getAbsolutePosition()
);
break;
case " ": // Space key
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, 10, 0), // Jump
sphere.getAbsolutePosition()
);
break;
}
}
});
Connect physics objects with various types of joints:
// Create a hinge joint (like a door hinge)
const hingePivot = new BABYLON.Vector3(0, 5, 0);
const hingeAxis = new BABYLON.Vector3(0, 1, 0); // Rotate around Y axis
const hingeJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: hingePivot,
connectedPivot: new BABYLON.Vector3(0, 0, 0),
mainAxis: hingeAxis,
connectedAxis: hingeAxis
}
);
// Connect a box to a fixed anchor point
const anchor = BABYLON.MeshBuilder.CreateBox("anchor", {size: 0.5}, scene);
anchor.position = hingePivot;
anchor.physicsImpostor = new BABYLON.PhysicsImpostor(
anchor,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0 }, // Fixed point (zero mass)
scene
);
// 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);
door.physicsImpostor = new BABYLON.PhysicsImpostor(
door,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 5, friction: 0.5 },
scene
);
// Connect the door to the anchor with a hinge joint
anchor.physicsImpostor.addJoint(door.physicsImpostor, hingeJoint);
Other types of physical joints and constraints:
// Ball-and-socket joint (allows rotation in all directions)
const ballJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(0, 0, 0),
connectedPivot: new BABYLON.Vector3(0, 2, 0)
}
);
// Distance joint (maintains fixed distance between objects)
const distanceJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.DistanceJoint, {
maxDistance: 5 // Maximum distance constraint
}
);
// Prismatic joint (allows movement along one axis only)
const prismaticJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.PrismaticJoint, {
mainAxis: new BABYLON.Vector3(1, 0, 0), // Movement along X axis
connectedAxis: new BABYLON.Vector3(1, 0, 0)
}
);
// Slider joint with limits
const sliderJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.SliderJoint, {
mainAxis: new BABYLON.Vector3(0, 1, 0),
connectedAxis: new BABYLON.Vector3(0, 1, 0)
}
);
sliderJoint.setLimit(1, 5); // Min and max distance
Create spring-like behavior with physics:
// Create a spring joint
const spring = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.SpringJoint, {
length: 5, // Rest length
stiffness: 50, // Spring strength
damping: 0.5, // Damping factor
collision: true // Allow connected bodies to collide
}
);
// Create anchor point
const springAnchor = BABYLON.MeshBuilder.CreateSphere("anchor", {diameter: 1}, scene);
springAnchor.position.y = 15;
springAnchor.physicsImpostor = new BABYLON.PhysicsImpostor(
springAnchor,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 0 }, // Fixed position
scene
);
// Create suspended object
const bob = BABYLON.MeshBuilder.CreateSphere("bob", {diameter: 2}, scene);
bob.position.y = 10;
bob.physicsImpostor = new BABYLON.PhysicsImpostor(
bob,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 2, restitution: 0.6 },
scene
);
// Connect with spring joint
springAnchor.physicsImpostor.addJoint(bob.physicsImpostor, spring);
// Visualize the spring connection with a line
const 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:
// Add collision event handler
sphere.physicsImpostor.onCollideEvent = (collider, collidedWith) => {
// collider is the sphere's impostor
// collidedWith is the other object's impostor
// 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);
// Play sound on collision
const impactVelocity = collider.getLinearVelocity().length();
if (impactVelocity > 1) {
const collisionSound = new BABYLON.Sound(
"collision",
"sounds/collision.mp3",
scene,
null,
{ volume: Math.min(impactVelocity / 10, 1) }
);
collisionSound.play();
}
};
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 7 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: Use mesh.scaling.isEqual(BABYLON.Vector3.One()) to check for uniform scaling. If non-uniform scaling is needed, create a properly sized collision mesh as a child of the visual mesh.
// Bad practice: Non-uniform scaling with physics
box.scaling = new BABYLON.Vector3(1, 2, 1);
box.physicsImpostor = new BABYLON.PhysicsImpostor(...);
// Good practice: Use a separate collision mesh
const visualMesh = BABYLON.MeshBuilder.CreateBox("visual", {width: 1, height: 2, depth: 1}, scene);
const collisionMesh = BABYLON.MeshBuilder.CreateBox("collision", {size: 1}, scene);
collisionMesh.scaling = new BABYLON.Vector3(1, 2, 1);
collisionMesh.visibility = 0; // Invisible collision mesh
collisionMesh.physicsImpostor = new BABYLON.PhysicsImpostor(...);
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.
// Enable CCD (Continuous Collision Detection) for fast objects
// Note: Implementation depends on physics engine
sphere.physicsImpostor.physicsBody.setCcdMotionThreshold(1);
sphere.physicsImpostor.physicsBody.setCcdSweptSphereRadius(0.2);
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
box.physicsImpostor.setLinearDamping(0.1);
box.physicsImpostor.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 groups (implementation varies by physics engine)
playerMesh.physicsImpostor.physicsBody.collisionFilterGroup = 2; // Player group
playerMesh.physicsImpostor.physicsBody.collisionFilterMask = 1; // Collide only with environment
enemyMesh.physicsImpostor.physicsBody.collisionFilterGroup = 4; // Enemy group
enemyMesh.physicsImpostor.physicsBody.collisionFilterMask = 1; // Collide only with environment
7. Initialization Order Issues
Problem: Physics behaviors depend on proper initialization order.
Solution: Always ensure physics engine is initialized before creating impostors, and impostors are created before joints/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
function cleanupPhysicsObject(mesh) {
if (mesh.physicsImpostor) {
mesh.physicsImpostor.dispose();
mesh.physicsImpostor = null;
}
mesh.dispose();
}