rapier2d/pipeline/
physics_pipeline.rs

1//! Physics pipeline structures.
2
3use crate::counters::Counters;
4// #[cfg(not(feature = "parallel"))]
5use crate::dynamics::IslandSolver;
6#[cfg(feature = "parallel")]
7use crate::dynamics::JointGraphEdge;
8use crate::dynamics::{
9    CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
10    RigidBodyChanges, RigidBodyType,
11};
12use crate::geometry::{
13    BroadPhaseBvh, BroadPhasePairEvent, ColliderChanges, ColliderHandle, ColliderPair,
14    ContactManifoldIndex, ModifiedColliders, NarrowPhase, TemporaryInteractionIndex,
15};
16use crate::math::{Real, Vector};
17use crate::pipeline::{EventHandler, PhysicsHooks};
18use crate::prelude::ModifiedRigidBodies;
19use {crate::dynamics::RigidBodySet, crate::geometry::ColliderSet};
20
21/// The main physics simulation engine that runs your physics world forward in time.
22///
23/// Think of this as the "game loop" for your physics simulation. Each frame, you call
24/// [`PhysicsPipeline::step`] to advance the simulation by one timestep. This structure
25/// handles all the complex physics calculations: detecting collisions between objects,
26/// resolving contacts so objects don't overlap, and updating positions and velocities.
27///
28/// ## Performance note
29/// This structure only contains temporary working memory (scratch buffers). You can create
30/// a new one anytime, but it's more efficient to reuse the same instance across frames
31/// since Rapier can reuse allocated memory.
32///
33/// ## How it works (simplified)
34/// Rapier uses a time-stepping approach where each step involves:
35/// 1. **Collision detection**: Find which objects are touching or overlapping
36/// 2. **Constraint solving**: Calculate forces to prevent overlaps and enforce joint constraints
37/// 3. **Integration**: Update object positions and velocities based on forces and gravity
38/// 4. **Position correction**: Fix any remaining overlaps that might have occurred
39// NOTE: this contains only workspace data, so there is no point in making this serializable.
40pub struct PhysicsPipeline {
41    /// Counters used for benchmarking only.
42    pub counters: Counters,
43    contact_pair_indices: Vec<TemporaryInteractionIndex>,
44    manifold_indices: Vec<Vec<ContactManifoldIndex>>,
45    joint_constraint_indices: Vec<Vec<ContactManifoldIndex>>,
46    broadphase_collider_pairs: Vec<ColliderPair>,
47    broad_phase_events: Vec<BroadPhasePairEvent>,
48    solvers: Vec<IslandSolver>,
49}
50
51impl Default for PhysicsPipeline {
52    fn default() -> Self {
53        PhysicsPipeline::new()
54    }
55}
56
57#[allow(dead_code)]
58fn check_pipeline_send_sync() {
59    fn do_test<T: Sync>() {}
60    do_test::<PhysicsPipeline>();
61}
62
63impl PhysicsPipeline {
64    /// Creates a new physics pipeline.
65    ///
66    /// Call this once when setting up your physics world. The pipeline can be reused
67    /// across multiple frames for better performance.
68    pub fn new() -> PhysicsPipeline {
69        PhysicsPipeline {
70            counters: Counters::new(true),
71            solvers: vec![],
72            contact_pair_indices: vec![],
73            manifold_indices: vec![],
74            joint_constraint_indices: vec![],
75            broadphase_collider_pairs: vec![],
76            broad_phase_events: vec![],
77        }
78    }
79
80    fn clear_modified_colliders(
81        &mut self,
82        colliders: &mut ColliderSet,
83        modified_colliders: &mut ModifiedColliders,
84    ) {
85        // TODO: we can’t just iterate on `modified_colliders` here to clear the
86        //       flags because the last substep will leave some colliders with
87        //       changes flags set after solving, but without the collider being
88        //       part of the `ModifiedColliders` set. This is a bit error-prone but
89        //       is necessary for the modified information to carry on to the
90        //       next frame’s narrow-phase for updating.
91        for co in colliders.colliders.iter_mut() {
92            co.1.changes = ColliderChanges::empty();
93        }
94        // for handle in modified_colliders.iter() {
95        //     if let Some(co) = colliders.get_mut_internal(*handle) {
96        //         co.changes = ColliderChanges::empty();
97        //     }
98        // }
99
100        modified_colliders.clear();
101    }
102
103    fn clear_modified_bodies(
104        &mut self,
105        bodies: &mut RigidBodySet,
106        modified_bodies: &mut ModifiedRigidBodies,
107    ) {
108        for handle in modified_bodies.iter() {
109            if let Some(rb) = bodies.get_mut_internal(*handle) {
110                rb.changes = RigidBodyChanges::empty();
111            }
112        }
113
114        modified_bodies.clear();
115    }
116
117    fn detect_collisions(
118        &mut self,
119        integration_parameters: &IntegrationParameters,
120        islands: &mut IslandManager,
121        broad_phase: &mut BroadPhaseBvh,
122        narrow_phase: &mut NarrowPhase,
123        bodies: &mut RigidBodySet,
124        colliders: &mut ColliderSet,
125        impulse_joints: &ImpulseJointSet,
126        multibody_joints: &MultibodyJointSet,
127        modified_colliders: &[ColliderHandle],
128        removed_colliders: &[ColliderHandle],
129        hooks: &dyn PhysicsHooks,
130        events: &dyn EventHandler,
131        handle_user_changes: bool,
132    ) {
133        self.counters.stages.collision_detection_time.resume();
134        self.counters.cd.broad_phase_time.resume();
135
136        // Update broad-phase.
137        self.broad_phase_events.clear();
138        self.broadphase_collider_pairs.clear();
139        broad_phase.update(
140            integration_parameters,
141            colliders,
142            bodies,
143            modified_colliders,
144            removed_colliders,
145            &mut self.broad_phase_events,
146        );
147
148        self.counters.cd.broad_phase_time.pause();
149        self.counters.cd.narrow_phase_time.resume();
150
151        // Update narrow-phase.
152        if handle_user_changes {
153            narrow_phase.handle_user_changes(
154                Some(islands),
155                modified_colliders,
156                removed_colliders,
157                colliders,
158                bodies,
159                events,
160            );
161        }
162        narrow_phase.register_pairs(
163            Some(islands),
164            colliders,
165            bodies,
166            &self.broad_phase_events,
167            events,
168        );
169        narrow_phase.compute_contacts(
170            integration_parameters.prediction_distance(),
171            integration_parameters.dt,
172            bodies,
173            colliders,
174            impulse_joints,
175            multibody_joints,
176            hooks,
177            events,
178        );
179        narrow_phase.compute_intersections(bodies, colliders, hooks, events);
180
181        self.counters.cd.narrow_phase_time.pause();
182        self.counters.stages.collision_detection_time.pause();
183    }
184
185    fn build_islands_and_solve_velocity_constraints(
186        &mut self,
187        gravity: &Vector<Real>,
188        integration_parameters: &IntegrationParameters,
189        islands: &mut IslandManager,
190        narrow_phase: &mut NarrowPhase,
191        bodies: &mut RigidBodySet,
192        colliders: &mut ColliderSet,
193        impulse_joints: &mut ImpulseJointSet,
194        multibody_joints: &mut MultibodyJointSet,
195        events: &dyn EventHandler,
196    ) {
197        self.counters.stages.island_construction_time.resume();
198        islands.update_active_set_with_contacts(
199            integration_parameters.dt,
200            integration_parameters.length_unit,
201            bodies,
202            colliders,
203            narrow_phase,
204            impulse_joints,
205            multibody_joints,
206            integration_parameters.min_island_size,
207        );
208
209        if self.manifold_indices.len() < islands.num_islands() {
210            self.manifold_indices
211                .resize(islands.num_islands(), Vec::new());
212        }
213
214        if self.joint_constraint_indices.len() < islands.num_islands() {
215            self.joint_constraint_indices
216                .resize(islands.num_islands(), Vec::new());
217        }
218
219        let mut manifolds = Vec::new();
220        narrow_phase.select_active_contacts(
221            islands,
222            bodies,
223            &mut self.contact_pair_indices,
224            &mut manifolds,
225            &mut self.manifold_indices,
226        );
227        impulse_joints.select_active_interactions(
228            islands,
229            bodies,
230            &mut self.joint_constraint_indices,
231        );
232        self.counters.stages.island_construction_time.pause();
233
234        self.counters.stages.update_time.resume();
235        for handle in islands.active_bodies() {
236            // TODO: should that be moved to the solver (just like we moved
237            //       the multibody dynamics update) since it depends on dt?
238            let rb = bodies.index_mut_internal(*handle);
239            rb.mprops
240                .update_world_mass_properties(rb.body_type, &rb.pos.position);
241            let effective_mass = rb.mprops.effective_mass();
242            rb.forces
243                .compute_effective_force_and_torque(gravity, &effective_mass);
244        }
245        self.counters.stages.update_time.pause();
246
247        self.counters.stages.solver_time.resume();
248        if self.solvers.len() < islands.num_islands() {
249            self.solvers
250                .resize_with(islands.num_islands(), IslandSolver::new);
251        }
252
253        #[cfg(not(feature = "parallel"))]
254        {
255            enable_flush_to_zero!();
256
257            for island_id in 0..islands.num_islands() {
258                self.solvers[island_id].init_and_solve(
259                    island_id,
260                    &mut self.counters,
261                    integration_parameters,
262                    islands,
263                    bodies,
264                    &mut manifolds[..],
265                    &self.manifold_indices[island_id],
266                    impulse_joints.joints_mut(),
267                    &self.joint_constraint_indices[island_id],
268                    multibody_joints,
269                )
270            }
271        }
272
273        #[cfg(feature = "parallel")]
274        {
275            use crate::geometry::ContactManifold;
276            use rayon::prelude::*;
277            use std::sync::atomic::Ordering;
278
279            let num_islands = islands.num_islands();
280            let solvers = &mut self.solvers[..num_islands];
281            let bodies = &std::sync::atomic::AtomicPtr::new(bodies as *mut _);
282            let manifolds = &std::sync::atomic::AtomicPtr::new(&mut manifolds as *mut _);
283            let impulse_joints =
284                &std::sync::atomic::AtomicPtr::new(impulse_joints.joints_vec_mut() as *mut _);
285            let multibody_joints = &std::sync::atomic::AtomicPtr::new(multibody_joints as *mut _);
286            let manifold_indices = &self.manifold_indices[..];
287            let joint_constraint_indices = &self.joint_constraint_indices[..];
288
289            // PERF: right now, we are only doing islands-based parallelism.
290            //       Intra-island parallelism (that hasn’t been ported to the new
291            //       solver yet) will be supported in the future.
292            self.counters.solver.velocity_resolution_time.resume();
293            rayon::scope(|_scope| {
294                enable_flush_to_zero!();
295
296                solvers
297                    .par_iter_mut()
298                    .enumerate()
299                    .for_each(|(island_id, solver)| {
300                        let bodies: &mut RigidBodySet =
301                            unsafe { &mut *bodies.load(Ordering::Relaxed) };
302                        let manifolds: &mut Vec<&mut ContactManifold> =
303                            unsafe { &mut *manifolds.load(Ordering::Relaxed) };
304                        let impulse_joints: &mut Vec<JointGraphEdge> =
305                            unsafe { &mut *impulse_joints.load(Ordering::Relaxed) };
306                        let multibody_joints: &mut MultibodyJointSet =
307                            unsafe { &mut *multibody_joints.load(Ordering::Relaxed) };
308
309                        let mut counters = Counters::new(false);
310                        solver.init_and_solve(
311                            island_id,
312                            &mut counters,
313                            integration_parameters,
314                            islands,
315                            bodies,
316                            &mut manifolds[..],
317                            &manifold_indices[island_id],
318                            impulse_joints,
319                            &joint_constraint_indices[island_id],
320                            multibody_joints,
321                        )
322                    });
323            });
324            self.counters.solver.velocity_resolution_time.pause();
325        }
326
327        // Generate contact force events if needed.
328        let inv_dt = crate::utils::inv(integration_parameters.dt);
329        for pair_id in self.contact_pair_indices.drain(..) {
330            let pair = narrow_phase.contact_pair_at_index(pair_id);
331            let co1 = &colliders[pair.collider1];
332            let co2 = &colliders[pair.collider2];
333            let threshold = co1
334                .effective_contact_force_event_threshold()
335                .min(co2.effective_contact_force_event_threshold());
336
337            if threshold < Real::MAX {
338                let total_magnitude = pair.total_impulse_magnitude() * inv_dt;
339
340                // NOTE: the strict inequality is important here, so we don’t
341                //       trigger an event if the force is 0.0 and the threshold is 0.0.
342                if total_magnitude > threshold {
343                    events.handle_contact_force_event(
344                        integration_parameters.dt,
345                        bodies,
346                        colliders,
347                        pair,
348                        total_magnitude,
349                    );
350                }
351            }
352        }
353
354        self.counters.stages.solver_time.pause();
355    }
356
357    fn run_ccd_motion_clamping(
358        &mut self,
359        integration_parameters: &IntegrationParameters,
360        islands: &IslandManager,
361        bodies: &mut RigidBodySet,
362        colliders: &mut ColliderSet,
363        broad_phase: &mut BroadPhaseBvh,
364        narrow_phase: &NarrowPhase,
365        ccd_solver: &mut CCDSolver,
366        events: &dyn EventHandler,
367    ) {
368        self.counters.ccd.toi_computation_time.start();
369        // Handle CCD
370        let impacts = ccd_solver.predict_impacts_at_next_positions(
371            integration_parameters,
372            islands,
373            bodies,
374            colliders,
375            broad_phase,
376            narrow_phase,
377            events,
378        );
379        ccd_solver.clamp_motions(integration_parameters.dt, bodies, &impacts);
380        self.counters.ccd.toi_computation_time.pause();
381    }
382
383    fn advance_to_final_positions(
384        &mut self,
385        islands: &IslandManager,
386        bodies: &mut RigidBodySet,
387        colliders: &mut ColliderSet,
388        modified_colliders: &mut ModifiedColliders,
389    ) {
390        // Set the rigid-bodies and kinematic bodies to their final position.
391        for handle in islands.active_bodies() {
392            let rb = bodies.index_mut_internal(*handle);
393            rb.pos.position = rb.pos.next_position;
394            rb.colliders
395                .update_positions(colliders, modified_colliders, &rb.pos.position);
396        }
397    }
398
399    fn interpolate_kinematic_velocities(
400        &mut self,
401        integration_parameters: &IntegrationParameters,
402        islands: &IslandManager,
403        bodies: &mut RigidBodySet,
404    ) {
405        // Update kinematic bodies velocities.
406        // TODO: what is the best place for this? It should at least be
407        // located before the island computation because we test the velocity
408        // there to determine if this kinematic body should wake-up dynamic
409        // bodies it is touching.
410        for handle in islands.active_bodies() {
411            // TODO PERF: only iterate on kinematic position-based bodies
412            let rb = bodies.index_mut_internal(*handle);
413
414            match rb.body_type {
415                RigidBodyType::KinematicPositionBased => {
416                    rb.vels = rb.pos.interpolate_velocity(
417                        integration_parameters.inv_dt(),
418                        &rb.mprops.local_mprops.local_com,
419                    );
420                }
421                RigidBodyType::KinematicVelocityBased => {}
422                _ => {}
423            }
424        }
425    }
426
427    /// Advances the physics simulation by one timestep.
428    ///
429    /// This is the main function you'll call every frame in your game loop. It performs all
430    /// physics calculations: collision detection, constraint solving, and updating object positions.
431    ///
432    /// # Parameters
433    ///
434    /// * `gravity` - The gravity vector applied to all dynamic bodies (e.g., `vector![0.0, -9.81, 0.0]` for Earth gravity pointing down)
435    /// * `integration_parameters` - Controls the simulation quality and timestep size (typically 60 Hz = 1/60 second per step)
436    /// * `islands` - Internal system that groups connected objects together for efficient solving (automatically managed)
437    /// * `broad_phase` - Fast collision detection phase that filters out distant object pairs (automatically managed)
438    /// * `narrow_phase` - Precise collision detection that computes exact contact points (automatically managed)
439    /// * `bodies` - Your collection of rigid bodies (the physical objects that move and collide)
440    /// * `colliders` - The collision shapes attached to your bodies (boxes, spheres, meshes, etc.)
441    /// * `impulse_joints` - Regular joints connecting bodies (hinges, sliders, etc.)
442    /// * `multibody_joints` - Articulated joints for robot-like structures (optional, can be empty)
443    /// * `ccd_solver` - Continuous collision detection to prevent fast objects from tunneling through thin walls
444    /// * `hooks` - Optional callbacks to customize collision filtering and contact modification
445    /// * `events` - Optional handler to receive collision events (when objects start/stop touching)
446    ///
447    /// # Example
448    ///
449    /// ```
450    /// # use rapier3d::prelude::*;
451    /// # let mut bodies = RigidBodySet::new();
452    /// # let mut colliders = ColliderSet::new();
453    /// # let mut impulse_joints = ImpulseJointSet::new();
454    /// # let mut multibody_joints = MultibodyJointSet::new();
455    /// # let mut islands = IslandManager::new();
456    /// # let mut broad_phase = BroadPhaseBvh::new();
457    /// # let mut narrow_phase = NarrowPhase::new();
458    /// # let mut ccd_solver = CCDSolver::new();
459    /// # let mut physics_pipeline = PhysicsPipeline::new();
460    /// # let integration_parameters = IntegrationParameters::default();
461    /// // In your game loop:
462    /// physics_pipeline.step(
463    ///     &vector![0.0, -9.81, 0.0],  // Gravity pointing down
464    ///     &integration_parameters,
465    ///     &mut islands,
466    ///     &mut broad_phase,
467    ///     &mut narrow_phase,
468    ///     &mut bodies,
469    ///     &mut colliders,
470    ///     &mut impulse_joints,
471    ///     &mut multibody_joints,
472    ///     &mut ccd_solver,
473    ///     &(),  // No custom hooks
474    ///     &(),  // No event handler
475    /// );
476    /// ```
477    pub fn step(
478        &mut self,
479        gravity: &Vector<Real>,
480        integration_parameters: &IntegrationParameters,
481        islands: &mut IslandManager,
482        broad_phase: &mut BroadPhaseBvh,
483        narrow_phase: &mut NarrowPhase,
484        bodies: &mut RigidBodySet,
485        colliders: &mut ColliderSet,
486        impulse_joints: &mut ImpulseJointSet,
487        multibody_joints: &mut MultibodyJointSet,
488        ccd_solver: &mut CCDSolver,
489        hooks: &dyn PhysicsHooks,
490        events: &dyn EventHandler,
491    ) {
492        self.counters.reset();
493        self.counters.step_started();
494
495        // Apply some of delayed wake-ups.
496        self.counters.stages.user_changes.start();
497        #[cfg(feature = "enhanced-determinism")]
498        let impulse_joints_iterator = impulse_joints
499            .to_wake_up
500            .drain(..)
501            .chain(multibody_joints.to_wake_up.drain(..));
502        #[cfg(not(feature = "enhanced-determinism"))]
503        let impulse_joints_iterator = impulse_joints
504            .to_wake_up
505            .drain()
506            .chain(multibody_joints.to_wake_up.drain());
507        for handle in impulse_joints_iterator {
508            islands.wake_up(bodies, handle, true);
509        }
510
511        // Apply modifications.
512        let mut modified_colliders = colliders.take_modified();
513        let mut removed_colliders = colliders.take_removed();
514
515        super::user_changes::handle_user_changes_to_colliders(
516            bodies,
517            colliders,
518            &modified_colliders[..],
519        );
520
521        let mut modified_bodies = bodies.take_modified();
522        super::user_changes::handle_user_changes_to_rigid_bodies(
523            Some(islands),
524            bodies,
525            colliders,
526            impulse_joints,
527            multibody_joints,
528            &modified_bodies,
529            &mut modified_colliders,
530        );
531
532        // Disabled colliders are treated as if they were removed.
533        // NOTE: this must be called here, after handle_user_changes_to_rigid_bodies to take into
534        //       account colliders disabled because of their parent rigid-body.
535        removed_colliders.extend(
536            modified_colliders
537                .iter()
538                .copied()
539                .filter(|h| colliders.get(*h).map(|c| !c.is_enabled()).unwrap_or(false)),
540        );
541        self.counters.stages.user_changes.pause();
542
543        // TODO: do this only on user-change.
544        // TODO: do we want some kind of automatic inverse kinematics?
545        for multibody in &mut multibody_joints.multibodies {
546            multibody.1.forward_kinematics(bodies, true);
547            multibody
548                .1
549                .update_rigid_bodies_internal(bodies, true, false, false);
550        }
551
552        self.detect_collisions(
553            integration_parameters,
554            islands,
555            broad_phase,
556            narrow_phase,
557            bodies,
558            colliders,
559            impulse_joints,
560            multibody_joints,
561            &modified_colliders,
562            &removed_colliders,
563            hooks,
564            events,
565            true,
566        );
567
568        self.counters.stages.user_changes.resume();
569        self.clear_modified_colliders(colliders, &mut modified_colliders);
570        self.clear_modified_bodies(bodies, &mut modified_bodies);
571        removed_colliders.clear();
572        self.counters.stages.user_changes.pause();
573
574        let mut remaining_time = integration_parameters.dt;
575        let mut integration_parameters = *integration_parameters;
576
577        let (ccd_is_enabled, mut remaining_substeps) =
578            if integration_parameters.max_ccd_substeps == 0 {
579                (false, 1)
580            } else {
581                (true, integration_parameters.max_ccd_substeps)
582            };
583
584        while remaining_substeps > 0 {
585            // If there are more than one CCD substep, we need to split
586            // the timestep into multiple intervals. First, estimate the
587            // size of the time slice we will integrate for this substep.
588            //
589            // Note that we must do this now, before the constraints resolution
590            // because we need to use the correct timestep length for the
591            // integration of external forces.
592            //
593            // If there is only one or zero CCD substep, there is no need
594            // to split the timestep interval. So we can just skip this part.
595            if ccd_is_enabled && remaining_substeps > 1 {
596                // NOTE: Take forces into account when updating the bodies CCD activation flags
597                //       these forces have not been integrated to the body's velocity yet.
598                let ccd_active =
599                    ccd_solver.update_ccd_active_flags(islands, bodies, remaining_time, true);
600                let first_impact = if ccd_active {
601                    ccd_solver.find_first_impact(
602                        remaining_time,
603                        &integration_parameters,
604                        islands,
605                        bodies,
606                        colliders,
607                        broad_phase,
608                        narrow_phase,
609                    )
610                } else {
611                    None
612                };
613
614                if let Some(toi) = first_impact {
615                    let original_interval = remaining_time / (remaining_substeps as Real);
616
617                    if toi < original_interval {
618                        integration_parameters.dt = original_interval;
619                    } else {
620                        integration_parameters.dt =
621                            toi + (remaining_time - toi) / (remaining_substeps as Real);
622                    }
623
624                    remaining_substeps -= 1;
625                } else {
626                    // No impact, don't do any other substep after this one.
627                    integration_parameters.dt = remaining_time;
628                    remaining_substeps = 0;
629                }
630
631                remaining_time -= integration_parameters.dt;
632
633                // Avoid substep length that are too small.
634                if remaining_time <= integration_parameters.min_ccd_dt {
635                    integration_parameters.dt += remaining_time;
636                    remaining_substeps = 0;
637                }
638            } else {
639                integration_parameters.dt = remaining_time;
640                remaining_time = 0.0;
641                remaining_substeps = 0;
642            }
643
644            self.counters.ccd.num_substeps += 1;
645
646            self.interpolate_kinematic_velocities(&integration_parameters, islands, bodies);
647            self.build_islands_and_solve_velocity_constraints(
648                gravity,
649                &integration_parameters,
650                islands,
651                narrow_phase,
652                bodies,
653                colliders,
654                impulse_joints,
655                multibody_joints,
656                events,
657            );
658
659            // If CCD is enabled, execute the CCD motion clamping.
660            if ccd_is_enabled {
661                // NOTE: don't the forces into account when updating the CCD active flags because
662                //       they have already been integrated into the velocities by the solver.
663                let ccd_active = ccd_solver.update_ccd_active_flags(
664                    islands,
665                    bodies,
666                    integration_parameters.dt,
667                    false,
668                );
669                if ccd_active {
670                    self.run_ccd_motion_clamping(
671                        &integration_parameters,
672                        islands,
673                        bodies,
674                        colliders,
675                        broad_phase,
676                        narrow_phase,
677                        ccd_solver,
678                        events,
679                    );
680                }
681            }
682
683            self.counters.stages.update_time.resume();
684            self.advance_to_final_positions(islands, bodies, colliders, &mut modified_colliders);
685            self.counters.stages.update_time.pause();
686
687            if remaining_substeps > 0 {
688                self.detect_collisions(
689                    &integration_parameters,
690                    islands,
691                    broad_phase,
692                    narrow_phase,
693                    bodies,
694                    colliders,
695                    impulse_joints,
696                    multibody_joints,
697                    &modified_colliders,
698                    &[],
699                    hooks,
700                    events,
701                    false,
702                );
703
704                self.clear_modified_colliders(colliders, &mut modified_colliders);
705            } else {
706                // If we ran the last substep, just update the broad-phase bvh instead
707                // of a full collision-detection step.
708                for handle in modified_colliders.iter() {
709                    let co = colliders.index_mut_internal(*handle);
710                    // NOTE: `advance_to_final_positions` might have added disabled colliders to
711                    //       `modified_colliders`. This raises the question: do we want
712                    //       rigid-body transform propagation to happen on disabled colliders if
713                    //       their parent rigid-body is enabled? For now, we are propagating as
714                    //       it feels less surprising to the user and makes handling collider
715                    //       re-enable less awkward.
716                    if co.is_enabled() {
717                        let aabb = co.compute_broad_phase_aabb(&integration_parameters, bodies);
718                        broad_phase.set_aabb(&integration_parameters, *handle, aabb);
719                    }
720
721                    // Clear the modified collider set, but keep the other collider changes flags.
722                    // This is needed so that the narrow-phase at the next timestep knows it must
723                    // not skip these colliders for its update.
724                    // TODO: this doesn’t feel very clean, but leaving the collider in the modified
725                    //       set would be expensive as this will be traversed by all the user-changes
726                    //       functions. An alternative would be to maintain a second modified set,
727                    //       one for user changes, and one for changes applied by the solver but that
728                    //       feels a bit too much. Let’s keep it simple for now and we’ll see how it
729                    //       goes after the persistent island rework.
730                    co.changes.remove(ColliderChanges::IN_MODIFIED_SET);
731                }
732
733                // Empty the modified colliders set. See comment for `co.change.remove(..)` above.
734                modified_colliders.clear();
735            }
736        }
737
738        // Finally, make sure we update the world mass-properties of the rigid-bodies
739        // that moved. Otherwise, users may end up applying forces with respect to an
740        // outdated center of mass.
741        // TODO: avoid updating the world mass properties twice (here, and
742        //       at the beginning of the next timestep) for bodies that were
743        //       not modified by the user in the mean time.
744        self.counters.stages.update_time.resume();
745        for handle in islands.active_bodies() {
746            let rb = bodies.index_mut_internal(*handle);
747            rb.mprops
748                .update_world_mass_properties(rb.body_type, &rb.pos.position);
749        }
750        self.counters.stages.update_time.pause();
751
752        // Re-insert the modified vector we extracted for the borrow-checker.
753        colliders.set_modified(modified_colliders);
754
755        self.counters.step_completed();
756    }
757}
758
759#[cfg(test)]
760mod test {
761    use na::point;
762
763    use crate::dynamics::{
764        CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, RigidBodyBuilder,
765        RigidBodySet,
766    };
767    use crate::geometry::{BroadPhaseBvh, ColliderBuilder, ColliderSet, NarrowPhase};
768    use crate::math::Vector;
769    use crate::pipeline::PhysicsPipeline;
770    use crate::prelude::{MultibodyJointSet, RevoluteJointBuilder, RigidBodyType};
771
772    #[test]
773    fn kinematic_and_fixed_contact_crash() {
774        let mut colliders = ColliderSet::new();
775        let mut impulse_joints = ImpulseJointSet::new();
776        let mut multibody_joints = MultibodyJointSet::new();
777        let mut pipeline = PhysicsPipeline::new();
778        let mut bf = BroadPhaseBvh::new();
779        let mut nf = NarrowPhase::new();
780        let mut bodies = RigidBodySet::new();
781        let mut islands = IslandManager::new();
782
783        let rb = RigidBodyBuilder::fixed().build();
784        let h1 = bodies.insert(rb.clone());
785        let co = ColliderBuilder::ball(10.0).build();
786        colliders.insert_with_parent(co.clone(), h1, &mut bodies);
787
788        // The same but with a kinematic body.
789        let rb = RigidBodyBuilder::kinematic_position_based().build();
790        let h2 = bodies.insert(rb.clone());
791        colliders.insert_with_parent(co, h2, &mut bodies);
792
793        pipeline.step(
794            &Vector::zeros(),
795            &IntegrationParameters::default(),
796            &mut islands,
797            &mut bf,
798            &mut nf,
799            &mut bodies,
800            &mut colliders,
801            &mut impulse_joints,
802            &mut multibody_joints,
803            &mut CCDSolver::new(),
804            &(),
805            &(),
806        );
807    }
808
809    #[test]
810    fn rigid_body_removal_before_step() {
811        let mut colliders = ColliderSet::new();
812        let mut impulse_joints = ImpulseJointSet::new();
813        let mut multibody_joints = MultibodyJointSet::new();
814        let mut pipeline = PhysicsPipeline::new();
815        let mut bf = BroadPhaseBvh::new();
816        let mut nf = NarrowPhase::new();
817        let mut islands = IslandManager::new();
818
819        let mut bodies = RigidBodySet::new();
820
821        // Check that removing the body right after inserting it works.
822        // We add two dynamic bodies, one kinematic body and one fixed body before removing
823        // them. This include a non-regression test where deleting a kinematic body crashes.
824        let rb = RigidBodyBuilder::dynamic().build();
825        let h1 = bodies.insert(rb.clone());
826        let h2 = bodies.insert(rb.clone());
827
828        // The same but with a kinematic body.
829        let rb = RigidBodyBuilder::kinematic_position_based().build();
830        let h3 = bodies.insert(rb.clone());
831
832        // The same but with a fixed body.
833        let rb = RigidBodyBuilder::fixed().build();
834        let h4 = bodies.insert(rb.clone());
835
836        let to_delete = [h1, h2, h3, h4];
837        for h in &to_delete {
838            bodies.remove(
839                *h,
840                &mut islands,
841                &mut colliders,
842                &mut impulse_joints,
843                &mut multibody_joints,
844                true,
845            );
846        }
847
848        pipeline.step(
849            &Vector::zeros(),
850            &IntegrationParameters::default(),
851            &mut islands,
852            &mut bf,
853            &mut nf,
854            &mut bodies,
855            &mut colliders,
856            &mut impulse_joints,
857            &mut multibody_joints,
858            &mut CCDSolver::new(),
859            &(),
860            &(),
861        );
862    }
863
864    #[cfg(feature = "serde-serialize")]
865    #[test]
866    fn rigid_body_removal_snapshot_handle_determinism() {
867        let mut colliders = ColliderSet::new();
868        let mut impulse_joints = ImpulseJointSet::new();
869        let mut multibody_joints = MultibodyJointSet::new();
870        let mut islands = IslandManager::new();
871
872        let mut bodies = RigidBodySet::new();
873        let rb = RigidBodyBuilder::dynamic().build();
874        let h1 = bodies.insert(rb.clone());
875        let h2 = bodies.insert(rb.clone());
876        let h3 = bodies.insert(rb.clone());
877
878        bodies.remove(
879            h1,
880            &mut islands,
881            &mut colliders,
882            &mut impulse_joints,
883            &mut multibody_joints,
884            true,
885        );
886        bodies.remove(
887            h3,
888            &mut islands,
889            &mut colliders,
890            &mut impulse_joints,
891            &mut multibody_joints,
892            true,
893        );
894        bodies.remove(
895            h2,
896            &mut islands,
897            &mut colliders,
898            &mut impulse_joints,
899            &mut multibody_joints,
900            true,
901        );
902
903        let ser_bodies = bincode::serialize(&bodies).unwrap();
904        let mut bodies2: RigidBodySet = bincode::deserialize(&ser_bodies).unwrap();
905
906        let h1a = bodies.insert(rb.clone());
907        let h2a = bodies.insert(rb.clone());
908        let h3a = bodies.insert(rb.clone());
909
910        let h1b = bodies2.insert(rb.clone());
911        let h2b = bodies2.insert(rb.clone());
912        let h3b = bodies2.insert(rb.clone());
913
914        assert_eq!(h1a, h1b);
915        assert_eq!(h2a, h2b);
916        assert_eq!(h3a, h3b);
917    }
918
919    #[test]
920    fn collider_removal_before_step() {
921        let mut pipeline = PhysicsPipeline::new();
922        let gravity = Vector::y() * -9.81;
923        let integration_parameters = IntegrationParameters::default();
924        let mut broad_phase = BroadPhaseBvh::new();
925        let mut narrow_phase = NarrowPhase::new();
926        let mut bodies = RigidBodySet::new();
927        let mut colliders = ColliderSet::new();
928        let mut ccd = CCDSolver::new();
929        let mut impulse_joints = ImpulseJointSet::new();
930        let mut multibody_joints = MultibodyJointSet::new();
931        let mut islands = IslandManager::new();
932        let physics_hooks = ();
933        let event_handler = ();
934
935        let body = RigidBodyBuilder::dynamic().build();
936        let b_handle = bodies.insert(body);
937        let collider = ColliderBuilder::ball(1.0).build();
938        let c_handle = colliders.insert_with_parent(collider, b_handle, &mut bodies);
939        colliders.remove(c_handle, &mut islands, &mut bodies, true);
940        bodies.remove(
941            b_handle,
942            &mut islands,
943            &mut colliders,
944            &mut impulse_joints,
945            &mut multibody_joints,
946            true,
947        );
948
949        for _ in 0..10 {
950            pipeline.step(
951                &gravity,
952                &integration_parameters,
953                &mut islands,
954                &mut broad_phase,
955                &mut narrow_phase,
956                &mut bodies,
957                &mut colliders,
958                &mut impulse_joints,
959                &mut multibody_joints,
960                &mut ccd,
961                &physics_hooks,
962                &event_handler,
963            );
964        }
965    }
966
967    #[test]
968    fn rigid_body_type_changed_dynamic_is_in_active_set() {
969        let mut colliders = ColliderSet::new();
970        let mut impulse_joints = ImpulseJointSet::new();
971        let mut multibody_joints = MultibodyJointSet::new();
972        let mut pipeline = PhysicsPipeline::new();
973        let mut bf = BroadPhaseBvh::new();
974        let mut nf = NarrowPhase::new();
975        let mut islands = IslandManager::new();
976
977        let mut bodies = RigidBodySet::new();
978
979        // Initialize body as kinematic with mass
980        let rb = RigidBodyBuilder::kinematic_position_based()
981            .additional_mass(1.0)
982            .build();
983        let h = bodies.insert(rb.clone());
984
985        // Step once
986        let gravity = Vector::y() * -9.81;
987        pipeline.step(
988            &gravity,
989            &IntegrationParameters::default(),
990            &mut islands,
991            &mut bf,
992            &mut nf,
993            &mut bodies,
994            &mut colliders,
995            &mut impulse_joints,
996            &mut multibody_joints,
997            &mut CCDSolver::new(),
998            &(),
999            &(),
1000        );
1001
1002        // Switch body type to Dynamic
1003        bodies
1004            .get_mut(h)
1005            .unwrap()
1006            .set_body_type(RigidBodyType::Dynamic, true);
1007
1008        // Step again
1009        pipeline.step(
1010            &gravity,
1011            &IntegrationParameters::default(),
1012            &mut islands,
1013            &mut bf,
1014            &mut nf,
1015            &mut bodies,
1016            &mut colliders,
1017            &mut impulse_joints,
1018            &mut multibody_joints,
1019            &mut CCDSolver::new(),
1020            &(),
1021            &(),
1022        );
1023
1024        let body = bodies.get(h).unwrap();
1025        let h_y = body.pos.position.translation.y;
1026
1027        // Expect gravity to be applied on second step after switching to Dynamic
1028        assert!(h_y < 0.0);
1029
1030        // Expect body to now be in active_set
1031        assert!(islands.active_set.contains(&h));
1032    }
1033
1034    #[test]
1035    fn joint_step_delta_time_0() {
1036        let mut colliders = ColliderSet::new();
1037        let mut impulse_joints = ImpulseJointSet::new();
1038        let mut multibody_joints = MultibodyJointSet::new();
1039        let mut pipeline = PhysicsPipeline::new();
1040        let mut bf = BroadPhaseBvh::new();
1041        let mut nf = NarrowPhase::new();
1042        let mut islands = IslandManager::new();
1043
1044        let mut bodies = RigidBodySet::new();
1045
1046        // Initialize bodies
1047        let rb = RigidBodyBuilder::fixed().additional_mass(1.0).build();
1048        let h = bodies.insert(rb.clone());
1049        let rb_dynamic = RigidBodyBuilder::dynamic().additional_mass(1.0).build();
1050        let h_dynamic = bodies.insert(rb_dynamic.clone());
1051
1052        // Add joint
1053        #[cfg(feature = "dim2")]
1054        let joint = RevoluteJointBuilder::new()
1055            .local_anchor1(point![0.0, 1.0])
1056            .local_anchor2(point![0.0, -3.0]);
1057        #[cfg(feature = "dim3")]
1058        let joint = RevoluteJointBuilder::new(Vector::z_axis())
1059            .local_anchor1(point![0.0, 1.0, 0.0])
1060            .local_anchor2(point![0.0, -3.0, 0.0]);
1061        impulse_joints.insert(h, h_dynamic, joint, true);
1062
1063        let parameters = IntegrationParameters {
1064            dt: 0.0,
1065            ..Default::default()
1066        };
1067        // Step once
1068        let gravity = Vector::y() * -9.81;
1069        pipeline.step(
1070            &gravity,
1071            &parameters,
1072            &mut islands,
1073            &mut bf,
1074            &mut nf,
1075            &mut bodies,
1076            &mut colliders,
1077            &mut impulse_joints,
1078            &mut multibody_joints,
1079            &mut CCDSolver::new(),
1080            &(),
1081            &(),
1082        );
1083        let translation = bodies[h_dynamic].translation();
1084        let rotation = bodies[h_dynamic].rotation();
1085        assert!(translation.x.is_finite());
1086        assert!(translation.y.is_finite());
1087        #[cfg(feature = "dim2")]
1088        assert!(rotation.is_finite());
1089        #[cfg(feature = "dim3")]
1090        {
1091            assert!(translation.z.is_finite());
1092            assert!(rotation.i.is_finite());
1093            assert!(rotation.j.is_finite());
1094            assert!(rotation.k.is_finite());
1095            assert!(rotation.w.is_finite());
1096        }
1097    }
1098
1099    #[test]
1100    #[cfg(feature = "dim2")]
1101    fn test_multi_sap_disable_body() {
1102        use na::vector;
1103        let mut rigid_body_set = RigidBodySet::new();
1104        let mut collider_set = ColliderSet::new();
1105
1106        /* Create the ground. */
1107        let collider = ColliderBuilder::cuboid(100.0, 0.1).build();
1108        collider_set.insert(collider);
1109
1110        /* Create the bouncing ball. */
1111        let rigid_body = RigidBodyBuilder::dynamic()
1112            .translation(vector![0.0, 10.0])
1113            .build();
1114        let collider = ColliderBuilder::ball(0.5).restitution(0.7).build();
1115        let ball_body_handle = rigid_body_set.insert(rigid_body);
1116        collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);
1117
1118        /* Create other structures necessary for the simulation. */
1119        let gravity = vector![0.0, -9.81];
1120        let integration_parameters = IntegrationParameters::default();
1121        let mut physics_pipeline = PhysicsPipeline::new();
1122        let mut island_manager = IslandManager::new();
1123        let mut broad_phase = BroadPhaseBvh::new();
1124        let mut narrow_phase = NarrowPhase::new();
1125        let mut impulse_joint_set = ImpulseJointSet::new();
1126        let mut multibody_joint_set = MultibodyJointSet::new();
1127        let mut ccd_solver = CCDSolver::new();
1128        let physics_hooks = ();
1129        let event_handler = ();
1130
1131        physics_pipeline.step(
1132            &gravity,
1133            &integration_parameters,
1134            &mut island_manager,
1135            &mut broad_phase,
1136            &mut narrow_phase,
1137            &mut rigid_body_set,
1138            &mut collider_set,
1139            &mut impulse_joint_set,
1140            &mut multibody_joint_set,
1141            &mut ccd_solver,
1142            &physics_hooks,
1143            &event_handler,
1144        );
1145
1146        // Test RigidBodyChanges::POSITION and disable
1147        {
1148            let ball_body = &mut rigid_body_set[ball_body_handle];
1149
1150            // Also, change the translation and rotation to different values
1151            ball_body.set_translation(vector![1.0, 1.0], true);
1152            ball_body.set_rotation(nalgebra::UnitComplex::new(1.0), true);
1153
1154            ball_body.set_enabled(false);
1155        }
1156
1157        physics_pipeline.step(
1158            &gravity,
1159            &integration_parameters,
1160            &mut island_manager,
1161            &mut broad_phase,
1162            &mut narrow_phase,
1163            &mut rigid_body_set,
1164            &mut collider_set,
1165            &mut impulse_joint_set,
1166            &mut multibody_joint_set,
1167            &mut ccd_solver,
1168            &physics_hooks,
1169            &event_handler,
1170        );
1171
1172        // Test RigidBodyChanges::POSITION and enable
1173        {
1174            let ball_body = &mut rigid_body_set[ball_body_handle];
1175
1176            // Also, change the translation and rotation to different values
1177            ball_body.set_translation(vector![0.0, 0.0], true);
1178            ball_body.set_rotation(nalgebra::UnitComplex::new(0.0), true);
1179
1180            ball_body.set_enabled(true);
1181        }
1182
1183        physics_pipeline.step(
1184            &gravity,
1185            &integration_parameters,
1186            &mut island_manager,
1187            &mut broad_phase,
1188            &mut narrow_phase,
1189            &mut rigid_body_set,
1190            &mut collider_set,
1191            &mut impulse_joint_set,
1192            &mut multibody_joint_set,
1193            &mut ccd_solver,
1194            &physics_hooks,
1195            &event_handler,
1196        );
1197    }
1198}