Spawn Impact Particles on Collision Normals in Matter.js
Matter.js provides detailed contact data during collision events,
enabling developers to create realistic visual effects such as sparks,
dust, or debris bursts. By listening to the collisionStart
event, extracting the collision normal and contact points from collision
pairs, and computing directional velocity vectors with an angular
spread, you can spawn particle bursts that accurately align with the
surface angle of any physical impact.
Accessing Collision Data
To spawn particles at the exact moment and location of an impact,
listen to the collisionStart event emitted by the Matter.js
engine. Each event payload contains a list of pairs, which
represent the two colliding bodies and the mathematical details of their
intersection.
Matter.Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
// Handle collision data here
});
});Extracting the Normal Vector and Contact Points
The pair.collision object contains the collision normal
and the contact vertices:
- Collision Normal
(
pair.collision.normal): A unit vector pointing frombodyAtowardbodyB. - Contact Point
(
pair.collision.supports): An array of vertices indicating where the contact occurred. Taking the first support point (pair.collision.supports[0]) is generally sufficient for determining the burst origin.
Because the normal always points from bodyA to
bodyB, you can invert it depending on which body should
emit the particles, or use the normal directly to cast particles outward
from the impacted surface.
Calculating Particle Trajectory Along the Normal
To orient particles along the collision vector:
- Calculate the base angle using
Math.atan2(normal.y, normal.x). - Add a randomized offset within a desired cone angle (spread) to create a natural spray pattern.
- Compute the horizontal and vertical velocity components using
Math.cos()andMath.sin().
Complete Implementation Example
The following self-contained implementation demonstrates how to capture collision points, compute angles from collision normals, and render bursting particles onto an HTML5 Canvas:
// Array to track active particles
const particles = [];
// Particle definition
class ImpactParticle {
constructor(x, y, angle, speed) {
this.x = x;
this.y = y;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.life = 1.0;
this.decay = Math.random() * 0.03 + 0.02;
this.size = Math.random() * 3 + 2;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= this.decay;
}
draw(ctx) {
ctx.save();
ctx.globalAlpha = Math.max(0, this.life);
ctx.fillStyle = '#ffaa00';
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
// Function to trigger a burst along the normal
function createBurst(originX, originY, normal, count = 15) {
// Base orientation from the collision normal vector
const baseAngle = Math.atan2(normal.y, normal.x);
const spread = Math.PI / 3; // 60-degree cone
for (let i = 0; i < count; i++) {
// Randomize direction within the cone
const angle = baseAngle + (Math.random() - 0.5) * spread;
const speed = Math.random() * 4 + 2;
particles.push(new ImpactParticle(originX, originY, angle, speed));
}
}
// Matter.js collision listener
Matter.Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
const collision = pair.collision;
// Ensure contact points exist
if (collision.supports.length > 0) {
const contactPoint = collision.supports[0];
const normal = collision.normal;
// Invert the normal if particles should bounce back off bodyB
const burstNormal = {
x: -normal.x,
y: -normal.y
};
createBurst(contactPoint.x, contactPoint.y, burstNormal);
}
});
});
// Canvas animation loop
function renderParticles(ctx) {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.update();
p.draw(ctx);
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}Filtering Low-Impact Collisions
To prevent particles from spawning during slow resting contacts or sliding motions, calculate the relative velocity between the two bodies. Multiply the relative velocity by the collision normal to get the normal impact speed, and only spawn particles if this value exceeds a defined threshold:
const relativeVelocity = {
x: pair.bodyA.velocity.x - pair.bodyB.velocity.x,
y: pair.bodyA.velocity.y - pair.bodyB.velocity.y
};
const impactSpeed = Math.abs(
relativeVelocity.x * collision.normal.x +
relativeVelocity.y * collision.normal.y
);
if (impactSpeed > 1.5) {
createBurst(contactPoint.x, contactPoint.y, burstNormal);
}