Skip to main content

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