avian3d/dynamics/integrator/
mod.rs1#[doc(alias = "symplectic_euler")]
7pub mod semi_implicit_euler;
8
9use crate::prelude::*;
10use bevy::{
11 ecs::{intern::Interned, query::QueryData, schedule::ScheduleLabel},
12 prelude::*,
13};
14use dynamics::solver::SolverDiagnostics;
15
16pub struct IntegratorPlugin {
26 schedule: Interned<dyn ScheduleLabel>,
27}
28
29impl IntegratorPlugin {
30 pub fn new(schedule: impl ScheduleLabel) -> Self {
34 Self {
35 schedule: schedule.intern(),
36 }
37 }
38}
39
40impl Default for IntegratorPlugin {
41 fn default() -> Self {
42 Self::new(SubstepSchedule)
43 }
44}
45
46impl Plugin for IntegratorPlugin {
47 fn build(&self, app: &mut App) {
48 app.init_resource::<Gravity>();
49
50 app.configure_sets(
51 self.schedule.intern(),
52 (IntegrationSet::Velocity, IntegrationSet::Position).chain(),
53 );
54
55 app.add_systems(
56 self.schedule.intern(),
57 (
58 integrate_velocities.in_set(IntegrationSet::Velocity),
59 integrate_positions.in_set(IntegrationSet::Position),
60 ),
61 );
62
63 #[cfg(feature = "3d")]
64 app.add_systems(
65 self.schedule.intern(),
66 dynamics::rigid_body::mass_properties::update_global_angular_inertia::<()>
67 .in_set(IntegrationSet::Position)
68 .after(integrate_positions),
69 );
70
71 app.get_schedule_mut(PhysicsSchedule)
72 .expect("add PhysicsSchedule first")
73 .add_systems(
74 (
75 apply_impulses.before(SolverSet::Substep),
76 clear_forces_and_impulses.after(SolverSet::Substep),
77 )
78 .in_set(PhysicsStepSet::Solver),
79 );
80 }
81}
82
83#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
86pub enum IntegrationSet {
87 Velocity,
89 Position,
91}
92
93#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
106#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
107#[cfg_attr(
114 feature = "2d",
115 doc = " .insert_resource(Gravity(Vec2::NEG_Y * 100.0))"
116)]
117#[cfg_attr(
118 feature = "3d",
119 doc = " .insert_resource(Gravity(Vec3::NEG_Y * 19.6))"
120)]
121#[derive(Reflect, Resource, Debug)]
129#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
130#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
131#[reflect(Debug, Resource)]
132pub struct Gravity(pub Vector);
133
134impl Default for Gravity {
135 fn default() -> Self {
136 Self(Vector::Y * -9.81)
137 }
138}
139
140impl Gravity {
141 pub const ZERO: Gravity = Gravity(Vector::ZERO);
143}
144
145#[derive(QueryData)]
146#[query_data(mutable)]
147struct VelocityIntegrationQuery {
148 rb: &'static RigidBody,
149 pos: &'static Position,
150 prev_pos: Option<&'static mut PreSolveAccumulatedTranslation>,
151 #[cfg(feature = "3d")]
152 rot: &'static Rotation,
153 lin_vel: &'static mut LinearVelocity,
154 ang_vel: &'static mut AngularVelocity,
155 force: &'static ExternalForce,
156 torque: &'static ExternalTorque,
157 mass: &'static ComputedMass,
158 angular_inertia: &'static ComputedAngularInertia,
159 #[cfg(feature = "3d")]
160 global_angular_inertia: &'static GlobalAngularInertia,
161 lin_damping: Option<&'static LinearDamping>,
162 ang_damping: Option<&'static AngularDamping>,
163 max_linear_speed: Option<&'static MaxLinearSpeed>,
164 max_angular_speed: Option<&'static MaxAngularSpeed>,
165 gravity_scale: Option<&'static GravityScale>,
166 locked_axes: Option<&'static LockedAxes>,
167}
168
169#[allow(clippy::type_complexity)]
170fn integrate_velocities(
171 mut bodies: Query<VelocityIntegrationQuery, RigidBodyActiveFilter>,
172 gravity: Res<Gravity>,
173 time: Res<Time>,
174 mut diagnostics: ResMut<SolverDiagnostics>,
175) {
176 let start = crate::utils::Instant::now();
177
178 let delta_secs = time.delta_seconds_adjusted();
179
180 bodies.par_iter_mut().for_each(|mut body| {
181 if let Some(mut previous_position) = body.prev_pos {
182 previous_position.0 = body.pos.0;
183 }
184
185 if body.rb.is_static() {
186 if *body.lin_vel != LinearVelocity::ZERO {
187 *body.lin_vel = LinearVelocity::ZERO;
188 }
189 if *body.ang_vel != AngularVelocity::ZERO {
190 *body.ang_vel = AngularVelocity::ZERO;
191 }
192 return;
193 }
194
195 if body.rb.is_dynamic() {
196 let locked_axes = body
197 .locked_axes
198 .map_or(LockedAxes::default(), |locked_axes| *locked_axes);
199
200 if let Some(lin_damping) = body.lin_damping {
202 if body.lin_vel.0 != Vector::ZERO && lin_damping.0 != 0.0 {
203 body.lin_vel.0 *= 1.0 / (1.0 + delta_secs * lin_damping.0);
204 }
205 }
206 if let Some(ang_damping) = body.ang_damping {
207 if body.ang_vel.0 != AngularVelocity::ZERO.0 && ang_damping.0 != 0.0 {
208 body.ang_vel.0 *= 1.0 / (1.0 + delta_secs * ang_damping.0);
209 }
210 }
211
212 let external_force = body.force.force();
213 let external_torque = body.torque.torque() + body.force.torque();
214 let gravity = gravity.0 * body.gravity_scale.map_or(1.0, |scale| scale.0);
215
216 semi_implicit_euler::integrate_velocity(
217 &mut body.lin_vel.0,
218 &mut body.ang_vel.0,
219 external_force,
220 external_torque,
221 *body.mass,
222 body.angular_inertia,
223 #[cfg(feature = "3d")]
224 body.global_angular_inertia,
225 #[cfg(feature = "3d")]
226 *body.rot,
227 locked_axes,
228 gravity,
229 delta_secs,
230 );
231 }
232
233 if let Some(max_linear_speed) = body.max_linear_speed {
235 let linear_speed_squared = body.lin_vel.0.length_squared();
236 if linear_speed_squared > max_linear_speed.0.powi(2) {
237 body.lin_vel.0 *= max_linear_speed.0 / linear_speed_squared.sqrt();
238 }
239 }
240 if let Some(max_angular_speed) = body.max_angular_speed {
241 #[cfg(feature = "2d")]
242 if body.ang_vel.abs() > max_angular_speed.0 {
243 body.ang_vel.0 = max_angular_speed.copysign(body.ang_vel.0);
244 }
245 #[cfg(feature = "3d")]
246 {
247 let angular_speed_squared = body.ang_vel.0.length_squared();
248 if angular_speed_squared > max_angular_speed.0.powi(2) {
249 body.ang_vel.0 *= max_angular_speed.0 / angular_speed_squared.sqrt();
250 }
251 }
252 }
253 });
254
255 diagnostics.integrate_velocities += start.elapsed();
256}
257
258#[allow(clippy::type_complexity)]
259fn integrate_positions(
260 mut bodies: Query<
261 (
262 &RigidBody,
263 &Position,
264 Option<&mut PreSolveAccumulatedTranslation>,
265 &mut AccumulatedTranslation,
266 &mut Rotation,
267 &LinearVelocity,
268 &AngularVelocity,
269 Option<&LockedAxes>,
270 ),
271 RigidBodyActiveFilter,
272 >,
273 time: Res<Time>,
274 mut diagnostics: ResMut<SolverDiagnostics>,
275) {
276 let start = crate::utils::Instant::now();
277
278 let delta_secs = time.delta_seconds_adjusted();
279
280 bodies.par_iter_mut().for_each(
281 |(
282 rb,
283 pos,
284 pre_solve_accumulated_translation,
285 mut accumulated_translation,
286 mut rot,
287 lin_vel,
288 ang_vel,
289 locked_axes,
290 )| {
291 if let Some(mut previous_position) = pre_solve_accumulated_translation {
292 previous_position.0 = pos.0;
293 }
294
295 if rb.is_static() || (lin_vel.0 == Vector::ZERO && *ang_vel == AngularVelocity::ZERO) {
296 return;
297 }
298
299 let locked_axes = locked_axes.map_or(LockedAxes::default(), |locked_axes| *locked_axes);
300
301 semi_implicit_euler::integrate_position(
302 &mut accumulated_translation.0,
303 &mut rot,
304 lin_vel.0,
305 ang_vel.0,
306 locked_axes,
307 delta_secs,
308 );
309 },
310 );
311
312 diagnostics.integrate_positions += start.elapsed();
313}
314
315#[cfg(feature = "2d")]
316type AngularValue = Scalar;
317#[cfg(feature = "3d")]
318type AngularValue = Vector;
319#[cfg(feature = "2d")]
320type TorqueValue = Scalar;
321#[cfg(feature = "3d")]
322type TorqueValue = Vector;
323
324type ImpulseQueryComponents = (
325 &'static RigidBody,
326 &'static mut ExternalImpulse,
327 &'static mut ExternalAngularImpulse,
328 &'static mut LinearVelocity,
329 &'static mut AngularVelocity,
330 &'static Rotation,
331 &'static ComputedMass,
332 &'static GlobalAngularInertia,
333 Option<&'static LockedAxes>,
334);
335
336fn apply_impulses(mut bodies: Query<ImpulseQueryComponents, RigidBodyActiveFilter>) {
337 for (
338 rb,
339 impulse,
340 ang_impulse,
341 mut lin_vel,
342 mut ang_vel,
343 _rotation,
344 mass,
345 global_angular_inertia,
346 locked_axes,
347 ) in &mut bodies
348 {
349 if !rb.is_dynamic() {
350 continue;
351 }
352
353 let locked_axes = locked_axes.map_or(LockedAxes::default(), |locked_axes| *locked_axes);
354
355 let effective_inv_mass = locked_axes.apply_to_vec(Vector::splat(mass.inverse()));
356 let effective_angular_inertia =
357 locked_axes.apply_to_angular_inertia(*global_angular_inertia);
358
359 let delta_lin_vel = impulse.impulse() * effective_inv_mass;
361 let delta_ang_vel = effective_angular_inertia.inverse()
362 * (ang_impulse.impulse() + impulse.angular_impulse());
363
364 if delta_lin_vel != Vector::ZERO {
365 lin_vel.0 += delta_lin_vel;
366 }
367 if delta_ang_vel != AngularVelocity::ZERO.0 {
368 ang_vel.0 += delta_ang_vel;
369 }
370 }
371}
372
373type ForceComponents = (
374 &'static mut ExternalForce,
375 &'static mut ExternalTorque,
376 &'static mut ExternalImpulse,
377 &'static mut ExternalAngularImpulse,
378);
379type ForceComponentsChanged = Or<(
380 Changed<ExternalForce>,
381 Changed<ExternalTorque>,
382 Changed<ExternalImpulse>,
383 Changed<ExternalAngularImpulse>,
384)>;
385
386pub fn clear_forces_and_impulses(mut forces: Query<ForceComponents, ForceComponentsChanged>) {
390 for (mut force, mut torque, mut impulse, mut angular_ímpulse) in &mut forces {
391 if !force.persistent {
392 force.clear();
393 }
394 if !torque.persistent {
395 torque.clear();
396 }
397 if !impulse.persistent {
398 impulse.clear();
399 }
400 if !angular_ímpulse.persistent {
401 angular_ímpulse.clear();
402 }
403 }
404}