avian3d/dynamics/solver/solver_body/mod.rs
1//! Efficient rigid body definitions used by the performance-critical solver.
2//!
3//! This helps improve memory locality and makes random access faster for the constraint solver.
4//!
5//! This includes the following types:
6//!
7//! - [`SolverBody`]: The body state used by the solver.
8//! - [`SolverBodyInertia`]: The inertial properties of a body used by the solver.
9
10mod plugin;
11
12pub use plugin::SolverBodyPlugin;
13
14use bevy::prelude::*;
15
16use super::{Rotation, Vector};
17use crate::{SymmetricTensor, math::Scalar, prelude::LockedAxes};
18#[cfg(feature = "3d")]
19use crate::{math::Quaternion, prelude::ComputedAngularInertia};
20
21// The `SolverBody` layout is inspired by `b2BodyState` in Box2D v3.
22
23/// Optimized rigid body state that the solver operates on,
24/// designed to improve memory locality and performance.
25///
26/// Only awake dynamic bodies and kinematic bodies have an associated solver body,
27/// stored as a component on the body entity. Static bodies and sleeping dynamic bodies
28/// do not move, so they instead use a "dummy state" with [`SolverBody::default()`].
29///
30/// # Representation
31///
32/// The solver doesn't have access to the position or rotation of static or sleeping bodies,
33/// which is a problem when computing constraint anchors. To work around this, we have two options:
34///
35/// - **Option 1**: Use delta positions and rotations. This requires preparing
36/// base anchors and other necessary positional data in world space,
37/// and computing the updated anchors during substeps.
38/// - **Option 2**: Use full positions and rotations. This requires storing
39/// anchors in world space for static bodies and sleeping bodies,
40/// and in local space for dynamic bodies.
41///
42/// Avian uses **Option 1**, because:
43///
44/// - Using delta positions reduces round-off error when bodies are far from the origin.
45/// - Mixing world space and local space values depending on the body type would be
46/// quite confusing and error-prone, and would possibly require more branching.
47///
48/// In addition to the delta position and rotation, we also store the linear and angular velocities
49/// and some bitflags. This all fits in 32 bytes in 2D or 56 bytes in 3D with the `f32` feature.
50///
51/// The 2D data layout has been designed to support fast conversion to and from
52/// wide SIMD types via scatter/gather operations in the future when SIMD optimizations
53/// are implemented.
54// TODO: Is there a better layout for 3D?
55#[derive(Component, Clone, Debug, Default, Reflect)]
56#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
57#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
58#[reflect(Component, Debug)]
59pub struct SolverBody {
60 /// The linear velocity of the body.
61 ///
62 /// 8 bytes in 2D and 12 bytes in 3D with the `f32` feature.
63 pub linear_velocity: Vector,
64 /// The angular velocity of the body.
65 ///
66 /// 4 bytes in 2D and 12 bytes in 3D with the `f32` feature.
67 #[cfg(feature = "2d")]
68 pub angular_velocity: Scalar,
69 /// The angular velocity of the body.
70 ///
71 /// 8 bytes in 2D and 12 bytes in 3D with the `f32` feature.
72 #[cfg(feature = "3d")]
73 pub angular_velocity: Vector,
74 /// The change in position of the body.
75 ///
76 /// Stored as a delta to avoid round-off error when far from the origin.
77 ///
78 /// 8 bytes in 2D and 12 bytes in 3D with the `f32` feature.
79 pub delta_position: Vector,
80 /// The change in rotation of the body.
81 ///
82 /// Stored as a delta because the rotation of static bodies cannot be accessed
83 /// in the solver, but they have a known delta rotation of zero.
84 ///
85 /// 8 bytes in 2D and 16 bytes in 3D with the `f32` feature.
86 pub delta_rotation: Rotation,
87 /// Flags for the body.
88 ///
89 /// 4 bytes.
90 pub flags: SolverBodyFlags,
91}
92
93impl SolverBody {
94 /// A dummy [`SolverBody`] for static bodies.
95 pub const DUMMY: Self = Self {
96 linear_velocity: Vector::ZERO,
97 #[cfg(feature = "2d")]
98 angular_velocity: 0.0,
99 #[cfg(feature = "3d")]
100 angular_velocity: Vector::ZERO,
101 delta_position: Vector::ZERO,
102 delta_rotation: Rotation::IDENTITY,
103 flags: SolverBodyFlags::empty(),
104 };
105
106 /// Computes the velocity at the given `point` relative to the center of the body.
107 pub fn velocity_at_point(&self, point: Vector) -> Vector {
108 #[cfg(feature = "2d")]
109 {
110 self.linear_velocity + self.angular_velocity * point.perp()
111 }
112 #[cfg(feature = "3d")]
113 {
114 self.linear_velocity + self.angular_velocity.cross(point)
115 }
116 }
117
118 /// Returns `true` if gyroscopic motion is enabled for this body.
119 pub fn is_gyroscopic(&self) -> bool {
120 self.flags.contains(SolverBodyFlags::GYROSCOPIC_MOTION)
121 }
122}
123
124/// Flags for [`SolverBody`].
125#[repr(transparent)]
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
127#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
128#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
129#[reflect(Debug, PartialEq)]
130pub struct SolverBodyFlags(u32);
131
132bitflags::bitflags! {
133 impl SolverBodyFlags: u32 {
134 /// Set if translation along the `X` axis is locked.
135 const TRANSLATION_X_LOCKED = 0b100_000;
136 /// Set if translation along the `Y` axis is locked.
137 const TRANSLATION_Y_LOCKED = 0b010_000;
138 /// Set if translation along the `Z` axis is locked.
139 const TRANSLATION_Z_LOCKED = 0b001_000;
140 /// Set if rotation around the `X` axis is locked.
141 const ROTATION_X_LOCKED = 0b000_100;
142 /// Set if rotation around the `Y` axis is locked.
143 const ROTATION_Y_LOCKED = 0b000_010;
144 /// Set if rotation around the `Z` axis is locked.
145 const ROTATION_Z_LOCKED = 0b000_001;
146 /// Set if all translational axes are locked.
147 const TRANSLATION_LOCKED = Self::TRANSLATION_X_LOCKED.bits() | Self::TRANSLATION_Y_LOCKED.bits() | Self::TRANSLATION_Z_LOCKED.bits();
148 /// Set if all rotational axes are locked.
149 const ROTATION_LOCKED = Self::ROTATION_X_LOCKED.bits() | Self::ROTATION_Y_LOCKED.bits() | Self::ROTATION_Z_LOCKED.bits();
150 /// Set if all translational and rotational axes are locked.
151 const ALL_LOCKED = Self::TRANSLATION_LOCKED.bits() | Self::ROTATION_LOCKED.bits();
152 /// Set if the body is kinematic. Otherwise, it is dynamic.
153 const IS_KINEMATIC = 1 << 6;
154 /// Set if gyroscopic motion is enabled.
155 const GYROSCOPIC_MOTION = 1 << 7;
156 }
157}
158
159impl SolverBodyFlags {
160 /// Returns the [`LockedAxes`] of the body.
161 pub fn locked_axes(&self) -> LockedAxes {
162 LockedAxes::from_bits(self.0 as u8)
163 }
164
165 /// Returns `true` if the body is dynamic.
166 pub fn is_dynamic(&self) -> bool {
167 !self.contains(SolverBodyFlags::IS_KINEMATIC)
168 }
169
170 /// Returns `true` if the body is kinematic.
171 pub fn is_kinematic(&self) -> bool {
172 self.contains(SolverBodyFlags::IS_KINEMATIC)
173 }
174}
175
176/*
177Box2D v3 stores mass and angular inertia in constraint data.
178For 2D, this is just 2 floats or 8 bytes for each body in each constraint.
179
180However, we also support 3D and locking translational axes, so our worst case
181would be *9* floats for each body, 3 for the effective mass vector
182and 6 for the symmetric 3x3 inertia tensor. Storing 36 bytes
183for each body in each constraint would be quite wasteful.
184
185Instead, we store a separate `SolverBodyInertia` struct for each `SolverBody`.
186The struct is optimized for memory locality and size.
187
188In 2D, we store the effective inertial properties directly:
189
190- Effective inverse mass (8 bytes)
191- Effective inverse angular inertia (4 bytes)
192- Flags (4 bytes)
193
194for a total of 16 bytes.
195
196In 3D, we instead compute the effective versions on the fly:
197
198- Inverse mass (4 bytes)
199- Inverse angular inertia (36 bytes, matrix with 9 floats)
200- Flags (4 bytes)
201
202for a total of 44 bytes. This will be 32 bytes in the future
203if/when we switch to a symmetric 3x3 matrix representation.
204
205The API abstracts over this difference in representation to reduce complexity.
206*/
207
208/// The inertial properties of a [`SolverBody`].
209///
210/// This includes the effective inverse mass and angular inertia,
211/// and flags indicating whether the body is static or has locked axes.
212///
213/// 16 bytes in 2D and 32 bytes in 3D with the `f32` feature.
214#[derive(Component, Clone, Debug, Reflect)]
215#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
216#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
217#[reflect(Component, Debug)]
218pub struct SolverBodyInertia {
219 /// The effective inverse mass of the body,
220 /// taking into account any locked axes.
221 ///
222 /// 8 bytes with the `f32` feature.
223 #[cfg(feature = "2d")]
224 effective_inv_mass: Vector,
225
226 /// The inverse mass of the body.
227 ///
228 /// 4 bytes with the `f32` feature.
229 #[cfg(feature = "3d")]
230 inv_mass: Scalar,
231
232 /// The effective inverse angular inertia of the body,
233 /// taking into account any locked axes.
234 ///
235 /// 4 bytes with the `f32` feature.
236 #[cfg(feature = "2d")]
237 effective_inv_angular_inertia: SymmetricTensor,
238
239 /// The world-space inverse angular inertia of the body.
240 ///
241 /// 32 bytes with the `f32` feature.
242 #[cfg(feature = "3d")]
243 effective_inv_angular_inertia: SymmetricTensor,
244
245 /// The [dominance] of the body.
246 ///
247 /// If the [`Dominance`] component is not specified, the default of `0` is returned for dynamic bodies.
248 /// For static and kinematic bodies, `i8::MAX + 1` (`128`) is always returned instead.
249 ///
250 /// 2 bytes.
251 ///
252 /// [dominance]: crate::dynamics::rigid_body::Dominance
253 /// [`Dominance`]: crate::dynamics::rigid_body::Dominance
254 dominance: i16,
255
256 /// Flags indicating the inertial properties of the body,
257 /// like locked axes and whether the body is static.
258 ///
259 /// 2 bytes.
260 flags: InertiaFlags,
261}
262
263impl SolverBodyInertia {
264 /// A dummy [`SolverBodyInertia`] for static bodies.
265 pub const DUMMY: Self = Self {
266 #[cfg(feature = "2d")]
267 effective_inv_mass: Vector::ZERO,
268 #[cfg(feature = "3d")]
269 inv_mass: 0.0,
270 #[cfg(feature = "2d")]
271 effective_inv_angular_inertia: 0.0,
272 #[cfg(feature = "3d")]
273 effective_inv_angular_inertia: SymmetricTensor::ZERO,
274 dominance: i8::MAX as i16 + 1,
275 flags: InertiaFlags::STATIC,
276 };
277}
278
279impl Default for SolverBodyInertia {
280 fn default() -> Self {
281 Self::DUMMY
282 }
283}
284
285/// Flags indicating the inertial properties of a body.
286#[repr(transparent)]
287#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
288#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
289#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
290#[reflect(Debug, PartialEq)]
291pub struct InertiaFlags(u16);
292
293bitflags::bitflags! {
294 impl InertiaFlags: u16 {
295 /// Set if translation along the `X` axis is locked.
296 const TRANSLATION_X_LOCKED = 0b100_000;
297 /// Set if translation along the `Y` axis is locked.
298 const TRANSLATION_Y_LOCKED = 0b010_000;
299 /// Set if translation along the `Z` axis is locked.
300 const TRANSLATION_Z_LOCKED = 0b001_000;
301 /// Set if rotation around the `X` axis is locked.
302 const ROTATION_X_LOCKED = 0b000_100;
303 /// Set if rotation around the `Y` axis is locked.
304 const ROTATION_Y_LOCKED = 0b000_010;
305 /// Set if rotation around the `Z` axis is locked.
306 const ROTATION_Z_LOCKED = 0b000_001;
307 /// Set if all translational axes are locked.
308 const TRANSLATION_LOCKED = Self::TRANSLATION_X_LOCKED.bits() | Self::TRANSLATION_Y_LOCKED.bits() | Self::TRANSLATION_Z_LOCKED.bits();
309 /// Set if all rotational axes are locked.
310 const ROTATION_LOCKED = Self::ROTATION_X_LOCKED.bits() | Self::ROTATION_Y_LOCKED.bits() | Self::ROTATION_Z_LOCKED.bits();
311 /// Set if all translational and rotational axes are locked.
312 const ALL_LOCKED = Self::TRANSLATION_LOCKED.bits() | Self::ROTATION_LOCKED.bits();
313 /// Set if the body has infinite mass.
314 const INFINITE_MASS = 1 << 6;
315 /// Set if the body has infinite inertia.
316 const INFINITE_ANGULAR_INERTIA = 1 << 7;
317 /// Set if the body is static.
318 const STATIC = Self::INFINITE_MASS.bits() | Self::INFINITE_ANGULAR_INERTIA.bits();
319 }
320}
321
322impl InertiaFlags {
323 /// Returns the [`LockedAxes`] of the body.
324 pub fn locked_axes(&self) -> LockedAxes {
325 LockedAxes::from_bits(self.0 as u8)
326 }
327}
328
329impl SolverBodyInertia {
330 /// Creates a new [`SolverBodyInertia`] with the given mass, angular inertia,
331 /// and locked axes.
332 #[inline]
333 #[cfg(feature = "2d")]
334 pub fn new(
335 inv_mass: Scalar,
336 inv_inertia: SymmetricTensor,
337 locked_axes: LockedAxes,
338 dominance: i8,
339 is_dynamic: bool,
340 ) -> Self {
341 let mut effective_inv_mass = Vector::splat(inv_mass);
342 let mut effective_inv_angular_inertia = inv_inertia;
343 let mut flags = InertiaFlags(locked_axes.to_bits() as u16);
344
345 if inv_mass == 0.0 {
346 flags |= InertiaFlags::INFINITE_MASS;
347 }
348 if inv_inertia == 0.0 {
349 flags |= InertiaFlags::INFINITE_ANGULAR_INERTIA;
350 }
351
352 if locked_axes.is_translation_x_locked() {
353 effective_inv_mass.x = 0.0;
354 }
355 if locked_axes.is_translation_y_locked() {
356 effective_inv_mass.y = 0.0;
357 }
358 if locked_axes.is_rotation_locked() {
359 effective_inv_angular_inertia = 0.0;
360 }
361
362 Self {
363 effective_inv_mass,
364 effective_inv_angular_inertia,
365 dominance: if is_dynamic {
366 dominance as i16
367 } else {
368 i8::MAX as i16 + 1
369 },
370 flags: InertiaFlags(flags.0),
371 }
372 }
373
374 /// Creates a new [`SolverBodyInertia`] with the given mass, angular inertia,
375 /// and locked axes.
376 #[inline]
377 #[cfg(feature = "3d")]
378 pub fn new(
379 inv_mass: Scalar,
380 inv_inertia: SymmetricTensor,
381 locked_axes: LockedAxes,
382 dominance: i8,
383 is_dynamic: bool,
384 ) -> Self {
385 let mut effective_inv_angular_inertia = inv_inertia;
386 let mut flags = InertiaFlags(locked_axes.to_bits() as u16);
387
388 if inv_mass == 0.0 {
389 flags |= InertiaFlags::INFINITE_MASS;
390 }
391 if inv_inertia == SymmetricTensor::ZERO {
392 flags |= InertiaFlags::INFINITE_ANGULAR_INERTIA;
393 }
394
395 if locked_axes.is_rotation_x_locked() {
396 effective_inv_angular_inertia.m00 = 0.0;
397 effective_inv_angular_inertia.m01 = 0.0;
398 effective_inv_angular_inertia.m02 = 0.0;
399 }
400
401 if locked_axes.is_rotation_y_locked() {
402 effective_inv_angular_inertia.m01 = 0.0;
403 effective_inv_angular_inertia.m11 = 0.0;
404 effective_inv_angular_inertia.m12 = 0.0;
405 }
406
407 if locked_axes.is_rotation_z_locked() {
408 effective_inv_angular_inertia.m02 = 0.0;
409 effective_inv_angular_inertia.m12 = 0.0;
410 effective_inv_angular_inertia.m22 = 0.0;
411 }
412
413 Self {
414 inv_mass,
415 effective_inv_angular_inertia,
416 dominance: if is_dynamic {
417 dominance as i16
418 } else {
419 i8::MAX as i16 + 1
420 },
421 flags: InertiaFlags(flags.0),
422 }
423 }
424
425 /// Returns the effective inverse mass of the body,
426 /// taking into account any locked axes.
427 #[inline]
428 #[cfg(feature = "2d")]
429 pub fn effective_inv_mass(&self) -> Vector {
430 self.effective_inv_mass
431 }
432
433 /// Returns the effective inverse mass of the body,
434 /// taking into account any locked axes.
435 #[inline]
436 #[cfg(feature = "3d")]
437 pub fn effective_inv_mass(&self) -> Vector {
438 let mut inv_mass = Vector::splat(self.inv_mass);
439
440 if self.flags.contains(InertiaFlags::TRANSLATION_X_LOCKED) {
441 inv_mass.x = 0.0;
442 }
443 if self.flags.contains(InertiaFlags::TRANSLATION_Y_LOCKED) {
444 inv_mass.y = 0.0;
445 }
446 if self.flags.contains(InertiaFlags::TRANSLATION_Z_LOCKED) {
447 inv_mass.z = 0.0;
448 }
449
450 inv_mass
451 }
452
453 /// Returns the effective inverse angular inertia of the body,
454 /// taking into account any locked axes.
455 #[inline]
456 #[cfg(feature = "2d")]
457 pub fn effective_inv_angular_inertia(&self) -> SymmetricTensor {
458 self.effective_inv_angular_inertia
459 }
460
461 /// Returns the effective inverse angular inertia of the body in world space,
462 /// taking into account any locked axes.
463 #[inline]
464 #[cfg(feature = "3d")]
465 pub fn effective_inv_angular_inertia(&self) -> SymmetricTensor {
466 self.effective_inv_angular_inertia
467 }
468
469 /// Updates the effective inverse angular inertia of the body in world space,
470 /// taking into account any locked axes.
471 #[inline]
472 #[cfg(feature = "3d")]
473 pub fn update_effective_inv_angular_inertia(
474 &mut self,
475 computed_angular_inertia: &ComputedAngularInertia,
476 rotation: Quaternion,
477 ) {
478 let locked_axes = self.flags.locked_axes();
479 let mut effective_inv_angular_inertia =
480 computed_angular_inertia.rotated(rotation).inverse();
481
482 if locked_axes.is_rotation_x_locked() {
483 effective_inv_angular_inertia.m00 = 0.0;
484 effective_inv_angular_inertia.m01 = 0.0;
485 effective_inv_angular_inertia.m02 = 0.0;
486 }
487
488 if locked_axes.is_rotation_y_locked() {
489 effective_inv_angular_inertia.m11 = 0.0;
490 effective_inv_angular_inertia.m01 = 0.0;
491 effective_inv_angular_inertia.m12 = 0.0;
492 }
493
494 if locked_axes.is_rotation_z_locked() {
495 effective_inv_angular_inertia.m22 = 0.0;
496 effective_inv_angular_inertia.m02 = 0.0;
497 effective_inv_angular_inertia.m12 = 0.0;
498 }
499
500 self.effective_inv_angular_inertia = effective_inv_angular_inertia;
501 }
502
503 /// Returns the [dominance] of the body.
504 ///
505 /// If the [`Dominance`] component is not specified, the default of `0` is returned for dynamic bodies.
506 /// For static and kinematic bodies, `i8::MAX + 1` (`128`) is always returned instead.
507 ///
508 /// [dominance]: crate::dynamics::rigid_body::Dominance
509 /// [`Dominance`]: crate::dynamics::rigid_body::Dominance
510 #[inline]
511 pub fn dominance(&self) -> i16 {
512 self.dominance
513 }
514
515 /// Returns the [`InertiaFlags`] of the body.
516 #[inline]
517 pub fn flags(&self) -> InertiaFlags {
518 self.flags
519 }
520}