# How Box3D Sensors Detect Overlaps Without Generating Contact Physics

> Discover how Box3D sensors detect overlaps without contact physics. Learn about their dedicated pipeline for efficient broad-phase queries and geometric tests, storing results for deterministic events.

- Repository: [Erin Catto/box3d](https://github.com/erincatto/box3d)
- Tags: internals
- Published: 2026-08-01

---

**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`](https://github.com/erincatto/box3d/blob/main/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`](https://github.com/erincatto/box3d/blob/main/src/sensor.c) lines 238-244). Each sensor maintains three critical dynamic arrays:

- **`hits`** – Temporary contacts discovered during the current simulation step
- **`overlaps1`** – 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`](https://github.com/erincatto/box3d/blob/main/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`](https://github.com/erincatto/box3d/blob/main/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`](https://github.com/erincatto/box3d/blob/main/src/sensor.c) lines 81-88) launches a `b3SensorTask` for every active sensor. Each task executes the following:

1. Queries the sensor's AABB against all three dynamic-tree broad-phase structures using `b3DynamicTree_Query` (lines 32-36)
2. Invokes `b3SensorQueryCallback` for every candidate shape found in the broad phase
3. 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 `overlaps2` not present in `overlaps1` trigger `b3SensorBeginTouchEvent` objects
- **Missing entries** from `overlaps1` not in `overlaps2` trigger `b3SensorEndTouchEvent` objects

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:

```c
/* 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:

```c
/* 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:

```c
b3DestroySensor(world, b3Array_Get(world->shapes, sensorId));

```

## Summary

- **Isolation Architecture**: Box3D stores sensor data in dedicated `b3Sensor` structs with double-buffered overlap arrays (`overlaps1` and `overlaps2`), completely separate from the contact solver.
- **Parallel Detection**: The `b3OverlapSensors()` function in [`src/sensor.c`](https://github.com/erincatto/box3d/blob/main/src/sensor.c) launches 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 `b3SensorBeginTouchEvent` and `b3SensorEndTouchEvent` objects for frame-accurate game logic.
- **Zero Physics Response**: Sensors are excluded from `b3Solver` processing, 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`](https://github.com/erincatto/box3d/blob/main/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.