avian3d/dynamics/solver/
schedule.rs

1//! Sets up the default scheduling, system set configuration, and time resources
2//! for the physics solver and substepping loop.
3//!
4//! See [`SolverSchedulePlugin`].
5
6use crate::prelude::*;
7use bevy::{
8    ecs::schedule::{ExecutorKind, LogLevel, ScheduleBuildSettings, ScheduleLabel},
9    prelude::*,
10};
11use dynamics::integrator::IntegrationSet;
12
13/// Sets up the default scheduling, system set configuration, and time resources for the physics solver.
14#[derive(Debug, Default)]
15pub struct SolverSchedulePlugin;
16
17impl Plugin for SolverSchedulePlugin {
18    fn build(&self, app: &mut App) {
19        // Register types.
20        app.register_type::<(Time<Substeps>, SubstepCount)>();
21
22        // Initialize resources.
23        app.insert_resource(Time::new_with(Substeps))
24            .init_resource::<SubstepCount>();
25
26        // Get the `PhysicsSchedule`, and panic if it doesn't exist.
27        let physics = app
28            .get_schedule_mut(PhysicsSchedule)
29            .expect("add PhysicsSchedule first");
30
31        // See `SolverSet` for what each system set is responsible for.
32        physics.configure_sets(
33            (
34                SolverSet::PreSubstep,
35                SolverSet::Substep,
36                SolverSet::PostSubstep,
37                SolverSet::Restitution,
38                SolverSet::ApplyTranslation,
39                SolverSet::StoreContactImpulses,
40            )
41                .chain()
42                .in_set(PhysicsStepSet::Solver),
43        );
44
45        // Run the substepping loop.
46        physics.add_systems(run_substep_schedule.in_set(SolverSet::Substep));
47
48        // Set up the substep schedule, the schedule that runs systems in the inner substepping loop.
49        app.edit_schedule(SubstepSchedule, |schedule| {
50            schedule
51                .set_executor_kind(ExecutorKind::SingleThreaded)
52                .set_build_settings(ScheduleBuildSettings {
53                    ambiguity_detection: LogLevel::Error,
54                    ..default()
55                })
56                .configure_sets(
57                    (
58                        IntegrationSet::Velocity,
59                        SubstepSolverSet::WarmStart,
60                        SubstepSolverSet::SolveConstraints,
61                        IntegrationSet::Position,
62                        SubstepSolverSet::Relax,
63                        SubstepSolverSet::SolveXpbdConstraints,
64                        SubstepSolverSet::SolveUserConstraints,
65                        SubstepSolverSet::XpbdVelocityProjection,
66                    )
67                        .chain(),
68                );
69        });
70    }
71}
72
73/// The substepping schedule that runs in [`SolverSet::Substep`].
74/// The number of substeps per physics step is configured through the [`SubstepCount`] resource.
75#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
76pub struct SubstepSchedule;
77
78/// System sets for the constraint solver.
79///
80/// # Steps
81///
82/// Below is the core solver loop.
83///
84/// 1. Generate and prepare constraints (contact constraints are generated by the [narrow phase](crate::prelude::NarrowPhasePlugin))
85/// 2. Substepping loop (runs the [`SubstepSchedule`] [`SubstepCount`] times; see [`SolverSet::Substep`])
86/// 3. Apply restitution ([`SolverSet::Restitution`])
87/// 4. Finalize positions by applying [`AccumulatedTranslation`] ([`SolverSet::ApplyTranslation`])
88/// 5. Store contact impulses for next frame's warm starting ([`SolverSet::StoreContactImpulses`])
89#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
90pub enum SolverSet {
91    /// A system set for systems running just before the substepping loop.
92    PreSubstep,
93    /// A system set for the substepping loop.
94    Substep,
95    /// A system set for systems running just after the substepping loop.
96    PostSubstep,
97    /// Applies [restitution](Restitution) for bodies after solving overlap.
98    Restitution,
99    /// Finalizes the positions of bodies by applying the [`AccumulatedTranslation`].
100    ///
101    /// Constraints don't modify the positions of bodies directly and instead adds
102    /// to this translation to improve numerical stability when bodies are far from the world origin.
103    ApplyTranslation,
104    /// Copies contact impulses from [`ContactConstraints`] to the contacts in the [`ContactGraph`].
105    /// They will be used for [warm starting](SubstepSolverSet::WarmStart) the next frame or substep.
106    ///
107    /// [`ContactConstraints`]: super::ContactConstraints
108    StoreContactImpulses,
109}
110
111/// System sets for the substepped part of the constraint solver.
112///
113/// # Steps
114///
115/// 1. Integrate velocity ([`IntegrationSet::Velocity`])
116/// 2. Warm start ([`SubstepSolverSet::WarmStart`])
117/// 3. Solve constraints with bias ([`SubstepSolverSet::SolveConstraints`])
118/// 4. Integrate positions ([`IntegrationSet::Position`])
119/// 5. Solve constraints without bias to relax velocities ([`SubstepSolverSet::Relax`])
120/// 6. Solve joints using Extended Position-Based Dynamics (XPBD). ([`SubstepSolverSet::SolveXpbdConstraints`])
121/// 7. Solve user-defined constraints. ([`SubstepSolverSet::SolveUserConstraints`])
122/// 8. Update velocities after XPBD constraint solving. ([`SubstepSolverSet::XpbdVelocityProjection`])
123#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
124pub enum SubstepSolverSet {
125    /// Warm starts the solver by applying the impulses from the previous frame or substep.
126    ///
127    /// This significantly improves convergence, but by itself can lead to overshooting.
128    /// Overshooting is reduced by [relaxing](SubstepSolverSet::Relax) the biased velocities
129    /// by running the solver a second time *without* bias.
130    WarmStart,
131    /// Solves velocity constraints using a position bias that boosts the response
132    /// to account for the constraint error.
133    SolveConstraints,
134    /// Solves velocity constraints without a position bias to relax the biased velocities
135    /// and impulses. This reduces overshooting caused by [warm starting](SubstepSolverSet::WarmStart).
136    Relax,
137    /// Solves joints using Extended Position-Based Dynamics (XPBD).
138    SolveXpbdConstraints,
139    /// A system set for user constraints.
140    SolveUserConstraints,
141    /// Performs velocity updates after XPBD constraint solving.
142    XpbdVelocityProjection,
143}
144
145/// The number of substeps used in the simulation.
146///
147/// A higher number of substeps reduces the value of [`Time`],
148/// which results in a more accurate simulation, but also reduces performance. The default
149/// substep count is currently 6.
150///
151/// If you use a very high substep count and encounter stability issues, consider enabling the `f64`
152/// feature as shown in the [getting started guide](crate#getting-started) to avoid floating point
153/// precision problems.
154///
155/// # Example
156///
157/// You can change the number of substeps by inserting the [`SubstepCount`] resource:
158///
159/// ```no_run
160#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
161#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
162/// use bevy::prelude::*;
163///
164/// fn main() {
165///     App::new()
166///         .add_plugins((DefaultPlugins, PhysicsPlugins::default()))
167///         .insert_resource(SubstepCount(12))
168///         .run();
169/// }
170/// ```
171#[derive(Debug, Reflect, Resource, Clone, Copy, PartialEq, Eq)]
172#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
173#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
174#[reflect(Debug, Resource, PartialEq)]
175pub struct SubstepCount(pub u32);
176
177impl Default for SubstepCount {
178    fn default() -> Self {
179        Self(6)
180    }
181}
182
183/// Runs the [`SubstepSchedule`].
184fn run_substep_schedule(world: &mut World) {
185    let delta = world.resource::<Time<Physics>>().delta();
186    let SubstepCount(substeps) = *world.resource::<SubstepCount>();
187    let sub_delta = delta.div_f64(substeps as f64);
188
189    let mut sub_delta_time = world.resource_mut::<Time<Substeps>>();
190    sub_delta_time.advance_by(sub_delta);
191
192    let _ = world.try_schedule_scope(SubstepSchedule, |world, schedule| {
193        for i in 0..substeps {
194            trace!("running SubstepSchedule: {i}");
195            *world.resource_mut::<Time>() = world.resource::<Time<Substeps>>().as_generic();
196            schedule.run(world);
197        }
198    });
199
200    // Set generic `Time` resource back to `Time<Physics>`.
201    // Later, it's set back to the default clock after the `PhysicsSchedule`.
202    *world.resource_mut::<Time>() = world.resource::<Time<Physics>>().as_generic();
203}