Optimize Terrain Collision Filtering in Matter.js
Simulating hundreds of static terrain tiles in Matter.js can quickly cause severe frame drops due to excessive collision detection checks during the engine's broadphase and narrowphase steps. This guide covers actionable strategies to optimize your terrain layout, focusing on collision bitmasks, combining adjacent bodies, leveraging sleep states, and dynamically managing bodies in the physics world to maintain high frame rates.
1. Configure Bitmask Collision Filters Properly
Matter.js evaluates collisions using bitwise masks defined in the
collisionFilter object. By default, every body attempts to
check collisions against every other body. Static tiles never need to
collide with other static tiles, so you must explicitly instruct the
engine to ignore static-to-static checks.
Define discrete 32-bit integer categories for your world objects:
const CATEGORY_TERRAIN = 0x0001;
const CATEGORY_PLAYER = 0x0002;
const CATEGORY_ENEMY = 0x0004;
// Apply to static tiles
const terrainTile = Matter.Bodies.rectangle(x, y, width, height, {
isStatic: true,
collisionFilter: {
category: CATEGORY_TERRAIN,
// Only register collisions with dynamic bodies, not other terrain
mask: CATEGORY_PLAYER | CATEGORY_ENEMY
}
});By omitting CATEGORY_TERRAIN from the terrain's
mask, you eliminate redundant calculations before
narrowphase collision algorithms run.
2. Merge Adjacent Static Tiles
Creating a distinct physics body for every 16x16 or 32x32 visual grid tile causes broadphase tree bloat. While visual layers need small tiles, physics terrain does not.
- Raycast / Scanline Merging: Parse your tilemap row by row or column by column. If five identical collision blocks sit consecutively horizontally, replace them with a single static rectangle spanning the combined width.
- Compound Bodies: For complex geometry, combine
adjacent shapes using
Matter.Body.create({ parts: [...] }). This creates a unified broadphase bounding box for multiple collision parts, reducing the workload of the spatial hash. - Polygon Tracing: For non-grid geometry, extract the exterior contour of your terrain using marching squares or outline-tracing algorithms to generate simple outer edges rather than internal blocks.
3. Enable Engine Sleeping
When dynamic bodies come to rest, Matter.js can exclude them from collision pairs completely until an external force acts on them:
const engine = Matter.Engine.create({
enableSleeping: true
});Setting enableSleeping: true prevents stationary dynamic
bodies (like resting items or debris) from running continuous collision
checks against hundreds of static terrain tiles.
4. Implement Spatial Chunking (World Streaming)
Matter.js uses a broadphase spatial hashing grid (or bounding box tree), but maintaining hundreds of off-screen bodies still incurs memory and traversal overhead.
Divide your world into chunks (e.g., 512x512 pixels):
- Store tile data in memory arrays rather than directly in the physics world.
- Track the camera or player position.
- Use
Matter.Composite.add(world, chunkBodies)to mount static bodies when a chunk enters the viewport plus a safety margin. - Use
Matter.Composite.remove(world, chunkBodies)to unmount chunks that move far off-screen.
5. Disable Internal Sensor Checks on Static Walls
Ensure that none of your static terrain blocks use
isSensor: true unless strictly required for trigger areas.
Sensor checks force the engine to fire event callbacks
(collisionStart, collisionActive) each tick,
adding CPU overhead that static boundary walls do not need. Keep static
geometry strictly physical by keeping isSensor set to
false.