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}