Smoke Dispersal and Buoyancy in Matter.js
This article explains how to simulate realistic smoke particle dispersal and buoyancy inside confined room geometry using the Matter.js 2D physics engine. By configuring static boundary bodies for walls, disabling particle-to-particle collisions via collision filters, and applying upward buoyancy alongside randomized horizontal turbulence in the engine update loop, you can achieve a lightweight, convincing smoke effect that naturally gathers against ceilings and escapes through room openings.
1. Creating the Confined Room Geometry
A confined environment requires static rigid bodies that prevent
smoke particles from escaping while allowing them to interact naturally
with obstacles. Define the floor, ceiling, and walls using
Matter.Bodies.rectangle with the isStatic
property set to true:
const { Engine, Render, Runner, Bodies, Composite, Body, Events } = Matter;
const engine = Engine.create();
const world = engine.world;
// Define boundaries: floor, ceiling, left wall, right wall
const wallOptions = { isStatic: true, friction: 0 };
const ground = Bodies.rectangle(400, 600, 810, 30, wallOptions);
const ceiling = Bodies.rectangle(400, 0, 810, 30, wallOptions);
const leftWall = Bodies.rectangle(0, 300, 30, 600, wallOptions);
const rightWall = Bodies.rectangle(800, 300, 30, 600, wallOptions);
// Add an internal partition to test smoke movement around obstacles
const partition = Bodies.rectangle(400, 400, 20, 300, wallOptions);
Composite.add(world, [ground, ceiling, leftWall, rightWall, partition]);2. Configuring Collision Filtering for Particles
Simulating hundreds of individual smoke particles colliding with each other quickly degrades performance. To optimize computation, assign collision filters so particles only register collisions with static boundaries, passing freely through one another:
const SMOKE_CATEGORY = 0x0002;
const WALL_CATEGORY = 0x0001;
// Update wall collision masks
[ground, ceiling, leftWall, rightWall, partition].forEach(body => {
body.collisionFilter.category = WALL_CATEGORY;
body.collisionFilter.mask = SMOKE_CATEGORY;
});
const smokeParticleOptions = {
frictionAir: 0.05, // Mimics air resistance to prevent runaway acceleration
restitution: 0.1, // Low bounce against walls
collisionFilter: {
category: SMOKE_CATEGORY,
mask: WALL_CATEGORY // Only collide with walls, ignore other smoke
}
};3. Implementing Buoyancy and Dispersal Forces
Matter.js applies downward gravity by default. While you can set
engine.gravity.y = -1, doing so inverts gravity for the
entire scene. A more robust solution is leaving scene gravity neutral or
standard and applying discrete upward (buoyant) and lateral (dispersive)
forces directly to active smoke particles on every frame.
Listen to the beforeUpdate event to modify particle
forces:
const particles = [];
function emitSmoke(x, y) {
const particle = Bodies.circle(x, y, 6, smokeParticleOptions);
particle.lifeSpan = 300; // Total frames before despawning
particle.age = 0;
particles.push(particle);
Composite.add(world, particle);
}
Events.on(engine, 'beforeUpdate', () => {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.age++;
// Remove expired particles
if (p.age >= p.lifeSpan) {
Composite.remove(world, p);
particles.splice(i, 1);
continue;
}
// Upward buoyancy force (counteracting gravity or driving upward drift)
const buoyancy = -0.0004 * p.mass;
// Horizontal dispersal using random micro-forces (Brownian motion)
const dispersion = (Math.random() - 0.5) * 0.0002;
Body.applyForce(p, p.position, {
x: dispersion,
y: buoyancy
});
// Expand particle size slightly as it ages
Body.scale(p, 1.002, 1.002);
}
});4. Simulating Gas Expansion and Ceilings Trapping
In a confined space, warm smoke flows vertically until it collides with the ceiling, where vertical velocity translates into lateral movement. To make this behavior realistic:
- High Air Friction: Setting
frictionAirbetween0.04and0.08stops particles from sliding endlessly across surfaces, forcing them to pool and expand realistically. - Diffusion Near Barriers: When a particle hits the ceiling, the vertical force stalls against the boundary. The lateral dispersal forces then push accumulating particles sideways along the surface until they encounter an opening or chimney.
- Visual Fading: In your rendering loop, map the
particle's render opacity to
1 - (p.age / p.lifeSpan)to visually convey thinning density as smoke disperses through the structure.