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();

}