Skip to main content

box2d_rust/
sensor.rs

1// Port of the sensor data model from box2d-cpp-reference/src/sensor.h plus
2// sensor destruction from sensor.c. The overlap update logic lands in a later
3// bring-up commit.
4//
5// SPDX-FileCopyrightText: 2023 Erin Catto
6// SPDX-License-Identifier: MIT
7
8use crate::bitset::BitSet;
9use crate::core::NULL_INDEX;
10
11/// Used to track shapes that hit sensors using time of impact. (b2SensorHit)
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct SensorHit {
14    pub sensor_id: i32,
15    pub visitor_id: i32,
16}
17
18/// (b2Visitor)
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct Visitor {
21    pub shape_id: i32,
22    pub generation: u16,
23}
24
25/// Sensors are shapes that live in the broad-phase but never have contacts.
26/// At the end of the time step all sensors are queried for overlap with any
27/// other shapes. Sensors ignore body type and sleeping. Sensors generate
28/// events when a new overlap appears or an overlap disappears. (b2Sensor)
29#[derive(Debug, Clone, Default)]
30pub struct Sensor {
31    pub hits: Vec<Visitor>,
32    pub overlaps1: Vec<Visitor>,
33    pub overlaps2: Vec<Visitor>,
34    pub shape_id: i32,
35}
36
37impl Sensor {
38    pub fn new(shape_id: i32) -> Sensor {
39        Sensor {
40            hits: Vec::new(),
41            overlaps1: Vec::new(),
42            overlaps2: Vec::new(),
43            shape_id,
44        }
45    }
46}
47
48/// (b2SensorTaskContext)
49#[derive(Debug, Clone, Default)]
50pub struct SensorTaskContext {
51    pub event_bits: BitSet,
52}
53
54/// (b2SensorOverlaps, from shape.h)
55#[derive(Debug, Clone, Default)]
56pub struct SensorOverlaps {
57    pub overlaps: Vec<i32>,
58}
59
60impl Default for SensorHit {
61    fn default() -> Self {
62        SensorHit {
63            sensor_id: NULL_INDEX,
64            visitor_id: NULL_INDEX,
65        }
66    }
67}
68
69/// Destroy the sensor record for a sensor shape, emitting end-touch events for
70/// its active overlaps. (b2DestroySensor — C takes the shape pointer; the Rust
71/// port takes the shape id.)
72pub fn destroy_sensor(world: &mut crate::world::World, sensor_shape_id: i32) {
73    use crate::events::SensorEndTouchEvent;
74    use crate::id::ShapeId;
75
76    let sensor_index = world.shapes[sensor_shape_id as usize].sensor_index;
77    let sensor_generation = world.shapes[sensor_shape_id as usize].generation;
78
79    let overlaps2 = std::mem::take(&mut world.sensors[sensor_index as usize].overlaps2);
80    for visitor in &overlaps2 {
81        let event = SensorEndTouchEvent {
82            sensor_shape_id: ShapeId {
83                index1: sensor_shape_id + 1,
84                world0: world.world_id,
85                generation: sensor_generation,
86            },
87            visitor_shape_id: ShapeId {
88                index1: visitor.shape_id + 1,
89                world0: world.world_id,
90                generation: visitor.generation,
91            },
92        };
93
94        world.sensor_end_events[world.end_event_array_index as usize].push(event);
95    }
96
97    // Destroy sensor (the C b2Array_Destroy calls drop with the Sensor)
98    let moved_index = world.sensors.len() as i32 - 1;
99    world.sensors.swap_remove(sensor_index as usize);
100    if moved_index != sensor_index {
101        // Fixup moved sensor
102        let moved_shape_id = world.sensors[sensor_index as usize].shape_id;
103        world.shapes[moved_shape_id as usize].sensor_index = sensor_index;
104    }
105}
106
107/// Update sensor overlaps and publish begin/end touch events.
108/// (b2SensorTask + b2OverlapSensors — serial: one worker, one range)
109pub fn overlap_sensors(world: &mut crate::world::World) {
110    use crate::distance::{shape_distance, DistanceInput, SimplexCache};
111    use crate::events::{SensorBeginTouchEvent, SensorEndTouchEvent};
112    use crate::id::ShapeId;
113    use crate::math_functions::inv_mul_world_transforms;
114    use crate::shape::{make_shape_distance_proxy, should_shapes_collide};
115    use crate::solver_set::DISABLED_SET;
116
117    let sensor_count = world.sensors.len();
118    if sensor_count == 0 {
119        return;
120    }
121
122    world.sensor_task_contexts[0]
123        .event_bits
124        .set_bit_count_and_clear(sensor_count as u32);
125
126    // (b2SensorTask over [0, sensorCount))
127    for sensor_index in 0..sensor_count {
128        // Swap overlap arrays
129        {
130            let sensor = &mut world.sensors[sensor_index];
131            std::mem::swap(&mut sensor.overlaps1, &mut sensor.overlaps2);
132            sensor.overlaps2.clear();
133
134            // Append sensor hits, then clear them
135            let hits = std::mem::take(&mut sensor.hits);
136            sensor.overlaps2.extend_from_slice(&hits);
137        }
138
139        let sensor_shape_id = world.sensors[sensor_index].shape_id;
140        let (sensor_body_id, sensor_enabled) = {
141            let shape = &world.shapes[sensor_shape_id as usize];
142            (shape.body_id, shape.enable_sensor_events)
143        };
144
145        if world.bodies[sensor_body_id as usize].set_index == DISABLED_SET || !sensor_enabled {
146            if !world.sensors[sensor_index].overlaps1.is_empty() {
147                // This sensor is dropping all overlaps because it has been
148                // disabled.
149                world.sensor_task_contexts[0]
150                    .event_bits
151                    .set_bit(sensor_index as u32);
152            }
153            continue;
154        }
155
156        let transform = crate::body::get_body_transform(world, sensor_body_id);
157
158        debug_assert!(world.shapes[sensor_shape_id as usize].sensor_index == sensor_index as i32);
159        let query_bounds = world.shapes[sensor_shape_id as usize].aabb;
160        let mask_bits = world.shapes[sensor_shape_id as usize].filter.mask_bits;
161
162        // Query all trees. The callback (b2SensorQueryCallback) collects
163        // overlaps into a local list; the world stays borrowed shared.
164        let mut new_overlaps: Vec<Visitor> = Vec::new();
165        {
166            let world_ref: &crate::world::World = world;
167            let sensor_shape = &world_ref.shapes[sensor_shape_id as usize];
168
169            let mut callback = |_proxy_id: i32, user_data: u64| -> bool {
170                let shape_id = user_data as i32;
171
172                if shape_id == sensor_shape_id {
173                    return true;
174                }
175
176                let other_shape = &world_ref.shapes[shape_id as usize];
177
178                // Are sensor events enabled on the other shape?
179                if !other_shape.enable_sensor_events {
180                    return true;
181                }
182
183                // Skip shapes on the same body
184                if other_shape.body_id == sensor_shape.body_id {
185                    return true;
186                }
187
188                // Check filter
189                if !should_shapes_collide(sensor_shape.filter, other_shape.filter) {
190                    return true;
191                }
192
193                // Custom user filter
194                if sensor_shape.enable_custom_filtering || other_shape.enable_custom_filtering {
195                    if let Some(custom_filter_fcn) = world_ref.custom_filter_fcn {
196                        let id_a = ShapeId {
197                            index1: sensor_shape_id + 1,
198                            world0: world_ref.world_id,
199                            generation: sensor_shape.generation,
200                        };
201                        let id_b = ShapeId {
202                            index1: shape_id + 1,
203                            world0: world_ref.world_id,
204                            generation: other_shape.generation,
205                        };
206                        if !custom_filter_fcn(id_a, id_b, world_ref.custom_filter_context) {
207                            return true;
208                        }
209                    }
210                }
211
212                // The relative pose is differenced in double so sensor overlap
213                // stays exact far from the origin
214                let other_transform =
215                    crate::body::get_body_transform(world_ref, other_shape.body_id);
216
217                let input = DistanceInput {
218                    proxy_a: make_shape_distance_proxy(sensor_shape),
219                    proxy_b: make_shape_distance_proxy(other_shape),
220                    transform: inv_mul_world_transforms(transform, other_transform),
221                    use_radii: true,
222                };
223                let mut cache = SimplexCache::default();
224                let output = shape_distance(&input, &mut cache, None);
225
226                let overlaps = output.distance < 10.0 * f32::EPSILON;
227                if !overlaps {
228                    return true;
229                }
230
231                // Record the overlap
232                new_overlaps.push(Visitor {
233                    shape_id,
234                    generation: other_shape.generation,
235                });
236
237                true
238            };
239
240            for tree in &world_ref.broad_phase.trees {
241                tree.query(query_bounds, mask_bits, &mut callback);
242            }
243        }
244
245        let sensor = &mut world.sensors[sensor_index];
246        sensor.overlaps2.extend_from_slice(&new_overlaps);
247
248        // Sort the overlaps to enable finding begin and end events. (The C
249        // comparator orders by shapeId only; duplicates are identical
250        // Visitors, so a stable key sort matches.)
251        sensor.overlaps2.sort_by_key(|visitor| visitor.shape_id);
252
253        // Remove duplicates from overlaps2 (sorted). Duplicates are possible
254        // due to the hit events appended earlier.
255        sensor.overlaps2.dedup_by_key(|visitor| visitor.shape_id);
256
257        let count1 = sensor.overlaps1.len();
258        let count2 = sensor.overlaps2.len();
259        if count1 != count2 {
260            // something changed
261            world.sensor_task_contexts[0]
262                .event_bits
263                .set_bit(sensor_index as u32);
264        } else {
265            for i in 0..count1 {
266                let s1 = sensor.overlaps1[i];
267                let s2 = sensor.overlaps2[i];
268
269                if s1.shape_id != s2.shape_id || s1.generation != s2.generation {
270                    // something changed
271                    world.sensor_task_contexts[0]
272                        .event_bits
273                        .set_bit(sensor_index as u32);
274                    break;
275                }
276            }
277        }
278    }
279
280    // Iterate sensor bits and publish events (b2OverlapSensors tail)
281    let world_id = world.world_id;
282    let end_event_array_index = world.end_event_array_index as usize;
283
284    let block_count = world.sensor_task_contexts[0].event_bits.block_count();
285    for k in 0..block_count {
286        let mut word = world.sensor_task_contexts[0].event_bits.block(k);
287        while word != 0 {
288            let ctz = word.trailing_zeros();
289            let sensor_index = (64 * k + ctz) as usize;
290
291            let sensor_shape_id = world.sensors[sensor_index].shape_id;
292            let sensor_generation = world.shapes[sensor_shape_id as usize].generation;
293            let sensor_id = ShapeId {
294                index1: sensor_shape_id + 1,
295                world0: world_id,
296                generation: sensor_generation,
297            };
298
299            let count1 = world.sensors[sensor_index].overlaps1.len();
300            let count2 = world.sensors[sensor_index].overlaps2.len();
301
302            // overlaps1 can have overlaps that end
303            // overlaps2 can have overlaps that begin
304            let mut index1 = 0;
305            let mut index2 = 0;
306            while index1 < count1 && index2 < count2 {
307                let r1 = world.sensors[sensor_index].overlaps1[index1];
308                let r2 = world.sensors[sensor_index].overlaps2[index2];
309                if r1.shape_id == r2.shape_id {
310                    match r1.generation.cmp(&r2.generation) {
311                        std::cmp::Ordering::Less => {
312                            // end
313                            let visitor_id = ShapeId {
314                                index1: r1.shape_id + 1,
315                                world0: world_id,
316                                generation: r1.generation,
317                            };
318                            world.sensor_end_events[end_event_array_index].push(
319                                SensorEndTouchEvent {
320                                    sensor_shape_id: sensor_id,
321                                    visitor_shape_id: visitor_id,
322                                },
323                            );
324                            index1 += 1;
325                        }
326                        std::cmp::Ordering::Greater => {
327                            // begin
328                            let visitor_id = ShapeId {
329                                index1: r2.shape_id + 1,
330                                world0: world_id,
331                                generation: r2.generation,
332                            };
333                            world.sensor_begin_events.push(SensorBeginTouchEvent {
334                                sensor_shape_id: sensor_id,
335                                visitor_shape_id: visitor_id,
336                            });
337                            index2 += 1;
338                        }
339                        std::cmp::Ordering::Equal => {
340                            // persisted
341                            index1 += 1;
342                            index2 += 1;
343                        }
344                    }
345                } else if r1.shape_id < r2.shape_id {
346                    // end
347                    let visitor_id = ShapeId {
348                        index1: r1.shape_id + 1,
349                        world0: world_id,
350                        generation: r1.generation,
351                    };
352                    world.sensor_end_events[end_event_array_index].push(SensorEndTouchEvent {
353                        sensor_shape_id: sensor_id,
354                        visitor_shape_id: visitor_id,
355                    });
356                    index1 += 1;
357                } else {
358                    // begin
359                    let visitor_id = ShapeId {
360                        index1: r2.shape_id + 1,
361                        world0: world_id,
362                        generation: r2.generation,
363                    };
364                    world.sensor_begin_events.push(SensorBeginTouchEvent {
365                        sensor_shape_id: sensor_id,
366                        visitor_shape_id: visitor_id,
367                    });
368                    index2 += 1;
369                }
370            }
371
372            while index1 < count1 {
373                // end
374                let r1 = world.sensors[sensor_index].overlaps1[index1];
375                let visitor_id = ShapeId {
376                    index1: r1.shape_id + 1,
377                    world0: world_id,
378                    generation: r1.generation,
379                };
380                world.sensor_end_events[end_event_array_index].push(SensorEndTouchEvent {
381                    sensor_shape_id: sensor_id,
382                    visitor_shape_id: visitor_id,
383                });
384                index1 += 1;
385            }
386
387            while index2 < count2 {
388                // begin
389                let r2 = world.sensors[sensor_index].overlaps2[index2];
390                let visitor_id = ShapeId {
391                    index1: r2.shape_id + 1,
392                    world0: world_id,
393                    generation: r2.generation,
394                };
395                world.sensor_begin_events.push(SensorBeginTouchEvent {
396                    sensor_shape_id: sensor_id,
397                    visitor_shape_id: visitor_id,
398                });
399                index2 += 1;
400            }
401
402            // Clear the smallest set bit
403            word &= word - 1;
404        }
405    }
406}