avian2d/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::IntegrationSystems;
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 with generics.
20        app.register_type::<Time<Substeps>>();
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 `SolverSystems` for what each system set is responsible for.
32        physics.configure_sets(
33            (
34                SolverSystems::PrepareSolverBodies,
35                SolverSystems::PrepareJoints,
36                SolverSystems::PrepareContactConstraints,
37                SolverSystems::PreSubstep,
38                SolverSystems::Substep,
39                SolverSystems::PostSubstep,
40                SolverSystems::Restitution,
41                SolverSystems::Finalize,
42                SolverSystems::StoreContactImpulses,
43            )
44                .chain()
45                .in_set(PhysicsStepSystems::Solver),
46        );
47
48        // Run the substepping loop.
49        physics.add_systems(run_substep_schedule.in_set(SolverSystems::Substep));
50
51        // Set up the substep schedule, the schedule that runs systems in the inner substepping loop.
52        app.edit_schedule(SubstepSchedule, |schedule| {
53            schedule
54                .set_executor_kind(ExecutorKind::SingleThreaded)
55                .set_build_settings(ScheduleBuildSettings {
56                    ambiguity_detection: LogLevel::Error,
57                    ..default()
58                })
59                .configure_sets(
60                    (
61                        IntegrationSystems::Velocity,
62                        SubstepSolverSystems::WarmStart,
63                        SubstepSolverSystems::SolveConstraints,
64                        IntegrationSystems::Position,
65                        SubstepSolverSystems::Relax,
66                        SubstepSolverSystems::Damping,
67                    )
68                        .chain(),
69                );
70        });
71    }
72}
73
74/// The substepping schedule that runs in [`SolverSystems::Substep`].
75/// The number of substeps per physics step is configured through the [`SubstepCount`] resource.
76#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
77pub struct SubstepSchedule;
78
79/// System sets for the constraint solver.
80///
81/// # Steps
82///
83/// Below is the core solver loop.
84///
85/// 1. Prepare solver bodies ([`SolverSystems::PrepareSolverBodies`])
86/// 2. Prepare joints ([`SolverSystems::PrepareJoints`])
87/// 3. Prepare contact constraints ([`SolverSystems::PrepareContactConstraints`])
88/// 4. Substepping loop (runs the [`SubstepSchedule`] [`SubstepCount`] times; see [`SolverSystems::Substep`])
89/// 5. Apply restitution ([`SolverSystems::Restitution`])
90/// 6. Write back solver body data to rigid bodies. ([`SolverSystems::Finalize`])
91/// 7. Store contact impulses for next frame's warm starting ([`SolverSystems::StoreContactImpulses`])
92#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
93pub enum SolverSystems {
94    /// Prepares [solver bodies] for the substepping loop.
95    ///
96    /// [solver bodies]: crate::dynamics::solver::solver_body::SolverBody
97    PrepareSolverBodies,
98    /// Prepares joint constraints for the substepping loop.
99    PrepareJoints,
100    /// Prepares contact constraints for the substepping loop.
101    PrepareContactConstraints,
102    /// A system set for systems running just before the substepping loop.
103    PreSubstep,
104    /// A system set for the substepping loop.
105    Substep,
106    /// A system set for systems running just after the substepping loop.
107    PostSubstep,
108    /// Applies [restitution](Restitution) for bodies after solving overlap.
109    Restitution,
110    /// Writes back solver body data to rigid bodies.
111    Finalize,
112    /// Copies contact impulses from [`ContactConstraints`] to the contacts in the [`ContactGraph`].
113    /// They will be used for [warm starting](SubstepSolverSystems::WarmStart) the next frame or substep.
114    ///
115    /// [`ContactConstraints`]: super::ContactConstraints
116    StoreContactImpulses,
117}
118
119/// A deprecated alias for [`SolverSystems`].
120#[deprecated(since = "0.4.0", note = "Renamed to `SolverSystems`")]
121pub type SolverSet = SolverSystems;
122
123/// System sets for the substepped part of the constraint solver.
124///
125/// # Steps
126///
127/// 1. Integrate velocity ([`IntegrationSystems::Velocity`])
128/// 2. Warm start ([`SubstepSolverSystems::WarmStart`])
129/// 3. Solve constraints with bias ([`SubstepSolverSystems::SolveConstraints`])
130/// 4. Integrate positions ([`IntegrationSystems::Position`])
131/// 5. Solve constraints without bias to relax velocities ([`SubstepSolverSystems::Relax`])
132/// 6. Apply velocity-based constraint damping ([`SubstepSolverSystems::Damping`])
133#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
134pub enum SubstepSolverSystems {
135    /// Warm starts the solver by applying the impulses from the previous frame or substep.
136    ///
137    /// This significantly improves convergence, but by itself can lead to overshooting.
138    /// Overshooting is reduced by [relaxing](SubstepSolverSystems::Relax) the biased velocities
139    /// by running the solver a second time *without* bias.
140    WarmStart,
141    /// Solves velocity constraints using a position bias that boosts the response
142    /// to account for the constraint error.
143    SolveConstraints,
144    /// Solves velocity constraints without a position bias to relax the biased velocities
145    /// and impulses. This reduces overshooting caused by [warm starting](SubstepSolverSystems::WarmStart).
146    Relax,
147    /// Applies velocity-based constraint damping, such as [`JointDamping`].
148    Damping,
149}
150
151/// A deprecated alias for [`SubstepSolverSystems`].
152#[deprecated(since = "0.4.0", note = "Renamed to `SubstepSolverSystems`")]
153pub type SubstepSolverSet = SubstepSolverSystems;
154
155/// The number of substeps used in the simulation.
156///
157/// A higher number of substeps reduces the value of [`Time`],
158/// which results in a more accurate simulation, but also reduces performance. The default
159/// substep count is currently 6.
160///
161/// If you use a very high substep count and encounter stability issues, consider enabling the `f64`
162/// feature as shown in the [getting started guide](crate#getting-started) to avoid floating point
163/// precision problems.
164///
165/// # Example
166///
167/// You can change the number of substeps by inserting the [`SubstepCount`] resource:
168///
169/// ```no_run
170#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
171#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
172/// use bevy::prelude::*;
173///
174/// fn main() {
175///     App::new()
176///         .add_plugins((DefaultPlugins, PhysicsPlugins::default()))
177///         .insert_resource(SubstepCount(12))
178///         .run();
179/// }
180/// ```
181#[derive(Debug, Reflect, Resource, Clone, Copy, PartialEq, Eq)]
182#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
183#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
184#[reflect(Debug, Resource, PartialEq)]
185pub struct SubstepCount(pub u32);
186
187impl Default for SubstepCount {
188    fn default() -> Self {
189        Self(6)
190    }
191}
192
193/// Runs the [`SubstepSchedule`].
194fn run_substep_schedule(world: &mut World) {
195    let delta = world.resource::<Time<Physics>>().delta();
196    let SubstepCount(substeps) = *world.resource::<SubstepCount>();
197    let sub_delta = delta.div_f64(substeps as f64);
198
199    let mut sub_delta_time = world.resource_mut::<Time<Substeps>>();
200    sub_delta_time.advance_by(sub_delta);
201
202    let _ = world.try_schedule_scope(SubstepSchedule, |world, schedule| {
203        for i in 0..substeps {
204            trace!("running SubstepSchedule: {i}");
205            *world.resource_mut::<Time>() = world.resource::<Time<Substeps>>().as_generic();
206            schedule.run(world);
207        }
208    });
209
210    // Set generic `Time` resource back to `Time<Physics>`.
211    // Later, it's set back to the default clock after the `PhysicsSchedule`.
212    *world.resource_mut::<Time>() = world.resource::<Time<Physics>>().as_generic();
213}