avian2d/dynamics/solver/
plugin.rs

1use crate::{
2    dynamics::{
3        joints::EntityConstraint,
4        solver::{
5            SolverDiagnostics,
6            constraint_graph::{
7                COLOR_OVERFLOW_INDEX, ConstraintGraph, ContactManifoldHandle, GraphColor,
8            },
9            contact::ContactConstraint,
10            schedule::SubstepSolverSystems,
11            softness_parameters::{SoftnessCoefficients, SoftnessParameters},
12            solver_body::{SolverBody, SolverBodyInertia},
13        },
14    },
15    prelude::*,
16};
17use bevy::{
18    ecs::{query::QueryData, system::lifetimeless::Read},
19    prelude::*,
20};
21use core::cmp::Ordering;
22
23/// Manages and solves contacts, joints, and other constraints.
24///
25/// Note that the [`ContactConstraints`] are currently generated by tbe [`NarrowPhasePlugin`].
26///
27/// # Implementation
28///
29/// Avian uses an impulse-based solver with substepping and [soft constraints](super::softness_parameters).
30/// Warm starting is used to improve convergence, along with a relaxation pass to reduce overshooting.
31///
32/// [Speculative collision](dynamics::ccd#speculative-collision) is used by default to prevent tunneling.
33/// Optional [sweep-based Continuous Collision Detection (CCD)](dynamics::ccd#swept-ccd) is handled by the [`CcdPlugin`].
34///
35/// [Joints](dynamics::joints) and user constraints are currently solved using [Extended Position-Based Dynamics (XPBD)](super::xpbd)
36/// if the `xpbd_joints` feature is enabled. In the future, they may transition to an impulse-based approach as well.
37///
38/// ## Solver Bodies
39///
40/// The solver maintains a [`SolverBody`] for each awake dynamic and kinematic body.
41/// It stores the body data needed by the solver in a more optimized format
42/// with better memory locality.
43///
44/// Only awake dynamic bodies and kinematic bodies have an associated solver body.
45/// Static bodies and sleeping dynamic bodies do not move and are not included in the solver.
46///
47/// The [`SolverBodyPlugin`] is added for managing solver bodies and synchronizing them with rigid body data.
48///
49/// # Steps
50///
51/// Below are the main steps of the `SolverPlugin`.
52///
53/// 1. [Prepare solver bodies](SolverSystems::PrepareSolverBodies)
54/// 2. [Prepare joint constraints](SolverSystems::PrepareJoints)
55/// 3. [Prepare contact constraints](SolverSystems::PrepareContactConstraints)
56/// 4. Substepping loop (runs the [`SubstepSchedule`] [`SubstepCount`] times)
57///     1. [Integrate velocities](crate::dynamics::integrator::IntegrationSystems::Velocity)
58///     2. [Warm start](SubstepSolverSystems::WarmStart)
59///     3. [Solve constraints with bias](SubstepSolverSystems::SolveConstraints)
60///     4. [Integrate positions](crate::dynamics::integrator::IntegrationSystems::Position)
61///     5. [Solve constraints without bias to relax velocities](SubstepSolverSystems::Relax)
62/// 5. [Apply restitution](SolverSystems::Restitution)
63/// 6. [Write back solver body data to rigid bodies](SolverSystems::Finalize)
64/// 7. [Store contact impulses for next frame's warm starting](SolverSystems::StoreContactImpulses)
65///
66/// If the `xpbd_joints` feature is enabled, the [`XpbdSolverPlugin`] can also be added to solve joints
67/// using Extended Position-Based Dynamics (XPBD).
68pub struct SolverPlugin {
69    length_unit: Scalar,
70}
71
72impl Default for SolverPlugin {
73    fn default() -> Self {
74        Self::new_with_length_unit(1.0)
75    }
76}
77
78impl SolverPlugin {
79    /// Creates a [`SolverPlugin`] with the given approximate dimensions of most objects.
80    ///
81    /// The length unit will be used for initializing the [`PhysicsLengthUnit`]
82    /// resource unless it already exists.
83    pub fn new_with_length_unit(unit: Scalar) -> Self {
84        Self { length_unit: unit }
85    }
86}
87
88impl Plugin for SolverPlugin {
89    fn build(&self, app: &mut App) {
90        app.init_resource::<SolverConfig>()
91            .init_resource::<ContactSoftnessCoefficients>()
92            .init_resource::<ContactConstraints>()
93            .init_resource::<ConstraintGraph>();
94
95        if app
96            .world()
97            .get_resource::<PhysicsLengthUnit>()
98            .is_none_or(|unit| unit.0 == 1.0)
99        {
100            app.insert_resource(PhysicsLengthUnit(self.length_unit));
101        }
102
103        // Get the `PhysicsSchedule`, and panic if it doesn't exist.
104        let physics = app
105            .get_schedule_mut(PhysicsSchedule)
106            .expect("add PhysicsSchedule first");
107
108        physics.add_systems(update_contact_softness.before(PhysicsStepSystems::NarrowPhase));
109
110        // Prepare contact constraints before the substepping loop.
111        physics.add_systems(
112            prepare_contact_constraints.in_set(SolverSystems::PrepareContactConstraints),
113        );
114
115        // Apply restitution.
116        physics.add_systems(solve_restitution.in_set(SolverSystems::Restitution));
117
118        // Store the current contact impulses for the next frame's warm starting.
119        physics.add_systems(store_contact_impulses.in_set(SolverSystems::StoreContactImpulses));
120
121        // Get the `SubstepSchedule`, and panic if it doesn't exist.
122        let substeps = app
123            .get_schedule_mut(SubstepSchedule)
124            .expect("add SubstepSchedule first");
125
126        // Warm start the impulses.
127        // This applies the impulses stored from the previous substep,
128        // which improves convergence.
129        substeps.add_systems(warm_start.in_set(SubstepSolverSystems::WarmStart));
130
131        // Solve velocities using a position bias.
132        substeps.add_systems(solve_contacts::<true>.in_set(SubstepSolverSystems::SolveConstraints));
133
134        // Relax biased velocities and impulses.
135        // This reduces overshooting caused by warm starting.
136        substeps.add_systems(solve_contacts::<false>.in_set(SubstepSolverSystems::Relax));
137
138        // Perform constraint damping.
139        substeps.add_systems(
140            (
141                joint_damping::<FixedJoint>,
142                joint_damping::<RevoluteJoint>,
143                #[cfg(feature = "3d")]
144                joint_damping::<SphericalJoint>,
145                joint_damping::<PrismaticJoint>,
146                joint_damping::<DistanceJoint>,
147            )
148                .chain()
149                .in_set(SubstepSolverSystems::Damping),
150        );
151    }
152
153    fn finish(&self, app: &mut App) {
154        // Register timer and counter diagnostics for the solver.
155        app.register_physics_diagnostics::<SolverDiagnostics>();
156    }
157}
158
159// TODO: Where should this type be and which plugin should initialize it?
160/// A units-per-meter scaling factor that adjusts the engine's internal properties
161/// to the scale of the world.
162///
163/// For example, a 2D game might use pixels as units and have an average object size
164/// of around 100 pixels. By setting the length unit to `100.0`, the physics engine
165/// will interpret 100 pixels as 1 meter for internal thresholds, improving stability.
166///
167/// Note that this is *not* used to scale forces or any other user-facing inputs or outputs.
168/// Instead, the value is only used to scale some internal length-based tolerances, such as
169/// [`SleepingThreshold::linear`] and [`NarrowPhaseConfig::default_speculative_margin`],
170/// as well as the scale used for [debug rendering](PhysicsDebugPlugin).
171///
172/// Choosing the appropriate length unit can help improve stability and robustness.
173///
174/// Default: `1.0`
175///
176/// # Example
177///
178/// The [`PhysicsLengthUnit`] can be inserted as a resource like normal,
179/// but it can also be specified through the [`PhysicsPlugins`] plugin group.
180///
181/// ```no_run
182/// # #[cfg(feature = "2d")]
183/// use avian2d::prelude::*;
184/// use bevy::prelude::*;
185///
186/// # #[cfg(feature = "2d")]
187/// fn main() {
188///     App::new()
189///         .add_plugins((
190///             DefaultPlugins,
191///             // A 2D game with 100 pixels per meter
192///             PhysicsPlugins::default().with_length_unit(100.0),
193///         ))
194///         .run();
195/// }
196/// # #[cfg(not(feature = "2d"))]
197/// # fn main() {} // Doc test needs main
198/// ```
199#[derive(Resource, Clone, Debug, Deref, DerefMut, PartialEq, Reflect)]
200#[reflect(Resource)]
201pub struct PhysicsLengthUnit(pub Scalar);
202
203impl Default for PhysicsLengthUnit {
204    fn default() -> Self {
205        Self(1.0)
206    }
207}
208
209/// Configuration parameters for the constraint solver that handles
210/// things like contacts and joints.
211///
212/// These are tuned to give good results for most applications, but can
213/// be configured if more control over the simulation behavior is needed.
214#[derive(Resource, Clone, Debug, PartialEq, Reflect)]
215#[reflect(Resource)]
216pub struct SolverConfig {
217    /// The damping ratio used for contact stabilization.
218    ///
219    /// Lower values make contacts more compliant or "springy",
220    /// allowing more visible penetration before overlap has been
221    /// resolved and the contact has been stabilized.
222    ///
223    /// Consider using a higher damping ratio if contacts seem too soft.
224    /// Note that making the value too large can cause instability.
225    ///
226    /// Default: `10.0`.
227    pub contact_damping_ratio: Scalar,
228
229    /// Scales the frequency used for contacts. A higher frequency
230    /// makes contact responses faster and reduces visible springiness,
231    /// but can hurt stability.
232    ///
233    /// The solver computes the frequency using the time step and substep count,
234    /// and limits the maximum frequency to be at most half of the time step due to
235    /// [Nyquist's theorem](https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem).
236    /// This factor scales the resulting frequency, which can lead to unstable behavior
237    /// if the factor is too large.
238    ///
239    /// Default: `1.5`
240    pub contact_frequency_factor: Scalar,
241
242    /// The maximum speed at which overlapping bodies are pushed apart by the solver.
243    ///
244    /// With a small value, overlap is resolved gently and gradually, while large values
245    /// can result in more snappy behavior.
246    ///
247    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
248    ///
249    /// Default: `4.0`
250    pub max_overlap_solve_speed: Scalar,
251
252    /// The coefficient in the `[0, 1]` range applied to
253    /// [warm start](SubstepSolverSystems::WarmStart) impulses.
254    ///
255    /// Warm starting uses the impulses from the previous frame as the initial
256    /// solution for the current frame. This helps the solver reach the desired
257    /// state much faster, meaning that *convergence* is improved.
258    ///
259    /// The coefficient should typically be set to `1.0`.
260    ///
261    /// Default: `1.0`
262    pub warm_start_coefficient: Scalar,
263
264    /// The minimum speed along the contact normal in units per second
265    /// for [restitution](Restitution) to be applied.
266    ///
267    /// An appropriate threshold should typically be small enough that objects
268    /// keep bouncing until the bounces are effectively unnoticeable,
269    /// but large enough that restitution is not applied unnecessarily,
270    /// improving performance and stability.
271    ///
272    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
273    ///
274    /// Default: `1.0`
275    pub restitution_threshold: Scalar,
276
277    /// The number of iterations used for applying [restitution](Restitution).
278    ///
279    /// A higher number of iterations can result in more accurate bounces,
280    /// but it only makes a difference when there are more than one contact point.
281    ///
282    /// For example, with just one iteration, a cube falling flat on the ground
283    /// might bounce and rotate to one side, because the impulses are applied
284    /// to the corners sequentially, and some of the impulses are likely to be larger
285    /// than the others. With multiple iterations, the impulses are applied more evenly.
286    ///
287    /// Default: `1`
288    pub restitution_iterations: usize,
289}
290
291impl Default for SolverConfig {
292    fn default() -> Self {
293        Self {
294            contact_damping_ratio: 10.0,
295            contact_frequency_factor: 1.5,
296            max_overlap_solve_speed: 4.0,
297            warm_start_coefficient: 1.0,
298            restitution_threshold: 1.0,
299            restitution_iterations: 1,
300        }
301    }
302}
303
304/// The [`SoftnessCoefficients`] used for contacts.
305///
306/// **Note**: This resource is updated automatically and not intended to be modified manually.
307/// Use the [`SolverConfig`] resource instead for tuning contact behavior.
308#[derive(Resource, Clone, Copy, PartialEq, Reflect)]
309#[reflect(Resource)]
310pub struct ContactSoftnessCoefficients {
311    /// The [`SoftnessCoefficients`] used for contacts against dynamic bodies.
312    pub dynamic: SoftnessCoefficients,
313    /// The [`SoftnessCoefficients`] used for contacts against static or kinematic bodies.
314    pub non_dynamic: SoftnessCoefficients,
315}
316
317impl Default for ContactSoftnessCoefficients {
318    fn default() -> Self {
319        Self {
320            dynamic: SoftnessParameters::new(10.0, 30.0).compute_coefficients(1.0 / 60.0),
321            non_dynamic: SoftnessParameters::new(10.0, 60.0).compute_coefficients(1.0 / 60.0),
322        }
323    }
324}
325
326fn update_contact_softness(
327    mut coefficients: ResMut<ContactSoftnessCoefficients>,
328    solver_config: Res<SolverConfig>,
329    physics_time: Res<Time<Physics>>,
330    substep_time: Res<Time<Substeps>>,
331) {
332    if solver_config.is_changed() || physics_time.is_changed() || substep_time.is_changed() {
333        let dt = physics_time.delta_secs_f64() as Scalar;
334        let h = substep_time.delta_secs_f64() as Scalar;
335
336        // The contact frequency should at most be half of the time step due to Nyquist's theorem.
337        // https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem
338        let max_hz = 1.0 / (dt * 2.0);
339        let hz = solver_config.contact_frequency_factor * max_hz.min(0.25 / h);
340
341        coefficients.dynamic = SoftnessParameters::new(solver_config.contact_damping_ratio, hz)
342            .compute_coefficients(h);
343
344        // TODO: Perhaps the non-dynamic softness should be configurable separately.
345        // Make contacts against static and kinematic bodies stiffer to avoid clipping through the environment.
346        coefficients.non_dynamic =
347            SoftnessParameters::new(solver_config.contact_damping_ratio, 2.0 * hz)
348                .compute_coefficients(h);
349    }
350}
351
352/// A resource that stores the contact constraints.
353#[derive(Resource, Default, Deref, DerefMut)]
354pub struct ContactConstraints(pub Vec<ContactConstraint>);
355
356#[derive(QueryData)]
357pub(super) struct BodyQuery {
358    pub(super) rb: Read<RigidBody>,
359    pub(super) linear_velocity: Read<LinearVelocity>,
360    pub(super) inertia: Option<Read<SolverBodyInertia>>,
361}
362
363fn prepare_contact_constraints(
364    contact_graph: Res<ContactGraph>,
365    mut constraint_graph: ResMut<ConstraintGraph>,
366    mut diagnostics: ResMut<SolverDiagnostics>,
367    bodies: Query<BodyQuery, RigidBodyActiveFilter>,
368    narrow_phase_config: Res<NarrowPhaseConfig>,
369    contact_softness: Res<ContactSoftnessCoefficients>,
370) {
371    let start = crate::utils::Instant::now();
372
373    for color in constraint_graph.colors.iter_mut() {
374        // TODO: Instead of clearing the vector, we could resize it, and just overwrite the old values in the loop below.
375        //       Then the inner loop could be parallelized too.
376        // TODO: Box2D uses an arena allocator for constraints. Might be worth looking into?
377        color.contact_constraints.clear();
378    }
379
380    // Get colors with at least one active manifold handle.
381    let mut active_colors = constraint_graph
382        .colors
383        .iter_mut()
384        .filter(|color| !color.manifold_handles.is_empty())
385        .collect::<Vec<&mut GraphColor>>();
386
387    // Generate contact constraints for each contact pair, parallelizing over graph colors.
388    crate::utils::par_for_each(&mut active_colors, 2, |_i, color| {
389        for handle in color.manifold_handles.iter() {
390            // Get the contact pair and its manifold.
391            let contact_pair = contact_graph
392                .get_by_id(handle.contact_id)
393                .unwrap_or_else(|| {
394                    panic!("Contact pair not found in graph: {:?}", handle.contact_id)
395                })
396                .1;
397            let manifold_index = handle.manifold_index;
398            let manifold = &contact_pair.manifolds[manifold_index];
399
400            if !contact_pair.generates_constraints() {
401                continue;
402            }
403
404            let (Some(body1_entity), Some(body2_entity)) = (contact_pair.body1, contact_pair.body2)
405            else {
406                continue;
407            };
408
409            // Get the two colliding bodies.
410            let Ok(body1) = bodies.get(body1_entity) else {
411                continue;
412            };
413            let Ok(body2) = bodies.get(body2_entity) else {
414                continue;
415            };
416
417            // TODO: To skip this, we probably shouldn't have manifold handles between non-dynamic bodies
418            //       in the constraint graph. Or alternatively, just don't generate contacts at all for them.
419            if !body1.rb.is_dynamic() && !body2.rb.is_dynamic() {
420                // If both bodies are static or kinematic, skip the contact.
421                continue;
422            }
423
424            let constraint = ContactConstraint::generate(
425                body1_entity,
426                body2_entity,
427                body1,
428                body2,
429                contact_pair.contact_id,
430                manifold,
431                manifold_index,
432                narrow_phase_config.match_contacts,
433                &contact_softness,
434            );
435
436            if !constraint.points.is_empty() {
437                color.contact_constraints.push(constraint);
438            }
439        }
440    });
441
442    diagnostics.prepare_constraints += start.elapsed();
443    diagnostics.contact_constraint_count = constraint_graph
444        .colors
445        .iter()
446        .map(|color| color.contact_constraints.len())
447        .sum::<usize>() as u32;
448}
449
450/// Warm starts the solver by applying the impulses from the previous frame or substep.
451///
452/// See [`SubstepSolverSystems::WarmStart`] for more information.
453fn warm_start(
454    bodies: Query<(&mut SolverBody, &SolverBodyInertia)>,
455    mut constraint_graph: ResMut<ConstraintGraph>,
456    solver_config: Res<SolverConfig>,
457    mut diagnostics: ResMut<SolverDiagnostics>,
458) {
459    let start = crate::utils::Instant::now();
460
461    // Warm start overflow constraints serially. They have lower priority, so they are solved first.
462    for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
463        .contact_constraints
464        .iter_mut()
465    {
466        warm_start_internal(&bodies, constraint, solver_config.warm_start_coefficient);
467    }
468
469    // Warm start constraints in each color in parallel.
470    for color in constraint_graph
471        .colors
472        .iter_mut()
473        .take(COLOR_OVERFLOW_INDEX)
474        .filter(|color| !color.contact_constraints.is_empty())
475    {
476        crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
477            warm_start_internal(&bodies, constraint, solver_config.warm_start_coefficient);
478        });
479    }
480
481    diagnostics.warm_start += start.elapsed();
482}
483
484fn warm_start_internal(
485    bodies: &Query<(&mut SolverBody, &SolverBodyInertia)>,
486    constraint: &mut ContactConstraint,
487    warm_start_coefficient: Scalar,
488) {
489    debug_assert!(!constraint.points.is_empty());
490
491    let mut dummy_body1 = SolverBody::DUMMY;
492    let mut dummy_body2 = SolverBody::DUMMY;
493
494    let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
495    let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
496
497    // Get the solver bodies for the two colliding entities.
498    if let Ok((body, inertia)) = unsafe { bodies.get_unchecked(constraint.body1) } {
499        body1 = body.into_inner();
500        inertia1 = inertia;
501    }
502    if let Ok((body, inertia)) = unsafe { bodies.get_unchecked(constraint.body2) } {
503        body2 = body.into_inner();
504        inertia2 = inertia;
505    }
506
507    // If a body has a higher dominance, it is treated as a static or kinematic body.
508    match constraint.relative_dominance.cmp(&0) {
509        Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
510        Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
511        _ => {}
512    }
513
514    constraint.warm_start(body1, body2, inertia1, inertia2, warm_start_coefficient);
515}
516
517/// Solves contacts by iterating through the given contact constraints
518/// and applying impulses to colliding rigid bodies.
519///
520/// This solve is done `iterations` times. With a substepped solver,
521/// `iterations` should typically be `1`, as substeps will handle the iteration.
522///
523/// If `use_bias` is `true`, the impulses will be boosted to account for overlap.
524/// The solver should often be run twice per frame or substep: first with the bias,
525/// and then without it to *relax* the velocities and reduce overshooting caused by
526/// [warm starting](SubstepSolverSystems::WarmStart).
527///
528/// See [`SubstepSolverSystems::SolveConstraints`] and [`SubstepSolverSystems::Relax`] for more information.
529#[allow(clippy::too_many_arguments)]
530#[allow(clippy::type_complexity)]
531fn solve_contacts<const USE_BIAS: bool>(
532    bodies: Query<(&mut SolverBody, &SolverBodyInertia)>,
533    mut constraint_graph: ResMut<ConstraintGraph>,
534    solver_config: Res<SolverConfig>,
535    length_unit: Res<PhysicsLengthUnit>,
536    time: Res<Time>,
537    mut diagnostics: ResMut<SolverDiagnostics>,
538) {
539    let start = crate::utils::Instant::now();
540
541    let delta_secs = time.delta_seconds_adjusted();
542    let max_overlap_solve_speed = solver_config.max_overlap_solve_speed * length_unit.0;
543
544    // Solve overflow constraints serially. They have lower priority, so they are solved first.
545    for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
546        .contact_constraints
547        .iter_mut()
548    {
549        solve_contacts_internal::<USE_BIAS>(
550            &bodies,
551            constraint,
552            max_overlap_solve_speed,
553            delta_secs,
554        );
555    }
556
557    // Solve contact constraints in each color in parallel.
558    for color in constraint_graph
559        .colors
560        .iter_mut()
561        .take(COLOR_OVERFLOW_INDEX)
562        .filter(|color| !color.contact_constraints.is_empty())
563    {
564        crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
565            solve_contacts_internal::<USE_BIAS>(
566                &bodies,
567                constraint,
568                max_overlap_solve_speed,
569                delta_secs,
570            );
571        });
572    }
573
574    if USE_BIAS {
575        diagnostics.solve_constraints += start.elapsed();
576    } else {
577        diagnostics.relax_velocities += start.elapsed();
578    }
579}
580
581fn solve_contacts_internal<const USE_BIAS: bool>(
582    bodies: &Query<(&mut SolverBody, &SolverBodyInertia)>,
583    constraint: &mut ContactConstraint,
584    max_overlap_solve_speed: Scalar,
585    delta_secs: Scalar,
586) {
587    let mut dummy_body1 = SolverBody::DUMMY;
588    let mut dummy_body2 = SolverBody::DUMMY;
589
590    let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
591    let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
592
593    // Get the solver bodies for the two colliding entities.
594    if let Ok((body, inertia)) = unsafe { bodies.get_unchecked(constraint.body1) } {
595        body1 = body.into_inner();
596        inertia1 = inertia;
597    }
598    if let Ok((body, inertia)) = unsafe { bodies.get_unchecked(constraint.body2) } {
599        body2 = body.into_inner();
600        inertia2 = inertia;
601    }
602
603    // If a body has a higher dominance, it is treated as a static or kinematic body.
604    match constraint.relative_dominance.cmp(&0) {
605        Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
606        Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
607        _ => {}
608    }
609
610    constraint.solve(
611        body1,
612        body2,
613        inertia1,
614        inertia2,
615        delta_secs,
616        USE_BIAS,
617        max_overlap_solve_speed,
618    );
619}
620
621/// Iterates through contact constraints and applies impulses to account for [`Restitution`].
622///
623/// Note that restitution with TGS Soft and speculative contacts may not be perfectly accurate.
624/// This is a tradeoff, but cheap CCD is often more important than perfect restitution.
625///
626/// The number of iterations can be increased with [`SolverConfig::restitution_iterations`]
627/// to apply restitution for multiple contact points more evenly.
628#[allow(clippy::too_many_arguments)]
629#[allow(clippy::type_complexity)]
630fn solve_restitution(
631    bodies: Query<(&mut SolverBody, &SolverBodyInertia)>,
632    mut constraint_graph: ResMut<ConstraintGraph>,
633    solver_config: Res<SolverConfig>,
634    length_unit: Res<PhysicsLengthUnit>,
635    mut diagnostics: ResMut<SolverDiagnostics>,
636) {
637    let start = crate::utils::Instant::now();
638
639    // The restitution threshold determining the speed required for restitution to be applied.
640    let threshold = solver_config.restitution_threshold * length_unit.0;
641
642    // Solve restitution for overflow constraints serially. They have lower priority, so they are solved first.
643    for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
644        .contact_constraints
645        .iter_mut()
646    {
647        solve_restitution_internal(
648            &bodies,
649            constraint,
650            threshold,
651            solver_config.restitution_iterations,
652        );
653    }
654
655    // Solve restitution for contact constraints in each color in parallel.
656    for color in constraint_graph
657        .colors
658        .iter_mut()
659        .take(COLOR_OVERFLOW_INDEX)
660        .filter(|color| !color.contact_constraints.is_empty())
661    {
662        crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
663            solve_restitution_internal(
664                &bodies,
665                constraint,
666                threshold,
667                solver_config.restitution_iterations,
668            );
669        });
670    }
671
672    diagnostics.apply_restitution += start.elapsed();
673}
674
675fn solve_restitution_internal(
676    bodies: &Query<(&mut SolverBody, &SolverBodyInertia)>,
677    constraint: &mut ContactConstraint,
678    threshold: Scalar,
679    iterations: usize,
680) {
681    let restitution = constraint.restitution;
682
683    if restitution == 0.0 {
684        return;
685    }
686
687    let mut dummy_body1 = SolverBody::DUMMY;
688    let mut dummy_body2 = SolverBody::DUMMY;
689
690    let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
691    let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
692
693    // Get the solver bodies for the two colliding entities.
694    if let Ok((body, inertia)) = unsafe { bodies.get_unchecked(constraint.body1) } {
695        body1 = body.into_inner();
696        inertia1 = inertia;
697    }
698    if let Ok((body, inertia)) = unsafe { bodies.get_unchecked(constraint.body2) } {
699        body2 = body.into_inner();
700        inertia2 = inertia;
701    }
702
703    // If a body has a higher dominance, it is treated as a static or kinematic body.
704    match constraint.relative_dominance.cmp(&0) {
705        Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
706        Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
707        _ => {}
708    }
709
710    // Performing multiple iterations can result in more accurate restitution,
711    // but only if there are more than one contact point.
712    let point_count = constraint.points.len();
713    let iterations = if point_count > 1 { iterations } else { 1 };
714
715    for _ in 0..iterations {
716        constraint.apply_restitution(body1, body2, inertia1, inertia2, threshold);
717    }
718}
719
720/// Copies contact impulses from [`ContactConstraints`] to the contacts in the [`ContactGraph`].
721/// They will be used for [warm starting](SubstepSolverSystems::WarmStart).
722fn store_contact_impulses(
723    mut contact_graph: ResMut<ContactGraph>,
724    mut constraint_graph: ResMut<ConstraintGraph>,
725    mut diagnostics: ResMut<SolverDiagnostics>,
726) {
727    let start = crate::utils::Instant::now();
728
729    for color in constraint_graph.colors.iter_mut() {
730        for constraint in &mut color.contact_constraints {
731            let Some(manifold) = contact_graph.get_manifold_mut(ContactManifoldHandle {
732                contact_id: constraint.contact_id,
733                manifold_index: constraint.manifold_index,
734            }) else {
735                unreachable!(
736                    "Contact manifold {:?} for contact ID {:?} not found in contact graph.",
737                    constraint.contact_id, constraint.manifold_index
738                );
739            };
740
741            for (contact, constraint_point) in
742                manifold.points.iter_mut().zip(constraint.points.iter())
743            {
744                contact.warm_start_normal_impulse = constraint_point.normal_part.impulse;
745                contact.warm_start_tangent_impulse = constraint_point
746                    .tangent_part
747                    .as_ref()
748                    .map_or(default(), |part| part.impulse);
749                contact.normal_impulse = constraint_point.normal_part.total_impulse;
750            }
751        }
752    }
753
754    diagnostics.store_impulses += start.elapsed();
755}
756
757/// Applies velocity corrections caused by joint damping.
758#[allow(clippy::type_complexity)]
759pub fn joint_damping<T: Component + EntityConstraint<2>>(
760    bodies: Query<(&mut SolverBody, &SolverBodyInertia)>,
761    joints: Query<(&T, &JointDamping)>,
762    time: Res<Time>,
763) {
764    let delta_secs = time.delta_seconds_adjusted();
765
766    let mut dummy_body1 = SolverBody::DUMMY;
767    let mut dummy_body2 = SolverBody::DUMMY;
768
769    for (joint, damping) in &joints {
770        let entities = joint.entities();
771
772        let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
773        let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
774
775        // Get the solver bodies for the two jointed entities.
776        if let Ok((body, inertia)) = unsafe { bodies.get_unchecked(entities[0]) } {
777            body1 = body.into_inner();
778            inertia1 = inertia;
779        }
780        if let Ok((body, inertia)) = unsafe { bodies.get_unchecked(entities[1]) } {
781            body2 = body.into_inner();
782            inertia2 = inertia;
783        }
784
785        let delta_omega = (body2.angular_velocity - body1.angular_velocity)
786            * (damping.angular * delta_secs).min(1.0);
787
788        if !body1.flags.is_kinematic() {
789            body1.angular_velocity += delta_omega;
790        }
791        if !body2.flags.is_kinematic() {
792            body2.angular_velocity -= delta_omega;
793        }
794
795        let delta_v = (body2.linear_velocity - body1.linear_velocity)
796            * (damping.linear * delta_secs).min(1.0);
797
798        let w1 = inertia1.effective_inv_mass();
799        let w2 = inertia2.effective_inv_mass();
800
801        let p = delta_v * (w1 + w2).recip_or_zero();
802
803        body1.linear_velocity += p * w1;
804        body2.linear_velocity -= p * w2;
805    }
806}