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
- Mass and Momentum Ratios: To adhere closer to real
physics (\(p = mv\)), scale
velocityTransferFactoraccording to the ratio of the projectile's mass relative to the total mass of the generated fragments. - Projectile Reaction: After transferring velocity to
the fragments, reduce the projectile's own velocity using
Matter.Body.setVelocity()or destroy it entirely to reflect the loss of kinetic energy during the collision. - Collision Filtering: Assign
collisionFilter.groupor distinct category masks to the generated debris pieces so they do not register collision events with the projectile in the same frame they are spawned.