Skip to main content

avian3d/dynamics/solver/
plugin.rs

1use crate::{
2    dynamics::{
3        joints::{
4            EntityConstraint,
5            joint_graph::{JointGraph, JointGraphChange},
6        },
7        solver::{
8            SolverDiagnostics,
9            constraint_graph::{
10                COLOR_OVERFLOW_INDEX, ConstraintGraph, ContactManifoldHandle, GraphColor,
11            },
12            contact::ContactConstraint,
13            islands::{BodyIslandNode, IslandId, PhysicsIslands, WakeIslands},
14            schedule::SubstepSolverSystems,
15            softness_parameters::{SoftnessCoefficients, SoftnessParameters},
16            solver_body::{
17                SolverBodies, SolverBodiesAccess, SolverBody, SolverBodyIndex, SolverBodyInertia,
18            },
19        },
20    },
21    prelude::*,
22};
23use bevy::{
24    ecs::{
25        entity_disabling::Disabled,
26        query::QueryData,
27        system::{SystemState, lifetimeless::Read},
28    },
29    prelude::*,
30};
31use core::cmp::Ordering;
32
33/// Manages and solves contacts, joints, and other constraints.
34///
35/// Note that the [`ContactConstraints`] are currently generated by tbe [`NarrowPhasePlugin`].
36///
37/// # Implementation
38///
39/// Avian uses an impulse-based solver with substepping and [soft constraints](super::softness_parameters).
40/// Warm starting is used to improve convergence, along with a relaxation pass to reduce overshooting.
41///
42/// [Speculative collision](dynamics::ccd#speculative-collision) is used by default to prevent tunneling.
43/// Optional [sweep-based Continuous Collision Detection (CCD)](dynamics::ccd#swept-ccd) is handled by the [`CcdPlugin`].
44///
45/// [Joints](dynamics::joints) and user constraints are currently solved using [Extended Position-Based Dynamics (XPBD)](super::xpbd)
46/// if the `xpbd_joints` feature is enabled. In the future, they may transition to an impulse-based approach as well.
47///
48/// ## Solver Bodies
49///
50/// The solver maintains a [`SolverBody`] for each awake dynamic and kinematic body.
51/// It stores the body data needed by the solver in a more optimized format
52/// with better memory locality.
53///
54/// Only awake dynamic bodies and kinematic bodies have an associated solver body.
55/// Static bodies and sleeping dynamic bodies do not move and are not included in the solver.
56///
57/// The [`SolverBodyPlugin`] is added for managing solver bodies and synchronizing them with rigid body data.
58///
59/// # Steps
60///
61/// Below are the main steps of the `SolverPlugin`.
62///
63/// 1. [Prepare solver bodies](SolverSystems::PrepareSolverBodies)
64/// 2. [Prepare joint constraints](SolverSystems::PrepareJoints)
65/// 3. [Prepare contact constraints](SolverSystems::PrepareContactConstraints)
66/// 4. Substepping loop (runs the [`SubstepSchedule`] [`SubstepCount`] times)
67///     1. [Integrate velocities](crate::dynamics::integrator::IntegrationSystems::Velocity)
68///     2. [Warm start](SubstepSolverSystems::WarmStart)
69///     3. [Solve constraints with bias](SubstepSolverSystems::SolveConstraints)
70///     4. [Integrate positions](crate::dynamics::integrator::IntegrationSystems::Position)
71///     5. [Solve constraints without bias to relax velocities](SubstepSolverSystems::Relax)
72/// 5. [Apply restitution](SolverSystems::Restitution)
73/// 6. [Write back solver body data to rigid bodies](SolverSystems::Finalize)
74/// 7. [Store contact impulses for next frame's warm starting](SolverSystems::StoreContactImpulses)
75///
76/// If the `xpbd_joints` feature is enabled, the [`XpbdSolverPlugin`] can also be added to solve joints
77/// using Extended Position-Based Dynamics (XPBD).
78pub struct SolverPlugin {
79    length_unit: f32,
80}
81
82impl Default for SolverPlugin {
83    fn default() -> Self {
84        Self::new_with_length_unit(1.0)
85    }
86}
87
88impl SolverPlugin {
89    /// Creates a [`SolverPlugin`] with the given approximate dimensions of most objects.
90    ///
91    /// The length unit will be used for initializing the [`PhysicsLengthUnit`]
92    /// resource unless it already exists.
93    pub fn new_with_length_unit(unit: f32) -> Self {
94        Self { length_unit: unit }
95    }
96}
97
98impl Plugin for SolverPlugin {
99    fn build(&self, app: &mut App) {
100        app.init_resource::<SolverConfig>()
101            .init_resource::<ContactSoftnessCoefficients>()
102            .init_resource::<ContactConstraints>()
103            .init_resource::<ConstraintGraph>();
104
105        if app
106            .world()
107            .get_resource::<PhysicsLengthUnit>()
108            .is_none_or(|unit| unit.0 == 1.0)
109        {
110            app.insert_resource(PhysicsLengthUnit(self.length_unit));
111        }
112
113        // Cache the system state used by the `FlushContactStatusChangeQueue` command.
114        let cached_state = CachedContactStatusChangeSystemState(SystemState::new(app.world_mut()));
115        app.insert_resource(cached_state);
116
117        // Get the `PhysicsSchedule`, and panic if it doesn't exist.
118        let physics = app
119            .get_schedule_mut(PhysicsSchedule)
120            .expect("add PhysicsSchedule first");
121
122        physics.add_systems(update_contact_softness.before(PhysicsStepSystems::NarrowPhase));
123
124        // Apply queued contact status changes and joint graph changes
125        // to the constraint graph and islands.
126        //
127        // These are drained at two points each time step:
128        //
129        // - Before the broad phase (contacts only)
130        // - At the start of the solver
131        physics.add_systems(apply_contact_status_changes.in_set(PhysicsStepSystems::First));
132        physics.add_systems(
133            (apply_contact_status_changes, apply_joint_graph_changes)
134                .chain()
135                .in_set(PhysicsStepSystems::Solver)
136                .before(SolverSystems::PrepareSolverBodies),
137        );
138
139        // Prepare contact constraints before the substepping loop.
140        physics.add_systems(
141            prepare_contact_constraints.in_set(SolverSystems::PrepareContactConstraints),
142        );
143
144        // Apply restitution.
145        physics.add_systems(solve_restitution.in_set(SolverSystems::Restitution));
146
147        // Store the current contact impulses for the next frame's warm starting.
148        physics.add_systems(store_contact_impulses.in_set(SolverSystems::StoreContactImpulses));
149
150        // Get the `SubstepSchedule`, and panic if it doesn't exist.
151        let substeps = app
152            .get_schedule_mut(SubstepSchedule)
153            .expect("add SubstepSchedule first");
154
155        // Warm start the impulses.
156        // This applies the impulses stored from the previous substep,
157        // which improves convergence.
158        substeps.add_systems(warm_start.in_set(SubstepSolverSystems::WarmStart));
159
160        // Solve velocities using a position bias.
161        substeps.add_systems(solve_contacts::<true>.in_set(SubstepSolverSystems::SolveConstraints));
162
163        // Relax biased velocities and impulses.
164        // This reduces overshooting caused by warm starting.
165        substeps.add_systems(solve_contacts::<false>.in_set(SubstepSolverSystems::Relax));
166
167        // Perform constraint damping.
168        substeps.add_systems(
169            (
170                joint_damping::<FixedJoint>,
171                joint_damping::<RevoluteJoint>,
172                #[cfg(feature = "3d")]
173                joint_damping::<SphericalJoint>,
174                joint_damping::<PrismaticJoint>,
175                joint_damping::<DistanceJoint>,
176            )
177                .chain()
178                .in_set(SubstepSolverSystems::Damping),
179        );
180    }
181
182    fn finish(&self, app: &mut App) {
183        // Register timer and counter diagnostics for the solver.
184        app.register_physics_diagnostics::<SolverDiagnostics>();
185    }
186}
187
188/// Applies the [`ContactStatusChange`]s to the [`ConstraintGraph`] and [`PhysicsIslands`].
189pub fn apply_contact_status_changes(
190    mut changes: ResMut<ContactStatusChangeQueue>,
191    contact_graph: Res<ContactGraph>,
192    mut constraint_graph: ResMut<ConstraintGraph>,
193    mut islands: Option<ResMut<PhysicsIslands>>,
194    mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
195    mut commands: Commands,
196) {
197    if changes.is_empty() {
198        return;
199    }
200
201    let mut islands_to_wake: Vec<IslandId> = Vec::new();
202
203    for change in changes.drain(..) {
204        apply_contact_status_change(
205            change,
206            &contact_graph,
207            &mut constraint_graph,
208            islands.as_deref_mut(),
209            &mut body_islands,
210            &mut islands_to_wake,
211        );
212    }
213
214    if !islands_to_wake.is_empty() {
215        islands_to_wake.sort_unstable();
216        islands_to_wake.dedup();
217
218        // Wake up the islands that were previously sleeping.
219        commands.queue(WakeIslands(islands_to_wake));
220    }
221}
222
223/// Applies [`JointGraphChange`] messages to [`PhysicsIslands`].
224pub fn apply_joint_graph_changes(
225    mut changes: MessageReader<JointGraphChange>,
226    joint_graph: Res<JointGraph>,
227    mut islands: Option<ResMut<PhysicsIslands>>,
228    mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
229    mut commands: Commands,
230) {
231    let Some(islands) = &mut islands else {
232        // Islands are not in use, so there is nothing to update.
233        changes.clear();
234        return;
235    };
236
237    let mut islands_to_wake: Vec<IslandId> = Vec::new();
238
239    for &change in changes.read() {
240        match change {
241            JointGraphChange::Added(joint_id) => {
242                // The joint may have already been removed before this change was applied.
243                if joint_graph.get_by_id(joint_id).is_none() {
244                    continue;
245                }
246
247                // Link the joint to an island.
248                if let Some(island) = islands.add_joint(joint_id, &mut body_islands, &joint_graph)
249                    && island.is_sleeping
250                {
251                    // Wake up the island if it was previously sleeping.
252                    islands_to_wake.push(island.id);
253                }
254            }
255            JointGraphChange::Removed(joint_id) => {
256                // The joint may never have been linked to an island.
257                if islands.joint_node(joint_id).is_none() {
258                    continue;
259                }
260
261                // Unlink the joint from its island.
262                if let Some(island) = islands.remove_joint(joint_id, &mut body_islands)
263                    && island.is_sleeping
264                {
265                    // Wake up the island if it was previously sleeping.
266                    islands_to_wake.push(island.id);
267                }
268            }
269        }
270    }
271
272    if !islands_to_wake.is_empty() {
273        islands_to_wake.sort_unstable();
274        islands_to_wake.dedup();
275
276        // Wake up the islands that were previously sleeping.
277        commands.queue(WakeIslands(islands_to_wake));
278    }
279}
280
281/// Applies a single [`ContactStatusChange`] to the [`ConstraintGraph`] and [`PhysicsIslands`].
282///
283/// Any islands that should be woken up are pushed to `islands_to_wake`.
284fn apply_contact_status_change(
285    change: ContactStatusChange,
286    contact_graph: &ContactGraph,
287    constraint_graph: &mut ConstraintGraph,
288    islands: Option<&mut PhysicsIslands>,
289    body_islands: &mut Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
290    islands_to_wake: &mut Vec<IslandId>,
291) {
292    match change {
293        ContactStatusChange::StartedGeneratingConstraints(contact) => {
294            let Some((_, contact_pair)) = contact_graph.get_by_id(contact) else {
295                return;
296            };
297
298            // Add a contact constraint for each manifold.
299            for _ in 0..contact_pair.manifolds.len() {
300                constraint_graph.push_manifold(contact_pair);
301            }
302
303            // Link the contact to an island.
304            if let Some(islands) = islands {
305                let island = islands.add_contact(contact, body_islands, contact_graph);
306
307                if let Some(island) = island
308                    && island.is_sleeping
309                {
310                    // Wake up the island if it was previously sleeping.
311                    islands_to_wake.push(island.id);
312                }
313            }
314        }
315        ContactStatusChange::StoppedGeneratingConstraints {
316            contact_id: contact,
317            body1,
318            body2,
319        } => {
320            // Remove the contact's constraints.
321            constraint_graph.remove_contact(contact, body1, body2);
322
323            // Unlink the contact from its island.
324            if let Some(islands) = islands
325                && islands.contact_node(contact).is_some()
326            {
327                let island = islands.remove_contact(contact, body_islands);
328                let island_id = island.id;
329
330                // TODO: Do we need this?
331                if island.is_sleeping {
332                    // Wake up the island if it was previously sleeping.
333                    islands_to_wake.push(island_id);
334                }
335
336                // The island may now be empty. Clean it up.
337                islands.remove_island_if_empty(island_id);
338            }
339        }
340        ContactStatusChange::ManifoldCountChanged {
341            contact,
342            body1,
343            body2,
344            delta,
345        } => {
346            if delta > 0 {
347                // The manifold count increased. Add the new manifolds to the constraint graph.
348                let Some((_, contact_pair)) = contact_graph.get_by_id(contact) else {
349                    return;
350                };
351                for _ in 0..delta {
352                    constraint_graph.push_manifold(contact_pair);
353                }
354            } else {
355                // The manifold count decreased. Remove the excess manifolds.
356                for _ in 0..delta.unsigned_abs() {
357                    constraint_graph.pop_manifold(contact, body1, body2);
358                }
359            }
360        }
361    }
362}
363
364/// A cached [`SystemState`] for [`FlushContactStatusChangeQueue`].
365#[derive(Resource)]
366struct CachedContactStatusChangeSystemState(
367    SystemState<(
368        ResMut<'static, ContactStatusChangeQueue>,
369        Res<'static, ContactGraph>,
370        ResMut<'static, ConstraintGraph>,
371        Option<ResMut<'static, PhysicsIslands>>,
372        Query<
373            'static,
374            'static,
375            &'static mut BodyIslandNode,
376            Or<(With<Disabled>, Without<Disabled>)>,
377        >,
378    )>,
379);
380
381/// A [`Command`] that immediately applies all queued [`ContactStatusChange`]s to the
382/// [`ConstraintGraph`] and [`PhysicsIslands`], rather than waiting for the next physics step.
383///
384/// The narrow phase and the collider/body lifecycle observers record contact status changes into
385/// the [`ContactStatusChangeQueue`], which is normally drained during the physics step.
386/// Queuing this command forces those changes to be applied right away.
387///
388/// This can be useful if the constraint graph and islands need to be consistent immediately
389/// after despawning colliders or bodies outside of the physics schedule. In general however,
390/// it is best to let the physics step handle this automatically.
391///
392/// # Example
393///
394/// ```
395#[cfg_attr(
396    feature = "2d",
397    doc = "use avian2d::{dynamics::solver::FlushContactStatusChangeQueue, prelude::*};"
398)]
399#[cfg_attr(
400    feature = "3d",
401    doc = "use avian3d::{dynamics::solver::FlushContactStatusChangeQueue, prelude::*};"
402)]
403/// use bevy::prelude::*;
404///
405/// fn clear_scene(mut commands: Commands, bodies: Query<Entity, With<RigidBody>>) {
406///     for entity in &bodies {
407///         commands.entity(entity).despawn();
408///     }
409///
410///     // Apply the resulting constraint graph and island changes immediately.
411///     commands.queue(FlushContactStatusChangeQueue);
412/// }
413/// ```
414pub struct FlushContactStatusChangeQueue;
415
416impl Command for FlushContactStatusChangeQueue {
417    type Out = ();
418
419    fn apply(self, world: &mut World) {
420        // The cached system state may not exist if the `SolverPlugin` was not added.
421        world.try_resource_scope(
422            |world, mut state: Mut<CachedContactStatusChangeSystemState>| {
423                let mut islands_to_wake: Vec<IslandId> = Vec::new();
424
425                {
426                    let (
427                        mut changes,
428                        contact_graph,
429                        mut constraint_graph,
430                        mut islands,
431                        mut body_islands,
432                    ) = state.0.get_mut(world).unwrap();
433
434                    for change in changes.drain(..) {
435                        apply_contact_status_change(
436                            change,
437                            &contact_graph,
438                            &mut constraint_graph,
439                            islands.as_deref_mut(),
440                            &mut body_islands,
441                            &mut islands_to_wake,
442                        );
443                    }
444                }
445
446                if !islands_to_wake.is_empty() {
447                    islands_to_wake.sort_unstable();
448                    islands_to_wake.dedup();
449
450                    // Wake up the islands that were previously sleeping.
451                    WakeIslands(islands_to_wake).apply(world);
452                }
453            },
454        );
455    }
456}
457
458// TODO: Where should this type be and which plugin should initialize it?
459/// A units-per-meter scaling factor that adjusts the engine's internal properties
460/// to the scale of the world.
461///
462/// For example, a 2D game might use pixels as units and have an average object size
463/// of around 100 pixels. By setting the length unit to `100.0`, the physics engine
464/// will interpret 100 pixels as 1 meter for internal thresholds, improving stability.
465///
466/// Note that this is *not* used to scale forces or any other user-facing inputs or outputs.
467/// Instead, the value is only used to scale some internal length-based tolerances, such as
468/// [`SleepingThreshold::linear`] and [`NarrowPhaseConfig::contact_tolerance`], as well as
469/// the scale used for [debug rendering](PhysicsDebugPlugin).
470///
471/// Choosing the appropriate length unit can help improve stability and robustness.
472///
473/// Default: `1.0`
474///
475/// # Example
476///
477/// The [`PhysicsLengthUnit`] can be inserted as a resource like normal,
478/// but it can also be specified through the [`PhysicsPlugins`] plugin group.
479///
480/// ```no_run
481/// # #[cfg(feature = "2d")]
482/// use avian2d::prelude::*;
483/// use bevy::prelude::*;
484///
485/// # #[cfg(feature = "2d")]
486/// fn main() {
487///     App::new()
488///         .add_plugins((
489///             DefaultPlugins,
490///             // A 2D game with 100 pixels per meter
491///             PhysicsPlugins::default().with_length_unit(100.0),
492///         ))
493///         .run();
494/// }
495/// # #[cfg(not(feature = "2d"))]
496/// # fn main() {} // Doc test needs main
497/// ```
498#[derive(Resource, Clone, Debug, Deref, DerefMut, PartialEq, Reflect)]
499#[reflect(Resource)]
500pub struct PhysicsLengthUnit(pub f32);
501
502impl Default for PhysicsLengthUnit {
503    fn default() -> Self {
504        Self(1.0)
505    }
506}
507
508/// Configuration parameters for the constraint solver that handles
509/// things like contacts and joints.
510///
511/// These are tuned to give good results for most applications, but can
512/// be configured if more control over the simulation behavior is needed.
513#[derive(Resource, Clone, Debug, PartialEq, Reflect)]
514#[reflect(Resource)]
515pub struct SolverConfig {
516    /// The damping ratio used for contact stabilization.
517    ///
518    /// Lower values make contacts more compliant or "springy",
519    /// allowing more visible penetration before overlap has been
520    /// resolved and the contact has been stabilized.
521    ///
522    /// Consider using a higher damping ratio if contacts seem too soft.
523    /// Note that making the value too large can cause instability.
524    ///
525    /// Default: `10.0`.
526    pub contact_damping_ratio: f32,
527
528    /// Scales the frequency used for contacts. A higher frequency
529    /// makes contact responses faster and reduces visible springiness,
530    /// but can hurt stability.
531    ///
532    /// The solver computes the frequency using the time step and substep count,
533    /// and limits the maximum frequency to be at most half of the time step due to
534    /// [Nyquist's theorem](https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem).
535    /// This factor scales the resulting frequency, which can lead to unstable behavior
536    /// if the factor is too large.
537    ///
538    /// Default: `1.5`
539    pub contact_frequency_factor: f32,
540
541    /// The maximum speed at which overlapping bodies are pushed apart by the solver.
542    ///
543    /// With a small value, overlap is resolved gently and gradually, while large values
544    /// can result in more snappy behavior.
545    ///
546    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
547    ///
548    /// Default: `4.0`
549    pub max_overlap_solve_speed: f32,
550
551    /// The coefficient in the `[0, 1]` range applied to
552    /// [warm start](SubstepSolverSystems::WarmStart) impulses.
553    ///
554    /// Warm starting uses the impulses from the previous frame as the initial
555    /// solution for the current frame. This helps the solver reach the desired
556    /// state much faster, meaning that *convergence* is improved.
557    ///
558    /// The coefficient should typically be set to `1.0`.
559    ///
560    /// Default: `1.0`
561    pub warm_start_coefficient: f32,
562
563    /// The minimum speed along the contact normal in units per second
564    /// for [restitution](Restitution) to be applied.
565    ///
566    /// An appropriate threshold should typically be small enough that objects
567    /// keep bouncing until the bounces are effectively unnoticeable,
568    /// but large enough that restitution is not applied unnecessarily,
569    /// improving performance and stability.
570    ///
571    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
572    ///
573    /// Default: `1.0`
574    pub restitution_threshold: f32,
575
576    /// The number of iterations used for applying [restitution](Restitution).
577    ///
578    /// A higher number of iterations can result in more accurate bounces,
579    /// but it only makes a difference when there are more than one contact point.
580    ///
581    /// For example, with just one iteration, a cube falling flat on the ground
582    /// might bounce and rotate to one side, because the impulses are applied
583    /// to the corners sequentially, and some of the impulses are likely to be larger
584    /// than the others. With multiple iterations, the impulses are applied more evenly.
585    ///
586    /// Default: `1`
587    pub restitution_iterations: usize,
588}
589
590impl Default for SolverConfig {
591    fn default() -> Self {
592        Self {
593            contact_damping_ratio: 10.0,
594            contact_frequency_factor: 1.5,
595            max_overlap_solve_speed: 4.0,
596            warm_start_coefficient: 1.0,
597            restitution_threshold: 1.0,
598            restitution_iterations: 1,
599        }
600    }
601}
602
603/// The [`SoftnessCoefficients`] used for contacts.
604///
605/// **Note**: This resource is updated automatically and not intended to be modified manually.
606/// Use the [`SolverConfig`] resource instead for tuning contact behavior.
607#[derive(Resource, Clone, Copy, PartialEq, Reflect)]
608#[reflect(Resource)]
609pub struct ContactSoftnessCoefficients {
610    /// The [`SoftnessCoefficients`] used for contacts against dynamic bodies.
611    pub dynamic: SoftnessCoefficients,
612    /// The [`SoftnessCoefficients`] used for contacts against static or kinematic bodies.
613    pub non_dynamic: SoftnessCoefficients,
614}
615
616impl Default for ContactSoftnessCoefficients {
617    fn default() -> Self {
618        Self {
619            dynamic: SoftnessParameters::new(10.0, 30.0).compute_coefficients(1.0 / 60.0),
620            non_dynamic: SoftnessParameters::new(10.0, 60.0).compute_coefficients(1.0 / 60.0),
621        }
622    }
623}
624
625fn update_contact_softness(
626    mut coefficients: ResMut<ContactSoftnessCoefficients>,
627    solver_config: Res<SolverConfig>,
628    physics_time: Res<Time<Physics>>,
629    substep_time: Res<Time<Substeps>>,
630) {
631    if solver_config.is_changed() || physics_time.is_changed() || substep_time.is_changed() {
632        let dt = physics_time.delta_secs();
633        let h = substep_time.delta_secs();
634
635        // The contact frequency should at most be half of the time step due to Nyquist's theorem.
636        // https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem
637        let max_hz = 1.0 / (dt * 2.0);
638        let hz = solver_config.contact_frequency_factor * max_hz.min(0.25 / h);
639
640        coefficients.dynamic = SoftnessParameters::new(solver_config.contact_damping_ratio, hz)
641            .compute_coefficients(h);
642
643        // TODO: Perhaps the non-dynamic softness should be configurable separately.
644        // Make contacts against static and kinematic bodies stiffer to avoid clipping through the environment.
645        coefficients.non_dynamic =
646            SoftnessParameters::new(solver_config.contact_damping_ratio, 2.0 * hz)
647                .compute_coefficients(h);
648    }
649}
650
651/// A resource that stores the contact constraints.
652#[derive(Resource, Default, Deref, DerefMut)]
653pub struct ContactConstraints(pub Vec<ContactConstraint>);
654
655#[derive(QueryData)]
656pub(super) struct BodyQuery {
657    pub(super) rb: Read<RigidBody>,
658    pub(super) linear_velocity: Read<LinearVelocity>,
659    pub(super) index: Option<Read<SolverBodyIndex>>,
660}
661
662fn prepare_contact_constraints(
663    contact_graph: Res<ContactGraph>,
664    mut constraint_graph: ResMut<ConstraintGraph>,
665    mut diagnostics: ResMut<SolverDiagnostics>,
666    bodies: Query<BodyQuery, RigidBodyActiveFilter>,
667    solver_bodies: Res<SolverBodies>,
668    narrow_phase_config: Res<NarrowPhaseConfig>,
669    contact_softness: Res<ContactSoftnessCoefficients>,
670) {
671    let start = crate::utils::Instant::now();
672
673    for color in constraint_graph.colors.iter_mut() {
674        // TODO: Instead of clearing the vector, we could resize it, and just overwrite the old values in the loop below.
675        //       Then the inner loop could be parallelized too.
676        // TODO: Box2D uses an arena allocator for constraints. Might be worth looking into?
677        color.contact_constraints.clear();
678    }
679
680    // Get colors with at least one active manifold handle.
681    let mut active_colors = constraint_graph
682        .colors
683        .iter_mut()
684        .filter(|color| !color.manifold_handles.is_empty())
685        .collect::<Vec<&mut GraphColor>>();
686
687    // Generate contact constraints for each contact pair, parallelizing over graph colors.
688    crate::utils::par_for_each(&mut active_colors, 2, |_i, color| {
689        for handle in color.manifold_handles.iter() {
690            // Get the contact pair and its manifold.
691            let contact_pair = contact_graph
692                .get_by_id(handle.contact_id)
693                .unwrap_or_else(|| {
694                    panic!("Contact pair not found in graph: {:?}", handle.contact_id)
695                })
696                .1;
697            let manifold_index = handle.manifold_index;
698            let manifold = &contact_pair.manifolds[manifold_index];
699
700            if !contact_pair.generates_constraints() {
701                continue;
702            }
703
704            let (Some(body1_entity), Some(body2_entity)) = (contact_pair.body1, contact_pair.body2)
705            else {
706                continue;
707            };
708
709            // Get the two colliding bodies.
710            let Ok(body1) = bodies.get(body1_entity) else {
711                continue;
712            };
713            let Ok(body2) = bodies.get(body2_entity) else {
714                continue;
715            };
716
717            // TODO: To skip this, we probably shouldn't have manifold handles between non-dynamic bodies
718            //       in the constraint graph. Or alternatively, just don't generate contacts at all for them.
719            if !body1.rb.is_dynamic() && !body2.rb.is_dynamic() {
720                // If both bodies are static or kinematic, skip the contact.
721                continue;
722            }
723
724            // Look up the solver body indices and inertias, falling back to dummy inertia
725            // for static bodies without an associated solver body.
726            let index1 = body1.index.copied().unwrap_or(SolverBodyIndex::INVALID);
727            let index2 = body2.index.copied().unwrap_or(SolverBodyIndex::INVALID);
728            let inertia1 = solver_bodies
729                .get_inertia(index1)
730                .unwrap_or(&SolverBodyInertia::DUMMY);
731            let inertia2 = solver_bodies
732                .get_inertia(index2)
733                .unwrap_or(&SolverBodyInertia::DUMMY);
734
735            let constraint = ContactConstraint::generate(
736                index1,
737                index2,
738                inertia1,
739                inertia2,
740                body1.linear_velocity.0,
741                body2.linear_velocity.0,
742                contact_pair.contact_id,
743                manifold,
744                manifold_index,
745                narrow_phase_config.match_contacts,
746                &contact_softness,
747            );
748
749            if !constraint.points.is_empty() {
750                color.contact_constraints.push(constraint);
751            }
752        }
753    });
754
755    diagnostics.prepare_constraints += start.elapsed();
756    diagnostics.contact_constraint_count = constraint_graph
757        .colors
758        .iter()
759        .map(|color| color.contact_constraints.len())
760        .sum::<usize>() as u32;
761}
762
763/// Warm starts the solver by applying the impulses from the previous frame or substep.
764///
765/// See [`SubstepSolverSystems::WarmStart`] for more information.
766fn warm_start(
767    mut solver_bodies: ResMut<SolverBodies>,
768    mut constraint_graph: ResMut<ConstraintGraph>,
769    solver_config: Res<SolverConfig>,
770    mut diagnostics: ResMut<SolverDiagnostics>,
771) {
772    let start = crate::utils::Instant::now();
773
774    let access = solver_bodies.access();
775
776    // Warm start overflow constraints serially. They have lower priority, so they are solved first.
777    for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
778        .contact_constraints
779        .iter_mut()
780    {
781        warm_start_internal(&access, constraint, solver_config.warm_start_coefficient);
782    }
783
784    // Warm start constraints in each color in parallel.
785    for color in constraint_graph
786        .colors
787        .iter_mut()
788        .take(COLOR_OVERFLOW_INDEX)
789        .filter(|color| !color.contact_constraints.is_empty())
790    {
791        crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
792            warm_start_internal(&access, constraint, solver_config.warm_start_coefficient);
793        });
794    }
795
796    diagnostics.warm_start += start.elapsed();
797}
798
799fn warm_start_internal(
800    access: &SolverBodiesAccess,
801    constraint: &mut ContactConstraint,
802    warm_start_coefficient: f32,
803) {
804    debug_assert!(!constraint.points.is_empty());
805
806    let mut dummy_body1 = SolverBody::DUMMY;
807    let mut dummy_body2 = SolverBody::DUMMY;
808
809    let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
810    let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
811
812    // Get the solver bodies for the two colliding bodies. A dummy body is used
813    // for static bodies without an associated solver body.
814    //
815    // SAFETY: Constraint graph coloring guarantees that the two bodies are distinct, and that no
816    //         other constraint solved in parallel within this color touches the same bodies.
817    let (b1, b2) =
818        unsafe { access.get_pair_unchecked_mut(constraint.body_index1, constraint.body_index2) };
819    if let Some((body, inertia)) = b1 {
820        body1 = body;
821        inertia1 = inertia;
822    }
823    if let Some((body, inertia)) = b2 {
824        body2 = body;
825        inertia2 = inertia;
826    }
827
828    // If a body has a higher dominance, it is treated as a static or kinematic body.
829    match constraint.relative_dominance.cmp(&0) {
830        Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
831        Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
832        _ => {}
833    }
834
835    constraint.warm_start(body1, body2, inertia1, inertia2, warm_start_coefficient);
836}
837
838/// Solves contacts by iterating through the given contact constraints
839/// and applying impulses to colliding rigid bodies.
840///
841/// This solve is done `iterations` times. With a substepped solver,
842/// `iterations` should typically be `1`, as substeps will handle the iteration.
843///
844/// If `use_bias` is `true`, the impulses will be boosted to account for overlap.
845/// The solver should often be run twice per frame or substep: first with the bias,
846/// and then without it to *relax* the velocities and reduce overshooting caused by
847/// [warm starting](SubstepSolverSystems::WarmStart).
848///
849/// See [`SubstepSolverSystems::SolveConstraints`] and [`SubstepSolverSystems::Relax`] for more information.
850#[allow(clippy::too_many_arguments)]
851#[allow(clippy::type_complexity)]
852fn solve_contacts<const USE_BIAS: bool>(
853    mut solver_bodies: ResMut<SolverBodies>,
854    mut constraint_graph: ResMut<ConstraintGraph>,
855    solver_config: Res<SolverConfig>,
856    length_unit: Res<PhysicsLengthUnit>,
857    time: Res<Time>,
858    mut diagnostics: ResMut<SolverDiagnostics>,
859) {
860    let start = crate::utils::Instant::now();
861
862    let delta_secs = time.delta_secs();
863    let max_overlap_solve_speed = solver_config.max_overlap_solve_speed * length_unit.0;
864
865    let access = solver_bodies.access();
866
867    // Solve overflow constraints serially. They have lower priority, so they are solved first.
868    for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
869        .contact_constraints
870        .iter_mut()
871    {
872        solve_contacts_internal::<USE_BIAS>(
873            &access,
874            constraint,
875            max_overlap_solve_speed,
876            delta_secs,
877        );
878    }
879
880    // Solve contact constraints in each color in parallel.
881    for color in constraint_graph
882        .colors
883        .iter_mut()
884        .take(COLOR_OVERFLOW_INDEX)
885        .filter(|color| !color.contact_constraints.is_empty())
886    {
887        crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
888            solve_contacts_internal::<USE_BIAS>(
889                &access,
890                constraint,
891                max_overlap_solve_speed,
892                delta_secs,
893            );
894        });
895    }
896
897    if USE_BIAS {
898        diagnostics.solve_constraints += start.elapsed();
899    } else {
900        diagnostics.relax_velocities += start.elapsed();
901    }
902}
903
904fn solve_contacts_internal<const USE_BIAS: bool>(
905    access: &SolverBodiesAccess,
906    constraint: &mut ContactConstraint,
907    max_overlap_solve_speed: f32,
908    delta_secs: f32,
909) {
910    let mut dummy_body1 = SolverBody::DUMMY;
911    let mut dummy_body2 = SolverBody::DUMMY;
912
913    let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
914    let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
915
916    // Get the solver bodies for the two colliding bodies. A dummy body is used
917    // for static bodies without an associated solver body.
918    //
919    // SAFETY: Constraint graph coloring guarantees that the two bodies are distinct, and that no
920    //         other constraint solved in parallel within this color touches the same bodies.
921    let (b1, b2) =
922        unsafe { access.get_pair_unchecked_mut(constraint.body_index1, constraint.body_index2) };
923    if let Some((body, inertia)) = b1 {
924        body1 = body;
925        inertia1 = inertia;
926    }
927    if let Some((body, inertia)) = b2 {
928        body2 = body;
929        inertia2 = inertia;
930    }
931
932    // If a body has a higher dominance, it is treated as a static or kinematic body.
933    match constraint.relative_dominance.cmp(&0) {
934        Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
935        Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
936        _ => {}
937    }
938
939    constraint.solve::<USE_BIAS>(
940        body1,
941        body2,
942        inertia1,
943        inertia2,
944        delta_secs,
945        max_overlap_solve_speed,
946    );
947}
948
949/// Iterates through contact constraints and applies impulses to account for [`Restitution`].
950///
951/// Note that restitution with TGS Soft and speculative contacts may not be perfectly accurate.
952/// This is a tradeoff, but cheap CCD is often more important than perfect restitution.
953///
954/// The number of iterations can be increased with [`SolverConfig::restitution_iterations`]
955/// to apply restitution for multiple contact points more evenly.
956#[allow(clippy::too_many_arguments)]
957#[allow(clippy::type_complexity)]
958fn solve_restitution(
959    mut solver_bodies: ResMut<SolverBodies>,
960    mut constraint_graph: ResMut<ConstraintGraph>,
961    solver_config: Res<SolverConfig>,
962    length_unit: Res<PhysicsLengthUnit>,
963    mut diagnostics: ResMut<SolverDiagnostics>,
964) {
965    let start = crate::utils::Instant::now();
966
967    // The restitution threshold determining the speed required for restitution to be applied.
968    let threshold = solver_config.restitution_threshold * length_unit.0;
969
970    let access = solver_bodies.access();
971
972    // Solve restitution for overflow constraints serially. They have lower priority, so they are solved first.
973    for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
974        .contact_constraints
975        .iter_mut()
976    {
977        solve_restitution_internal(
978            &access,
979            constraint,
980            threshold,
981            solver_config.restitution_iterations,
982        );
983    }
984
985    // Solve restitution for contact constraints in each color in parallel.
986    for color in constraint_graph
987        .colors
988        .iter_mut()
989        .take(COLOR_OVERFLOW_INDEX)
990        .filter(|color| !color.contact_constraints.is_empty())
991    {
992        crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
993            solve_restitution_internal(
994                &access,
995                constraint,
996                threshold,
997                solver_config.restitution_iterations,
998            );
999        });
1000    }
1001
1002    diagnostics.apply_restitution += start.elapsed();
1003}
1004
1005fn solve_restitution_internal(
1006    access: &SolverBodiesAccess,
1007    constraint: &mut ContactConstraint,
1008    threshold: f32,
1009    iterations: usize,
1010) {
1011    let restitution = constraint.restitution;
1012
1013    if restitution == 0.0 {
1014        return;
1015    }
1016
1017    let mut dummy_body1 = SolverBody::DUMMY;
1018    let mut dummy_body2 = SolverBody::DUMMY;
1019
1020    let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
1021    let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
1022
1023    // Get the solver bodies for the two colliding bodies. A dummy body is used
1024    // for static bodies without an associated solver body.
1025    //
1026    // SAFETY: Constraint graph coloring guarantees that the two bodies are distinct, and that no
1027    //         other constraint solved in parallel within this color touches the same bodies.
1028    let (b1, b2) =
1029        unsafe { access.get_pair_unchecked_mut(constraint.body_index1, constraint.body_index2) };
1030    if let Some((body, inertia)) = b1 {
1031        body1 = body;
1032        inertia1 = inertia;
1033    }
1034    if let Some((body, inertia)) = b2 {
1035        body2 = body;
1036        inertia2 = inertia;
1037    }
1038
1039    // If a body has a higher dominance, it is treated as a static or kinematic body.
1040    match constraint.relative_dominance.cmp(&0) {
1041        Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
1042        Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
1043        _ => {}
1044    }
1045
1046    // Performing multiple iterations can result in more accurate restitution,
1047    // but only if there are more than one contact point.
1048    let point_count = constraint.points.len();
1049    let iterations = if point_count > 1 { iterations } else { 1 };
1050
1051    for _ in 0..iterations {
1052        constraint.apply_restitution(body1, body2, inertia1, inertia2, threshold);
1053    }
1054}
1055
1056/// Copies contact impulses from [`ContactConstraints`] to the contacts in the [`ContactGraph`].
1057/// They will be used for [warm starting](SubstepSolverSystems::WarmStart).
1058fn store_contact_impulses(
1059    mut contact_graph: ResMut<ContactGraph>,
1060    mut constraint_graph: ResMut<ConstraintGraph>,
1061    mut diagnostics: ResMut<SolverDiagnostics>,
1062) {
1063    let start = crate::utils::Instant::now();
1064
1065    for color in constraint_graph.colors.iter_mut() {
1066        for constraint in &mut color.contact_constraints {
1067            let Some(manifold) = contact_graph.get_manifold_mut(ContactManifoldHandle {
1068                contact_id: constraint.contact_id,
1069                manifold_index: constraint.manifold_index,
1070            }) else {
1071                unreachable!(
1072                    "Contact manifold {:?} for contact ID {:?} not found in contact graph.",
1073                    constraint.contact_id, constraint.manifold_index
1074                );
1075            };
1076
1077            for (contact, constraint_point) in
1078                manifold.points.iter_mut().zip(constraint.points.iter())
1079            {
1080                contact.warm_start_normal_impulse = constraint_point.normal_part.impulse;
1081                contact.warm_start_tangent_impulse = constraint_point
1082                    .tangent_part
1083                    .as_ref()
1084                    .map_or(default(), |part| part.impulse);
1085                contact.normal_impulse = constraint_point.normal_part.total_impulse;
1086            }
1087        }
1088    }
1089
1090    diagnostics.store_impulses += start.elapsed();
1091}
1092
1093/// Applies velocity corrections caused by joint damping.
1094#[allow(clippy::type_complexity)]
1095pub fn joint_damping<T: Component + EntityConstraint<2>>(
1096    mut solver_bodies: ResMut<SolverBodies>,
1097    index_query: Query<&SolverBodyIndex>,
1098    joints: Query<(&T, &JointDamping)>,
1099    time: Res<Time>,
1100) {
1101    let delta_secs = time.delta_secs();
1102
1103    let access = solver_bodies.access();
1104
1105    let mut dummy_body1 = SolverBody::DUMMY;
1106    let mut dummy_body2 = SolverBody::DUMMY;
1107
1108    for (joint, damping) in &joints {
1109        let entities = joint.entities();
1110
1111        // Map the two jointed entities to their solver body indices, using an invalid index
1112        // for static bodies without an associated solver body.
1113        let index1 = index_query
1114            .get(entities[0])
1115            .copied()
1116            .unwrap_or(SolverBodyIndex::INVALID);
1117        let index2 = index_query
1118            .get(entities[1])
1119            .copied()
1120            .unwrap_or(SolverBodyIndex::INVALID);
1121
1122        if index1 == index2 {
1123            continue;
1124        }
1125
1126        let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
1127        let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
1128
1129        // Get the solver bodies for the two jointed bodies.
1130        //
1131        // SAFETY: The two jointed bodies are distinct, and joints are processed serially here.
1132        let (b1, b2) = unsafe { access.get_pair_unchecked_mut(index1, index2) };
1133        // Kinematic bodies keep the zero dummy inertia, so damping never changes their velocity.
1134        if let Some((body, inertia)) = b1 {
1135            if !body.flags.is_kinematic() {
1136                inertia1 = inertia;
1137            }
1138            body1 = body;
1139        }
1140        if let Some((body, inertia)) = b2 {
1141            if !body.flags.is_kinematic() {
1142                inertia2 = inertia;
1143            }
1144            body2 = body;
1145        }
1146
1147        let delta_omega = (body2.angular_velocity - body1.angular_velocity)
1148            * (damping.angular * delta_secs).min(1.0);
1149
1150        let i1 = inertia1.effective_inv_angular_inertia();
1151        let i2 = inertia2.effective_inv_angular_inertia();
1152
1153        #[cfg(feature = "2d")]
1154        let angular_impulse = (i1 + i2).recip_or_zero() * delta_omega;
1155        #[cfg(feature = "3d")]
1156        let angular_impulse = inverse_on_free_axes(i1 + i2) * delta_omega;
1157        body1.angular_velocity += i1 * angular_impulse;
1158        body2.angular_velocity -= i2 * angular_impulse;
1159
1160        let delta_v = (body2.linear_velocity - body1.linear_velocity)
1161            * (damping.linear * delta_secs).min(1.0);
1162
1163        let w1 = inertia1.effective_inv_mass();
1164        let w2 = inertia2.effective_inv_mass();
1165
1166        let p = delta_v * (w1 + w2).recip_or_zero();
1167
1168        body1.linear_velocity += p * w1;
1169        body2.linear_velocity -= p * w2;
1170    }
1171}
1172
1173/// Inverts a summed inverse inertia on its non-zero axes, so a locked axis doesn't zero the whole inverse.
1174#[cfg(feature = "3d")]
1175fn inverse_on_free_axes(inv_inertia: SymmetricTensor) -> SymmetricTensor {
1176    let locked = SymmetricTensor::from_diagonal(Vec3::select(
1177        inv_inertia.diagonal().cmpeq(Vec3::ZERO),
1178        Vec3::ONE,
1179        Vec3::ZERO,
1180    ));
1181    (inv_inertia + locked).inverse_or_zero() - locked
1182}