How Box3D Sensors Detect Overlaps Without Generating Contact Physics
Box3D implements sensors as special collision shapes that execute parallel broad-phase queries and geometric overlap tests through a dedicated pipeline, storing results in double-buffered arrays to generate deterministic begin/end touch events while explicitly excluding themselves from the contact solver to avoid any physical response.
Box3D's sensor system allows developers to detect when shapes intersect without the bodies responding physically—essential for triggers, hitboxes, and proximity detection. According to the erincatto/box3d source code, sensors operate through an isolated detection pipeline in src/sensor.c that separates overlap queries from the physics solver, ensuring fast event generation without creating contact manifolds or impulse responses.
The Sensor Architecture: Isolation from Contact Solvers
Box3D implements sensors as first-class objects that maintain their own state separate from the physics simulation. Unlike regular collision shapes, sensors never enter the contact resolution phase, allowing them to detect intersections purely as informational events.
The b3Sensor Structure
When a shape is marked as a sensor (shape->sensorIndex != B3_NULL_INDEX), the system allocates a b3Sensor struct in the world's sensor array (see src/sensor.c lines 238-244). Each sensor maintains three critical dynamic arrays:
hits– Temporary contacts discovered during the current simulation stepoverlaps1– Overlaps from the previous step (previous buffer)overlaps2– Newly detected overlaps for the current step (current buffer)
This double-buffered design enables the system to compare states between frames and determine exactly when overlaps begin and end.
Shape Association
Sensors attach to shapes through the sensorIndex field defined in src/shape.h. When b3_enableSensorEvents is set in the shape definition flags, the creation function allocates the sensor structure and establishes the bidirectional link between the shape and its sensor data. According to src/solver.c, shapes with valid sensorIndex values are explicitly excluded from the continuous collision solver (fastShape->sensorIndex != B3_NULL_INDEX), ensuring zero physical interaction.
The Detection Pipeline: From Broad Phase to Events
The sensor detection system operates in parallel during each simulation tick through a specialized task-based pipeline that queries the broad-phase structures without disturbing the contact solver.
Parallel Broad-Phase Queries
During b3World_Step(), the function b3OverlapSensors() (see src/sensor.c lines 81-88) launches a b3SensorTask for every active sensor. Each task executes the following:
- Queries the sensor's AABB against all three dynamic-tree broad-phase structures using
b3DynamicTree_Query(lines 32-36) - Invokes
b3SensorQueryCallbackfor every candidate shape found in the broad phase - Filters candidates by convexity, custom filters, and body ID before performing expensive geometric tests
This parallel approach ensures sensor detection scales with multi-core processors without blocking the main physics thread.
Geometric Overlap Testing
Inside the query callback (lines 92-150), Box3D performs precise geometric intersection tests. The function b3OverlapSensor() dispatches to primitive-specific routines (capsule, compound, hull, mesh, sphere) based on the shape types involved (lines 28-73).
When an overlap is confirmed, the system creates a b3Visitor entry containing the overlapping shape ID and generation, appending it to sensor->overlaps2 (lines 58-63). This visitor list represents all shapes currently intersecting the sensor during the current frame.
Double-Buffered Event Detection
After all parallel queries complete, Box3D processes the buffers to generate discrete events. The system sorts both overlaps1 and overlaps2 arrays and compares them (lines 37-77):
- New entries in
overlaps2not present inoverlaps1triggerb3SensorBeginTouchEventobjects - Missing entries from
overlaps1not inoverlaps2triggerb3SensorEndTouchEventobjects
The main thread merges per-worker eventBits from all threads and iterates over the set bits to populate world->sensorBeginEvents and world->sensorEndEvents (lines 100-120). These arrays provide deterministic, ordered event lists for user consumption without any contact physics overhead.
Implementation Examples
Creating Sensor Shapes
To create a sensor that detects overlaps without physical response:
/* Define a spherical sensor that reports overlaps but has no mass or collision response */
b3ShapeDef sphereDef = b3DefaultShapeDef();
sphereDef.type = b3_sphereShape;
sphereDef.position = b3Vec3_set(0.0f, 1.0f, 0.0f);
sphereDef.sphere.radius = 0.5f;
sphereDef.flags |= b3_enableSensorEvents; // Critical: enables sensor behavior
b3ShapeId sensorId = b3CreateShape(world, bodyId, &sphereDef);
The b3_enableSensorEvents flag ensures the shape is allocated as a sensor rather than a collidable object.
Processing Overlap Events
Access current overlaps and process discrete begin/end events:
/* Query the sensor's current overlaps directly */
b3Sensor* sensor = b3Array_Get(world->sensors,
b3Array_Get(world->shapes, sensorId).sensorIndex);
for (int i = 0; i < sensor->overlaps2.count; ++i) {
b3Visitor* v = sensor->overlaps2.data + i;
printf("Overlapping shape id = %d (generation %u)\n",
v->shapeId, v->generation);
}
/* Process discrete begin/end events once per frame */
for (int i = 0; i < world->sensorBeginEvents.count; ++i) {
b3SensorBeginTouchEvent* ev = &world->sensorBeginEvents.data[i];
/* React to new overlap: ev->sensorShapeId, ev->visitorShapeId */
}
for (int i = 0; i < world->sensorEndEvents[world->endEventArrayIndex].count; ++i) {
b3SensorEndTouchEvent* ev = &world->sensorEndEvents[world->endEventArrayIndex].data[i];
/* React to ended overlap: ev->sensorShapeId, ev->visitorShapeId */
}
To remove a sensor entirely:
b3DestroySensor(world, b3Array_Get(world->shapes, sensorId));
Summary
- Isolation Architecture: Box3D stores sensor data in dedicated
b3Sensorstructs with double-buffered overlap arrays (overlaps1andoverlaps2), completely separate from the contact solver. - Parallel Detection: The
b3OverlapSensors()function insrc/sensor.claunches parallel tasks that query the broad-phase dynamic trees without blocking physics calculations. - Geometric Precision: Actual overlap testing occurs in
b3OverlapSensor()through type-specific primitive tests, ensuring accurate collision detection. - Deterministic Events: By comparing sorted buffers each frame, Box3D generates precise
b3SensorBeginTouchEventandb3SensorEndTouchEventobjects for frame-accurate game logic. - Zero Physics Response: Sensors are excluded from
b3Solverprocessing, guaranteeing no contact manifolds, impulses, or positional corrections occur between sensed objects.
Frequently Asked Questions
What is the difference between a Box3D sensor and a regular collision shape?
A regular collision shape participates in the full physics pipeline: broad-phase detection, narrow-phase contact generation, constraint solving, and impulse application. A Box3D sensor executes only the broad-phase and narrow-phase detection steps, storing results in the b3Sensor structure's visitor arrays. According to src/solver.c, sensors bypass the contact solver entirely because they carry the sensorIndex flag, ensuring they detect intersections without generating contact manifolds or affecting body velocities.
How does Box3D handle sensor events in multi-threaded simulations?
Box3D processes sensors using the task system via b3SensorTask instances launched by b3OverlapSensors(). Each worker thread maintains its own eventBits bitset to track which sensors experienced state changes. After parallel queries complete, the main thread merges these bitsets and generates the final b3SensorBeginTouchEvent and b3SensorEndTouchEvent arrays. This design prevents thread contention while ensuring deterministic event ordering regardless of task completion timing.
Can sensors detect overlaps with other sensors?
Yes, the callback system in b3SensorQueryCallback (lines 92-150) does not exclude sensor shapes from the candidate list. When one sensor's AABB query encounters another sensor shape, the geometric test in b3OverlapSensor() executes normally and appends a b3Visitor entry if they intersect. Both sensors will report the overlap in their respective event arrays, though no physical response occurs between them since both are excluded from the solver.
Why are sensor events double-buffered in Box3D?
The double-buffered design using overlaps1 and overlaps2 allows Box3D to detect discrete state transitions between simulation steps. By comparing the previous frame's overlaps with the current frame's results, the system can distinguish between continuing overlaps, newly beginning contacts, and just-ended separations. This buffering ensures that b3SensorBeginTouchEvent and b3SensorEndTouchEvent are generated exactly once per state change, providing reliable triggers for game logic that must react to entry and exit events rather than continuous intersection.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →