How do mask bits control planck.js fixture collisions?
In planck.js (a 2D JavaScript physics engine based on
Box2D), mask bits act as a selective filter that
determines which categories of objects a specific fixture is allowed to
collide with. Every fixture is assigned a collision filter consisting of
a category bitmask and a mask bitmask. By performing a bitwise
AND operation between one object’s categories and another
object’s mask, the physics engine can instantly decide whether to
simulate a physical impact or let the objects pass through each other.
This mechanism is crucial for optimizing game performance and creating
complex interactions, such as ignoring collisions between teammates or
allowing a character to pass through platforms from below.
Understanding the Planck.js Collision Filter
To understand mask bits, you must first look at the three primary
components of the Filter object in
planck.js:
categoryBits: Defines what the fixture is. It acts as the object’s identity layer.maskBits: Defines what the fixture collides with. It acts as the object’s permission layer.groupIndex: A master override that can force or completely disable collisions regardless of the category and mask bits.
Both categoryBits and maskBits are 16-bit
integers, meaning you have up to 16 unique collision groups available
(ranging from \(0x0001\) to \(0x8000\)).
The Bitwise Collision Formula
When two fixtures (Fixture A and Fixture B) look like they are about
to touch, planck.js evaluates their filters using bitwise
logic. A collision is only allowed to occur if both of
the following conditions are met:
const collideA = (filterA.maskBits & filterB.categoryBits) !== 0;
const collideB = (filterB.maskBits & filterA.categoryBits) !== 0;
const willCollide = collideA && collideB;If either evaluation results in 0, the physics engine
completely ignores the collision.
Defining Roles via Mask Bits
Mask bits allow developers to create sophisticated multi-layered physics environments. They essentially fulfill three critical roles:
1. Specifying Selective Immunity
By default, every fixture has a maskBits value of
0xFFFF (all bits set to 1), meaning it collides with
everything. By changing the mask bits, you can grant an object immunity
to specific elements. For example, a player projectile can be configured
to ignore the player who shot it by omitting the player’s category bit
from the projectile’s mask bits, while keeping the enemy and wall bits
active.
2. Creating Multi-Layered Environments
Games often require backgrounds or decorative foreground elements
that interact with certain mechanics but not others. Mask bits allow
objects like particles or weather effects to collide exclusively with
the ground (maskBits = GROUND_BIT) while seamlessly passing
through characters, enemies, and items.
3. Enhancing Performance
Evaluating full physical contacts (calculating manifolds, friction, and restitution) is computationally expensive. Mask bits filter out unwanted collisions at a very early stage in the physics pipeline (the broad-phase collision detection). By ensuring that objects only check for collisions with relevant categories, you significantly reduce the CPU overhead in scenes with hundreds of active bodies.
Implementing Mask Bits in Code
When setting up your fixtures, you apply these filters directly to the fixture definition or update them dynamically on an existing fixture.
// Define the categories using hexadecimal bit shifts
const CATEGORY_PLAYER = 0x0001; // 0000000000000001
const CATEGORY_ENEMY = 0x0002; // 0000000000000010
const CATEGORY_WALL = 0x0004; // 0000000000000100
// Player Fixture Definition
// The player belongs to PLAYER, and collides with ENEMY and WALL
playerFixtureDef.filterCategoryBits = CATEGORY_PLAYER;
playerFixtureDef.filterMaskBits = CATEGORY_ENEMY | CATEGORY_WALL;
// Enemy Fixture Definition
// The enemy belongs to ENEMY, and collides only with WALL and PLAYER
enemyFixtureDef.filterCategoryBits = CATEGORY_ENEMY;
enemyFixtureDef.filterMaskBits = CATEGORY_WALL | CATEGORY_PLAYER;In this scenario, if you were to add an independent decorative asset
with a category of 0x0008, neither the player nor the enemy
would physically interact with it because 0x0008 was
omitted from their respective filterMaskBits.