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}