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