rapier2d/pipeline/
collision_pipeline.rs

1//! Physics pipeline structures.
2
3use crate::dynamics::{ImpulseJointSet, IntegrationParameters, MultibodyJointSet};
4use crate::geometry::{
5    BroadPhaseBvh, BroadPhasePairEvent, ColliderChanges, ColliderHandle, ColliderPair,
6    ModifiedColliders, NarrowPhase,
7};
8use crate::math::Real;
9use crate::pipeline::{EventHandler, PhysicsHooks};
10use crate::{dynamics::RigidBodySet, geometry::ColliderSet};
11
12/// A collision detection pipeline that can be used without full physics simulation.
13///
14/// This runs only collision detection (broad-phase + narrow-phase) without dynamics/forces.
15/// Use when you want to detect collisions but don't need physics simulation.
16///
17/// **For full physics**, use [`PhysicsPipeline`](crate::pipeline::PhysicsPipeline) instead which includes this internally.
18///
19/// ## Use cases
20///
21/// - Collision detection in a non-physics game
22/// - Custom physics integration where you handle forces yourself
23/// - Debugging collision detection separately from dynamics
24///
25/// Like PhysicsPipeline, this only holds temporary buffers. Reuse the same instance for performance.
26// NOTE: this contains only workspace data, so there is no point in making this serializable.
27pub struct CollisionPipeline {
28    broadphase_collider_pairs: Vec<ColliderPair>,
29    broad_phase_events: Vec<BroadPhasePairEvent>,
30}
31
32#[allow(dead_code)]
33fn check_pipeline_send_sync() {
34    fn do_test<T: Sync>() {}
35    do_test::<CollisionPipeline>();
36}
37
38impl Default for CollisionPipeline {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl CollisionPipeline {
45    /// Initializes a new physics pipeline.
46    pub fn new() -> CollisionPipeline {
47        CollisionPipeline {
48            broadphase_collider_pairs: Vec::new(),
49            broad_phase_events: Vec::new(),
50        }
51    }
52
53    fn detect_collisions(
54        &mut self,
55        prediction_distance: Real,
56        broad_phase: &mut BroadPhaseBvh,
57        narrow_phase: &mut NarrowPhase,
58        bodies: &mut RigidBodySet,
59        colliders: &mut ColliderSet,
60        modified_colliders: &[ColliderHandle],
61        removed_colliders: &[ColliderHandle],
62        hooks: &dyn PhysicsHooks,
63        events: &dyn EventHandler,
64        handle_user_changes: bool,
65    ) {
66        // Update broad-phase.
67        self.broad_phase_events.clear();
68        self.broadphase_collider_pairs.clear();
69
70        let params = IntegrationParameters {
71            normalized_prediction_distance: prediction_distance,
72            dt: 0.0,
73            ..Default::default()
74        };
75
76        broad_phase.update(
77            &params,
78            colliders,
79            bodies,
80            modified_colliders,
81            removed_colliders,
82            &mut self.broad_phase_events,
83        );
84
85        // Update narrow-phase.
86        if handle_user_changes {
87            narrow_phase.handle_user_changes(
88                None,
89                modified_colliders,
90                removed_colliders,
91                colliders,
92                bodies,
93                events,
94            );
95        }
96
97        narrow_phase.register_pairs(None, colliders, bodies, &self.broad_phase_events, events);
98        narrow_phase.compute_contacts(
99            prediction_distance,
100            0.0,
101            bodies,
102            colliders,
103            &ImpulseJointSet::new(),
104            &MultibodyJointSet::new(),
105            hooks,
106            events,
107        );
108        narrow_phase.compute_intersections(bodies, colliders, hooks, events);
109    }
110
111    fn clear_modified_colliders(
112        &mut self,
113        colliders: &mut ColliderSet,
114        modified_colliders: &mut ModifiedColliders,
115    ) {
116        for handle in modified_colliders.iter() {
117            if let Some(co) = colliders.get_mut_internal(*handle) {
118                co.changes = ColliderChanges::empty();
119            }
120        }
121
122        modified_colliders.clear();
123    }
124
125    /// Executes one step of the collision detection.
126    pub fn step(
127        &mut self,
128        prediction_distance: Real,
129        broad_phase: &mut BroadPhaseBvh,
130        narrow_phase: &mut NarrowPhase,
131        bodies: &mut RigidBodySet,
132        colliders: &mut ColliderSet,
133        hooks: &dyn PhysicsHooks,
134        events: &dyn EventHandler,
135    ) {
136        let modified_bodies = bodies.take_modified();
137        let mut modified_colliders = colliders.take_modified();
138        let mut removed_colliders = colliders.take_removed();
139
140        super::user_changes::handle_user_changes_to_colliders(
141            bodies,
142            colliders,
143            &modified_colliders[..],
144        );
145        super::user_changes::handle_user_changes_to_rigid_bodies(
146            None,
147            bodies,
148            colliders,
149            &mut ImpulseJointSet::new(),
150            &mut MultibodyJointSet::new(),
151            &modified_bodies,
152            &mut modified_colliders,
153        );
154
155        // Disabled colliders are treated as if they were removed.
156        removed_colliders.extend(
157            modified_colliders
158                .iter()
159                .copied()
160                .filter(|h| colliders.get(*h).map(|c| !c.is_enabled()).unwrap_or(false)),
161        );
162
163        self.detect_collisions(
164            prediction_distance,
165            broad_phase,
166            narrow_phase,
167            bodies,
168            colliders,
169            &modified_colliders[..],
170            &removed_colliders,
171            hooks,
172            events,
173            true,
174        );
175
176        self.clear_modified_colliders(colliders, &mut modified_colliders);
177        removed_colliders.clear();
178    }
179}
180
181#[cfg(test)]
182mod tests {
183
184    #[test]
185    #[cfg(feature = "dim3")]
186    pub fn test_no_rigid_bodies() {
187        use crate::prelude::*;
188        let mut rigid_body_set = RigidBodySet::new();
189        let mut collider_set = ColliderSet::new();
190
191        /* Create the ground. */
192        let collider_a = ColliderBuilder::cuboid(1.0, 1.0, 1.0)
193            .active_collision_types(ActiveCollisionTypes::all())
194            .sensor(true)
195            .active_events(ActiveEvents::COLLISION_EVENTS)
196            .build();
197
198        let a_handle = collider_set.insert(collider_a);
199
200        let collider_b = ColliderBuilder::cuboid(1.0, 1.0, 1.0)
201            .active_collision_types(ActiveCollisionTypes::all())
202            .sensor(true)
203            .active_events(ActiveEvents::COLLISION_EVENTS)
204            .build();
205
206        let _ = collider_set.insert(collider_b);
207
208        let integration_parameters = IntegrationParameters::default();
209        let mut broad_phase = BroadPhaseBvh::new();
210        let mut narrow_phase = NarrowPhase::new();
211        let mut collision_pipeline = CollisionPipeline::new();
212        let physics_hooks = ();
213
214        collision_pipeline.step(
215            integration_parameters.prediction_distance(),
216            &mut broad_phase,
217            &mut narrow_phase,
218            &mut rigid_body_set,
219            &mut collider_set,
220            &physics_hooks,
221            &(),
222        );
223
224        let mut hit = false;
225
226        for (_, _, intersecting) in narrow_phase.intersection_pairs_with(a_handle) {
227            if intersecting {
228                hit = true;
229            }
230        }
231
232        assert!(hit, "No hit found");
233    }
234
235    #[test]
236    #[cfg(feature = "dim2")]
237    pub fn test_no_rigid_bodies() {
238        use crate::prelude::*;
239        let mut rigid_body_set = RigidBodySet::new();
240        let mut collider_set = ColliderSet::new();
241
242        /* Create the ground. */
243        let collider_a = ColliderBuilder::cuboid(1.0, 1.0)
244            .active_collision_types(ActiveCollisionTypes::all())
245            .sensor(true)
246            .active_events(ActiveEvents::COLLISION_EVENTS)
247            .build();
248
249        let a_handle = collider_set.insert(collider_a);
250
251        let collider_b = ColliderBuilder::cuboid(1.0, 1.0)
252            .active_collision_types(ActiveCollisionTypes::all())
253            .sensor(true)
254            .active_events(ActiveEvents::COLLISION_EVENTS)
255            .build();
256
257        let _ = collider_set.insert(collider_b);
258
259        let integration_parameters = IntegrationParameters::default();
260        let mut broad_phase = BroadPhaseBvh::new();
261        let mut narrow_phase = NarrowPhase::new();
262        let mut collision_pipeline = CollisionPipeline::new();
263        let physics_hooks = ();
264
265        collision_pipeline.step(
266            integration_parameters.prediction_distance(),
267            &mut broad_phase,
268            &mut narrow_phase,
269            &mut rigid_body_set,
270            &mut collider_set,
271            &physics_hooks,
272            &(),
273        );
274
275        let mut hit = false;
276
277        for (_, _, intersecting) in narrow_phase.intersection_pairs_with(a_handle) {
278            if intersecting {
279                hit = true;
280            }
281        }
282
283        assert!(hit, "No hit found");
284    }
285}