Transfer Impact Velocity to Debris in Matter.js

Simulating realistic destruction physics in Matter.js requires transferring the momentum and velocity of an incoming projectile to the fractured debris pieces created upon impact. This guide explains how to capture collision dynamics, generate debris bodies at the impact site, and calculate the appropriate linear and angular velocities to disperse those fragments realistically based on the projectile's trajectory and speed.

1. Capture the Collision Event

To transfer velocity, you must first detect the exact moment of impact and read the projectile’s current velocity vector before it is dampened or resolved by the engine. Matter.js provides the collisionStart event for this purpose.

Matter.Events.on(engine, 'collisionStart', (event) => {
    const pairs = event.pairs;

    for (let i = 0; i < pairs.length; i++) {
        const { bodyA, bodyB } = pairs[i];

        // Identify which body is the projectile and which is the destructible target
        if (bodyA.label === 'projectile' && bodyB.label === 'destructible') {
            shatterObject(bodyB, bodyA);
        } else if (bodyB.label === 'projectile' && bodyA.label === 'destructible') {
            shatterObject(bodyA, bodyB);
        }
    }
});

2. Read Projectile Momentum and Remove the Target

Inside the shatter handler, extract the projectile's velocity components (x and y) and position. Remove the original target body from the physics world so it can be replaced by the debris pieces.

function shatterObject(target, projectile) {
    const impactVelocity = { ...projectile.velocity };
    const targetPosition = { ...target.position };

    // Remove the original target
    Matter.Composite.remove(engine.world, target);

    // Proceed to create debris
    spawnDebris(targetPosition, impactVelocity);
}

3. Generate Debris and Transfer Velocity

When creating the fragments, do not simply apply the projectile’s exact velocity vector to every piece, as this causes the debris to move uniformly in a single clump. Instead, combine the projectile’s directional velocity with a randomized radial dispersion force.

function spawnDebris(origin, impactVelocity) {
    const fragmentCount = 6;
    const fragmentSize = 15;
    const debrisPieces = [];

    // Transfer factor determines how much projectile speed carries over (0.0 to 1.0)
    const velocityTransferFactor = 0.6;
    const scatterStrength = 3;

    for (let i = 0; i < fragmentCount; i++) {
        // Offset initial position slightly
        const offsetX = (Math.random() - 0.5) * 20;
        const offsetY = (Math.random() - 0.5) * 20;

        const fragment = Matter.Bodies.rectangle(
            origin.x + offsetX,
            origin.y + offsetY,
            fragmentSize,
            fragmentSize,
            {
                frictionAir: 0.02,
                density: 0.001,
                label: 'debris'
            }
        );

        // Calculate combined velocity: forward momentum + radial scatter
        const scatterVelocity = {
            x: (Math.random() - 0.5) * scatterStrength,
            y: (Math.random() - 0.5) * scatterStrength
        };

        const finalVelocity = {
            x: (impactVelocity.x * velocityTransferFactor) + scatterVelocity.x,
            y: (impactVelocity.y * velocityTransferFactor) + scatterVelocity.y
        };

        // Add to array
        debrisPieces.push(fragment);

        // Add to world first
        Matter.Composite.add(engine.world, fragment);

        // Apply linear velocity
        Matter.Body.setVelocity(fragment, finalVelocity);

        // Apply a random tumble (angular velocity)
        Matter.Body.setAngularVelocity(fragment, (Math.random() - 0.5) * 0.2);
    }
}

Key Considerations for Realistic Behavior