1#[cfg(doc)]
2use super::IntegrationParameters;
3use crate::alloc_prelude::*;
4use crate::control::PdErrors;
5#[cfg(doc)]
6use crate::control::PidController;
7use crate::dynamics::MassProperties;
8use crate::geometry::{
9 ColliderChanges, ColliderHandle, ColliderMassProps, ColliderParent, ColliderPosition,
10 ColliderSet, ColliderShape, ModifiedColliders,
11};
12use crate::math::{AngVector, AngularInertia, Pose, Real, Rotation, Vector};
13use crate::utils::{
14 AngularInertiaOps, CrossProduct, DotProduct, PoseOps, ScalarType, SimdRealCopy,
15};
16use num::Zero;
17#[cfg(feature = "dim2")]
18use parry::math::Rot2;
19
20#[deprecated(note = "renamed as RigidBodyType")]
22pub type BodyStatus = RigidBodyType;
23
24#[derive(Copy, Clone, Debug, PartialEq, Eq)]
25#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
26pub enum RigidBodyType {
28 Dynamic = 0,
33
34 Fixed = 1,
38
39 KinematicPositionBased = 2,
47
48 KinematicVelocityBased = 3,
56 }
59
60impl RigidBodyType {
61 pub fn is_fixed(self) -> bool {
63 self == RigidBodyType::Fixed
64 }
65
66 pub fn is_dynamic(self) -> bool {
68 self == RigidBodyType::Dynamic
69 }
70
71 pub fn is_kinematic(self) -> bool {
73 self == RigidBodyType::KinematicPositionBased
74 || self == RigidBodyType::KinematicVelocityBased
75 }
76
77 pub fn is_dynamic_or_kinematic(self) -> bool {
82 self != RigidBodyType::Fixed
83 }
84}
85
86bitflags::bitflags! {
87 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
88 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
89 pub struct RigidBodyChanges: u32 {
91 const IN_MODIFIED_SET = 1 << 0;
93 const POSITION = 1 << 1;
95 const SLEEP = 1 << 2;
97 const COLLIDERS = 1 << 3;
99 const TYPE = 1 << 4;
101 const DOMINANCE = 1 << 5;
103 const LOCAL_MASS_PROPERTIES = 1 << 6;
105 const ENABLED_OR_DISABLED = 1 << 7;
107 }
108}
109
110impl Default for RigidBodyChanges {
111 fn default() -> Self {
112 RigidBodyChanges::empty()
113 }
114}
115
116#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
117#[derive(Clone, Debug, Copy, PartialEq)]
118pub struct RigidBodyPosition {
120 pub position: Pose,
122 pub next_position: Pose,
132}
133
134impl Default for RigidBodyPosition {
135 fn default() -> Self {
136 Self {
137 position: Pose::IDENTITY,
138 next_position: Pose::IDENTITY,
139 }
140 }
141}
142
143impl RigidBodyPosition {
144 #[must_use]
147 pub fn interpolate_velocity(&self, inv_dt: Real, local_com: Vector) -> RigidBodyVelocity<Real> {
148 let pose_err = self.pose_errors(local_com);
149 RigidBodyVelocity {
150 linvel: pose_err.linear * inv_dt,
151 angvel: pose_err.angular * inv_dt,
152 }
153 }
154
155 #[must_use]
159 pub fn integrate_forces_and_velocities(
160 &self,
161 dt: Real,
162 forces: &RigidBodyForces,
163 vels: &RigidBodyVelocity<Real>,
164 mprops: &RigidBodyMassProps,
165 ) -> Pose {
166 let new_vels = forces.integrate(dt, vels, mprops);
167 let local_com = mprops.local_mprops.local_com;
168 new_vels.integrate(dt, &self.position, &local_com)
169 }
170
171 pub fn pose_errors(&self, local_com: Vector) -> PdErrors {
179 let com = self.position * local_com;
180 let shift = Pose::from_translation(com);
181 let dpos = shift.inverse() * self.next_position * self.position.inverse() * shift;
182
183 let angular;
184 #[cfg(feature = "dim2")]
185 {
186 angular = dpos.rotation.angle();
187 }
188 #[cfg(feature = "dim3")]
189 {
190 angular = dpos.rotation.to_scaled_axis();
191 }
192 let linear = dpos.translation;
193
194 PdErrors { linear, angular }
195 }
196}
197
198impl<T> From<T> for RigidBodyPosition
199where
200 Pose: From<T>,
201{
202 fn from(position: T) -> Self {
203 let position = position.into();
204 Self {
205 position,
206 next_position: position,
207 }
208 }
209}
210
211bitflags::bitflags! {
212 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
213 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
214 pub struct AxesMask: u8 {
216 const LIN_X = 1 << 0;
218 const LIN_Y = 1 << 1;
220 #[cfg(feature = "dim3")]
222 const LIN_Z = 1 << 2;
223 #[cfg(feature = "dim3")]
225 const ANG_X = 1 << 3;
226 #[cfg(feature = "dim3")]
228 const ANG_Y = 1 << 4;
229 const ANG_Z = 1 << 5;
231 }
232}
233
234impl Default for AxesMask {
235 fn default() -> Self {
236 AxesMask::empty()
237 }
238}
239
240bitflags::bitflags! {
241 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
242 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
243 pub struct LockedAxes: u8 {
272 const TRANSLATION_LOCKED_X = 1 << 0;
274 const TRANSLATION_LOCKED_Y = 1 << 1;
276 const TRANSLATION_LOCKED_Z = 1 << 2;
278 const TRANSLATION_LOCKED = Self::TRANSLATION_LOCKED_X.bits() | Self::TRANSLATION_LOCKED_Y.bits() | Self::TRANSLATION_LOCKED_Z.bits();
280 const ROTATION_LOCKED_X = 1 << 3;
282 const ROTATION_LOCKED_Y = 1 << 4;
284 const ROTATION_LOCKED_Z = 1 << 5;
286 const ROTATION_LOCKED = Self::ROTATION_LOCKED_X.bits() | Self::ROTATION_LOCKED_Y.bits() | Self::ROTATION_LOCKED_Z.bits();
288 }
289}
290
291#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
293#[derive(Copy, Clone, Debug, PartialEq)]
294pub enum RigidBodyAdditionalMassProps {
295 MassProps(MassProperties),
297 Mass(Real),
300}
301
302impl Default for RigidBodyAdditionalMassProps {
303 fn default() -> Self {
304 RigidBodyAdditionalMassProps::MassProps(MassProperties::default())
305 }
306}
307
308#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
309#[derive(Clone, Debug, PartialEq)]
310pub struct RigidBodyMassProps {
313 pub world_com: Vector,
315 pub effective_inv_mass: Vector,
317 pub effective_world_inv_inertia: AngularInertia,
320 pub local_mprops: MassProperties,
322 pub flags: LockedAxes,
324 pub additional_local_mprops: Option<Box<RigidBodyAdditionalMassProps>>,
326 #[cfg_attr(feature = "serde-serialize", serde(default))]
330 pub(crate) max_extent: Real,
331}
332
333impl Default for RigidBodyMassProps {
334 fn default() -> Self {
335 Self {
336 flags: LockedAxes::empty(),
337 local_mprops: MassProperties::zero(),
338 additional_local_mprops: None,
339 world_com: Vector::ZERO,
340 effective_inv_mass: Vector::ZERO,
341 effective_world_inv_inertia: AngularInertia::zero(),
342 max_extent: 0.0,
343 }
344 }
345}
346
347impl From<LockedAxes> for RigidBodyMassProps {
348 fn from(flags: LockedAxes) -> Self {
349 Self {
350 flags,
351 ..Self::default()
352 }
353 }
354}
355
356impl From<MassProperties> for RigidBodyMassProps {
357 fn from(local_mprops: MassProperties) -> Self {
358 Self {
359 local_mprops,
360 ..Default::default()
361 }
362 }
363}
364
365impl RigidBodyMassProps {
366 #[must_use]
368 pub fn mass(&self) -> Real {
369 crate::utils::inv(self.local_mprops.inv_mass)
370 }
371
372 #[must_use]
375 pub fn effective_mass(&self) -> Vector {
376 self.effective_inv_mass.map(crate::utils::inv)
377 }
378
379 #[must_use]
382 pub fn effective_angular_inertia(&self) -> AngularInertia {
383 #[allow(unused_mut)] let mut ang_inertia = self.effective_world_inv_inertia;
385
386 #[cfg(feature = "dim3")]
388 {
389 if self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
390 ang_inertia.m11 = 1.0;
391 }
392 if self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
393 ang_inertia.m22 = 1.0;
394 }
395 if self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
396 ang_inertia.m33 = 1.0;
397 }
398 }
399
400 #[allow(unused_mut)] let mut result = ang_inertia.inverse();
402
403 #[cfg(feature = "dim3")]
405 {
406 if self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
407 result.m11 = 0.0;
408 }
409 if self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
410 result.m22 = 0.0;
411 }
412 if self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
413 result.m33 = 0.0;
414 }
415 }
416
417 result
418 }
419
420 pub fn recompute_mass_properties_from_colliders(
422 &mut self,
423 colliders: &ColliderSet,
424 attached_colliders: &RigidBodyColliders,
425 body_type: RigidBodyType,
426 position: &Pose,
427 ) {
428 let added_mprops = self
429 .additional_local_mprops
430 .as_ref()
431 .map(|mprops| **mprops)
432 .unwrap_or_else(|| RigidBodyAdditionalMassProps::MassProps(MassProperties::default()));
433
434 self.local_mprops = MassProperties::default();
435
436 for handle in &attached_colliders.0 {
437 if let Some(co) = colliders.get(*handle) {
438 if co.is_enabled() {
439 if let Some(co_parent) = co.parent {
440 let to_add = co
441 .mprops
442 .mass_properties(&*co.shape)
443 .transform_by(&co_parent.pos_wrt_parent);
444 self.local_mprops += to_add;
445 }
446 }
447 }
448 }
449
450 match added_mprops {
451 RigidBodyAdditionalMassProps::MassProps(mprops) => {
452 self.local_mprops += mprops;
453 }
454 RigidBodyAdditionalMassProps::Mass(mass) => {
455 let prev_mass = self.local_mprops.mass();
456 if prev_mass > 0.0 {
457 self.local_mprops.set_mass(prev_mass + mass, true);
458 } else {
459 let mut unit_mprops = MassProperties::default();
463 for handle in &attached_colliders.0 {
464 if let Some(co) = colliders.get(*handle) {
465 if co.is_enabled() {
466 if let Some(co_parent) = co.parent {
467 unit_mprops += co
468 .shape
469 .mass_properties(1.0)
470 .transform_by(&co_parent.pos_wrt_parent);
471 }
472 }
473 }
474 }
475
476 if unit_mprops.mass() > 0.0 {
477 unit_mprops.set_mass(mass, true);
478 self.local_mprops += unit_mprops;
479 } else {
480 self.local_mprops.set_mass(mass, true);
482 }
483 }
484 }
485 }
486
487 self.recompute_max_extent(colliders, attached_colliders);
488 self.update_world_mass_properties(body_type, position);
489 }
490
491 pub(crate) fn recompute_max_extent(
494 &mut self,
495 colliders: &ColliderSet,
496 attached_colliders: &RigidBodyColliders,
497 ) {
498 let local_com = self.local_mprops.local_com;
499 let mut max_extent: Real = 0.0;
500 for handle in &attached_colliders.0 {
501 if let Some(co) = colliders.get(*handle) {
502 if co.is_enabled() {
503 if let Some(co_parent) = co.parent {
504 let sphere = co
505 .shape
506 .compute_local_bounding_sphere()
507 .transform_by(&co_parent.pos_wrt_parent);
508 let extent = (sphere.center - local_com).length() + sphere.radius;
509 max_extent = max_extent.max(extent);
510 }
511 }
512 }
513 }
514 self.max_extent = max_extent;
515 }
516
517 #[inline]
523 pub fn max_extent(&self) -> Real {
524 self.max_extent
525 }
526
527 pub fn update_world_mass_properties(&mut self, body_type: RigidBodyType, position: &Pose) {
529 self.world_com = self.local_mprops.world_com(position);
530 self.effective_inv_mass = Vector::splat(self.local_mprops.inv_mass);
531 self.effective_world_inv_inertia = self.local_mprops.world_inv_inertia(&position.rotation);
532
533 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_X) {
535 self.effective_inv_mass.x = 0.0;
536 }
537
538 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_Y) {
539 self.effective_inv_mass.y = 0.0;
540 }
541
542 #[cfg(feature = "dim3")]
543 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_Z) {
544 self.effective_inv_mass.z = 0.0;
545 }
546
547 #[cfg(feature = "dim2")]
548 {
549 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
550 self.effective_world_inv_inertia = 0.0;
551 }
552 }
553 #[cfg(feature = "dim3")]
554 {
555 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
556 self.effective_world_inv_inertia.m11 = 0.0;
557 self.effective_world_inv_inertia.m12 = 0.0;
558 self.effective_world_inv_inertia.m13 = 0.0;
559 }
560
561 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
562 self.effective_world_inv_inertia.m22 = 0.0;
563 self.effective_world_inv_inertia.m12 = 0.0;
564 self.effective_world_inv_inertia.m23 = 0.0;
565 }
566 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
567 self.effective_world_inv_inertia.m33 = 0.0;
568 self.effective_world_inv_inertia.m13 = 0.0;
569 self.effective_world_inv_inertia.m23 = 0.0;
570 }
571 }
572 }
573}
574
575#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
576#[derive(Clone, Debug, Copy, PartialEq)]
577#[repr(C)]
581pub struct RigidBodyVelocity<T: ScalarType> {
582 pub linvel: T::Vector,
584 pub angvel: T::AngVector,
586}
587
588impl Default for RigidBodyVelocity<Real> {
589 fn default() -> Self {
590 Self::zero()
591 }
592}
593
594impl RigidBodyVelocity<Real> {
595 #[must_use]
597 #[cfg(feature = "dim2")]
598 pub fn new(linvel: Vector, angvel: AngVector) -> Self {
599 Self { linvel, angvel }
600 }
601
602 #[must_use]
604 #[cfg(feature = "dim3")]
605 pub fn new(linvel: Vector, angvel: AngVector) -> Self {
606 Self {
607 linvel: Vector::new(linvel.x, linvel.y, linvel.z),
608 angvel: AngVector::new(angvel.x, angvel.y, angvel.z),
609 }
610 }
611
612 #[must_use]
617 #[cfg(feature = "dim2")]
618 pub fn from_slice(slice: &[Real]) -> Self {
619 Self {
620 linvel: Vector::new(slice[0], slice[1]),
621 angvel: slice[2],
622 }
623 }
624
625 #[must_use]
630 #[cfg(feature = "dim3")]
631 pub fn from_slice(slice: &[Real]) -> Self {
632 Self {
633 linvel: Vector::new(slice[0], slice[1], slice[2]),
634 angvel: AngVector::new(slice[3], slice[4], slice[5]),
635 }
636 }
637
638 #[must_use]
640 pub fn zero() -> Self {
641 Self {
642 linvel: Default::default(),
643 angvel: Default::default(),
644 }
645 }
646
647 #[must_use]
649 pub fn is_finite(&self) -> bool {
650 self.linvel.is_finite() && self.angvel.is_finite()
651 }
652
653 #[inline]
657 pub fn as_slice(&self) -> &[Real] {
658 self.as_vector().as_slice()
659 }
660
661 #[inline]
665 pub fn as_mut_slice(&mut self) -> &mut [Real] {
666 self.as_vector_mut().as_mut_slice()
667 }
668
669 #[inline]
673 #[cfg(feature = "dim2")]
674 pub fn as_vector(&self) -> &na::Vector3<Real> {
675 unsafe { core::mem::transmute(self) }
676 }
677
678 #[inline]
682 #[cfg(feature = "dim2")]
683 pub fn as_vector_mut(&mut self) -> &mut na::Vector3<Real> {
684 unsafe { core::mem::transmute(self) }
685 }
686
687 #[inline]
691 #[cfg(feature = "dim3")]
692 pub fn as_vector(&self) -> &na::Vector6<Real> {
693 unsafe { core::mem::transmute(self) }
694 }
695
696 #[inline]
700 #[cfg(feature = "dim3")]
701 pub fn as_vector_mut(&mut self) -> &mut na::Vector6<Real> {
702 unsafe { core::mem::transmute(self) }
703 }
704
705 #[must_use]
707 #[cfg(feature = "dim2")]
708 pub fn transformed(self, rotation: &Rotation) -> Self {
709 Self {
710 linvel: *rotation * self.linvel,
711 angvel: self.angvel,
712 }
713 }
714
715 #[must_use]
717 #[cfg(feature = "dim3")]
718 pub fn transformed(self, rotation: &Rotation) -> Self {
719 Self {
720 linvel: *rotation * self.linvel,
721 angvel: *rotation * self.angvel,
722 }
723 }
724
725 #[must_use]
731 pub fn pseudo_kinetic_energy(&self) -> Real {
732 0.5 * (self.linvel.length_squared() + self.angvel.gdot(self.angvel))
733 }
734
735 #[must_use]
737 #[cfg(feature = "dim2")]
738 pub fn velocity_at_point(&self, point: Vector, world_com: Vector) -> Vector {
739 let dpt = point - world_com;
740 self.linvel + self.angvel.gcross(dpt)
741 }
742
743 #[must_use]
745 #[cfg(feature = "dim3")]
746 pub fn velocity_at_point(&self, point: Vector, world_com: Vector) -> Vector {
747 let dpt = point - world_com;
748 self.linvel + self.angvel.gcross(dpt)
749 }
750
751 #[must_use]
753 pub fn is_zero(&self) -> bool {
754 self.linvel == Vector::ZERO && self.angvel == AngVector::default()
755 }
756
757 #[must_use]
759 #[profiling::function]
760 pub fn kinetic_energy(&self, rb_mprops: &RigidBodyMassProps) -> Real {
761 let mut energy = (rb_mprops.mass() * self.linvel.length_squared()) / 2.0;
762
763 #[cfg(feature = "dim2")]
764 if !num::Zero::is_zero(&rb_mprops.effective_world_inv_inertia) {
765 let inertia = 1.0 / rb_mprops.effective_world_inv_inertia;
766 energy += inertia * self.angvel * self.angvel / 2.0;
767 }
768
769 #[cfg(feature = "dim3")]
770 if !rb_mprops.effective_world_inv_inertia.is_zero() {
771 let inertia = rb_mprops.effective_world_inv_inertia.inverse_unchecked();
772 energy += self.angvel.gdot(inertia * self.angvel) / 2.0;
773 }
774
775 energy
776 }
777
778 pub fn apply_impulse(&mut self, rb_mprops: &RigidBodyMassProps, impulse: Vector) {
782 self.linvel += impulse * rb_mprops.effective_inv_mass;
783 }
784
785 #[cfg(feature = "dim2")]
789 pub fn apply_torque_impulse(&mut self, rb_mprops: &RigidBodyMassProps, torque_impulse: Real) {
790 self.angvel += rb_mprops.effective_world_inv_inertia * torque_impulse;
791 }
792
793 #[cfg(feature = "dim3")]
797 pub fn apply_torque_impulse(&mut self, rb_mprops: &RigidBodyMassProps, torque_impulse: Vector) {
798 self.angvel += rb_mprops.effective_world_inv_inertia * torque_impulse;
799 }
800
801 #[cfg(feature = "dim2")]
805 pub fn apply_impulse_at_point(
806 &mut self,
807 rb_mprops: &RigidBodyMassProps,
808 impulse: Vector,
809 point: Vector,
810 ) {
811 let torque_impulse = (point - rb_mprops.world_com).perp_dot(impulse);
812 self.apply_impulse(rb_mprops, impulse);
813 self.apply_torque_impulse(rb_mprops, torque_impulse);
814 }
815
816 #[cfg(feature = "dim3")]
820 pub fn apply_impulse_at_point(
821 &mut self,
822 rb_mprops: &RigidBodyMassProps,
823 impulse: Vector,
824 point: Vector,
825 ) {
826 let torque_impulse = (point - rb_mprops.world_com).cross(impulse);
827 self.apply_impulse(rb_mprops, impulse);
828 self.apply_torque_impulse(rb_mprops, torque_impulse);
829 }
830}
831
832impl<T: ScalarType> RigidBodyVelocity<T> {
833 #[must_use]
835 pub fn apply_damping(&self, dt: T, damping: &RigidBodyDamping<T>) -> Self {
836 let one = T::one();
837 RigidBodyVelocity {
838 linvel: self.linvel * (one / (one + dt * damping.linear_damping)),
839 angvel: self.angvel * (one / (one + dt * damping.angular_damping)),
840 }
841 }
842
843 #[must_use]
846 #[inline]
847 #[allow(clippy::let_and_return)] pub fn integrate(&self, dt: T, init_pos: &T::Pose, local_com: &T::Vector) -> T::Pose {
849 let com = *init_pos * *local_com;
850 let result = init_pos
851 .append_translation(-com)
852 .append_rotation(self.angvel * dt)
853 .append_translation(com + self.linvel * dt);
854 result
857 }
858}
859
860impl RigidBodyVelocity<Real> {
861 #[inline]
864 #[cfg(feature = "dim2")]
865 pub(crate) fn integrate_linearized(
866 &self,
867 dt: Real,
868 translation: &mut Vector,
869 rotation: &mut Rotation,
870 ) {
871 let dang = self.angvel * dt;
872 let new_cos = rotation.re - dang * rotation.im;
873 let new_sin = rotation.im + dang * rotation.re;
874 *rotation = Rot2::from_cos_sin_unchecked(new_cos, new_sin);
875 rotation.normalize_mut();
877 *translation += self.linvel * dt;
878 }
879
880 #[inline]
883 #[cfg(feature = "dim3")]
884 pub(crate) fn integrate_linearized(
885 &self,
886 dt: Real,
887 translation: &mut Vector,
888 rotation: &mut Rotation,
889 ) {
890 let hang = self.angvel * (dt * 0.5);
893 let id_plus_hang = Rotation::from_xyzw(hang.x, hang.y, hang.z, 1.0);
895 *rotation = id_plus_hang * *rotation;
896 *rotation = rotation.normalize();
897 *translation += self.linvel * dt;
898 }
899}
900
901impl core::ops::Mul<Real> for RigidBodyVelocity<Real> {
902 type Output = Self;
903
904 fn mul(self, rhs: Real) -> Self {
905 RigidBodyVelocity {
906 linvel: self.linvel * rhs,
907 angvel: self.angvel * rhs,
908 }
909 }
910}
911
912impl core::ops::Add<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
913 type Output = Self;
914
915 fn add(self, rhs: Self) -> Self {
916 RigidBodyVelocity {
917 linvel: self.linvel + rhs.linvel,
918 angvel: self.angvel + rhs.angvel,
919 }
920 }
921}
922
923impl core::ops::AddAssign<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
924 fn add_assign(&mut self, rhs: Self) {
925 self.linvel += rhs.linvel;
926 self.angvel += rhs.angvel;
927 }
928}
929
930impl core::ops::Sub<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
931 type Output = Self;
932
933 fn sub(self, rhs: Self) -> Self {
934 RigidBodyVelocity {
935 linvel: self.linvel - rhs.linvel,
936 angvel: self.angvel - rhs.angvel,
937 }
938 }
939}
940
941impl core::ops::SubAssign<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
942 fn sub_assign(&mut self, rhs: Self) {
943 self.linvel -= rhs.linvel;
944 self.angvel -= rhs.angvel;
945 }
946}
947
948#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
949#[derive(Clone, Debug, Copy, PartialEq)]
950pub struct RigidBodyDamping<T> {
952 pub linear_damping: T,
954 pub angular_damping: T,
956}
957
958impl<T: SimdRealCopy> Default for RigidBodyDamping<T> {
959 fn default() -> Self {
960 Self {
961 linear_damping: T::zero(),
962 angular_damping: T::zero(),
963 }
964 }
965}
966
967#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
968#[derive(Clone, Debug, Copy, PartialEq)]
969pub struct RigidBodyForces {
971 pub force: Vector,
973 pub torque: AngVector,
975 pub gravity_scale: Real,
978 pub user_force: Vector,
980 pub user_torque: AngVector,
982 #[cfg(feature = "dim3")]
984 pub gyroscopic_forces_enabled: bool,
985}
986
987impl Default for RigidBodyForces {
988 fn default() -> Self {
989 #[cfg(feature = "dim2")]
990 return Self {
991 force: Vector::ZERO,
992 torque: 0.0,
993 gravity_scale: 1.0,
994 user_force: Vector::ZERO,
995 user_torque: 0.0,
996 };
997
998 #[cfg(feature = "dim3")]
999 return Self {
1000 force: Vector::ZERO,
1001 torque: AngVector::ZERO,
1002 gravity_scale: 1.0,
1003 user_force: Vector::ZERO,
1004 user_torque: AngVector::ZERO,
1005 gyroscopic_forces_enabled: true,
1006 };
1007 }
1008}
1009
1010impl RigidBodyForces {
1011 #[must_use]
1013 pub fn integrate(
1014 &self,
1015 dt: Real,
1016 init_vels: &RigidBodyVelocity<Real>,
1017 mprops: &RigidBodyMassProps,
1018 ) -> RigidBodyVelocity<Real> {
1019 let linear_acc = self.force * mprops.effective_inv_mass;
1020 let angular_acc = mprops.effective_world_inv_inertia * self.torque;
1021
1022 RigidBodyVelocity {
1023 linvel: init_vels.linvel + linear_acc * dt,
1024 angvel: init_vels.angvel + angular_acc * dt,
1025 }
1026 }
1027
1028 pub fn compute_effective_force_and_torque(&mut self, gravity: Vector, mass: Vector) {
1031 self.force = self.user_force + gravity * mass * self.gravity_scale;
1032 self.torque = self.user_torque;
1033 }
1034
1035 pub fn apply_force_at_point(
1037 &mut self,
1038 rb_mprops: &RigidBodyMassProps,
1039 force: Vector,
1040 point: Vector,
1041 ) {
1042 self.user_force += force;
1043 self.user_torque += (point - rb_mprops.world_com).gcross(force);
1044 }
1045}
1046
1047#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1048#[derive(Clone, Debug, Copy, PartialEq)]
1049pub struct RigidBodyCcd {
1051 pub ccd_thickness: Real,
1054 pub ccd_active: bool,
1059 pub ccd_enabled: bool,
1064 pub soft_ccd_prediction: Real,
1066 pub allow_fast_rotation: bool,
1071}
1072
1073impl Default for RigidBodyCcd {
1074 fn default() -> Self {
1075 Self {
1076 ccd_thickness: Real::MAX,
1077 ccd_active: false,
1078 ccd_enabled: false,
1079 soft_ccd_prediction: 0.0,
1080 allow_fast_rotation: false,
1081 }
1082 }
1083}
1084
1085impl RigidBodyCcd {
1086 pub fn max_point_velocity(&self, vels: &RigidBodyVelocity<Real>, max_extent: Real) -> Real {
1092 #[cfg(feature = "dim2")]
1093 return vels.linvel.length() + vels.angvel.abs() * max_extent;
1094 #[cfg(feature = "dim3")]
1095 return vels.linvel.length() + vels.angvel.length() * max_extent;
1096 }
1097
1098 pub fn is_moving_fast(
1103 &self,
1104 dt: Real,
1105 vels: &RigidBodyVelocity<Real>,
1106 forces: Option<&RigidBodyForces>,
1107 max_extent: Real,
1108 ) -> bool {
1109 let max_point_velocity = if let Some(forces) = forces {
1110 let linear_part = (vels.linvel + forces.force * dt).length();
1111 #[cfg(feature = "dim2")]
1112 let angular_part = (vels.angvel + forces.torque * dt).abs() * max_extent;
1113 #[cfg(feature = "dim3")]
1114 let angular_part = (vels.angvel + forces.torque * dt).length() * max_extent;
1115 linear_part + angular_part
1116 } else {
1117 self.max_point_velocity(vels, max_extent)
1118 };
1119
1120 max_point_velocity * dt > Self::FAST_BODY_SAFETY_FACTOR * self.ccd_thickness
1121 }
1122
1123 pub const FAST_BODY_SAFETY_FACTOR: Real = 0.5;
1126
1127 pub fn is_moving_fast_with_next_position(
1132 &self,
1133 dt: Real,
1134 vels: &RigidBodyVelocity<Real>,
1135 pos: &RigidBodyPosition,
1136 local_com: Vector,
1137 max_extent: Real,
1138 ) -> bool {
1139 let com1 = pos.position * local_com;
1140 let com2 = pos.next_position * local_com;
1141
1142 let delta_rot = pos.next_position.rotation * pos.position.rotation.inverse();
1145 #[cfg(feature = "dim2")]
1146 let angular_delta = delta_rot.sin().abs() * max_extent;
1147 #[cfg(feature = "dim3")]
1148 let angular_delta =
1149 2.0 * Vector::new(delta_rot.x, delta_rot.y, delta_rot.z).length() * max_extent;
1150
1151 let max_delta_position = (com2 - com1).length() + angular_delta;
1152 let max_velocity = self.max_point_velocity(vels, max_extent);
1153 let max_motion = max_delta_position.max(max_velocity * dt);
1154
1155 max_motion > Self::FAST_BODY_SAFETY_FACTOR * self.ccd_thickness
1156 }
1157}
1158
1159#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1160#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
1161pub struct RigidBodyIds {
1163 pub(crate) active_island_id: u32,
1164 pub(crate) active_set_id: u32,
1165 pub(crate) island_id: u32,
1168 pub(crate) island_index: u32,
1171}
1172
1173impl Default for RigidBodyIds {
1174 fn default() -> Self {
1175 Self {
1176 active_island_id: u32::MAX,
1177 active_set_id: u32::MAX,
1178 island_id: crate::dynamics::INVALID_ISLAND,
1179 island_index: u32::MAX,
1180 }
1181 }
1182}
1183
1184#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1185#[derive(Default, Clone, Debug, PartialEq, Eq)]
1186pub struct RigidBodyColliders(pub Vec<ColliderHandle>);
1192
1193impl RigidBodyColliders {
1194 pub fn detach_collider(
1196 &mut self,
1197 rb_changes: &mut RigidBodyChanges,
1198 co_handle: ColliderHandle,
1199 ) {
1200 if let Some(i) = self.0.iter().position(|e| *e == co_handle) {
1201 rb_changes.set(RigidBodyChanges::COLLIDERS, true);
1202 self.0.swap_remove(i);
1203 }
1204 }
1205
1206 pub fn attach_collider(
1208 &mut self,
1209 rb_type: RigidBodyType,
1210 rb_changes: &mut RigidBodyChanges,
1211 rb_ccd: &mut RigidBodyCcd,
1212 rb_mprops: &mut RigidBodyMassProps,
1213 rb_pos: &RigidBodyPosition,
1214 co_handle: ColliderHandle,
1215 co_pos: &mut ColliderPosition,
1216 co_parent: &ColliderParent,
1217 co_shape: &ColliderShape,
1218 co_mprops: &ColliderMassProps,
1219 ) {
1220 rb_changes.set(RigidBodyChanges::COLLIDERS, true);
1221
1222 co_pos.0 = rb_pos.position * co_parent.pos_wrt_parent;
1223 if !crate::dynamics::ccd::shape_never_ccd_swept(&**co_shape) {
1227 rb_ccd.ccd_thickness = rb_ccd.ccd_thickness.min(co_shape.ccd_thickness());
1228 }
1229
1230 let mass_properties = co_mprops
1231 .mass_properties(&**co_shape)
1232 .transform_by(&co_parent.pos_wrt_parent);
1233 self.0.push(co_handle);
1234 rb_mprops.local_mprops += mass_properties;
1235 rb_mprops.update_world_mass_properties(rb_type, &rb_pos.position);
1236 }
1237
1238 pub(crate) fn update_positions(
1240 &self,
1241 colliders: &mut ColliderSet,
1242 modified_colliders: &mut ModifiedColliders,
1243 parent_pos: &Pose,
1244 ) {
1245 for handle in &self.0 {
1246 let co = colliders.index_mut_internal(*handle);
1250 let new_pos = parent_pos * co.parent.as_ref().unwrap().pos_wrt_parent;
1251
1252 modified_colliders.push_once(*handle, co);
1255
1256 co.changes |= ColliderChanges::POSITION;
1257 co.pos = ColliderPosition(new_pos);
1258 }
1259 }
1260}
1261
1262#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1263#[derive(Default, Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1264pub struct RigidBodyDominance(pub i8);
1266
1267impl RigidBodyDominance {
1268 pub fn effective_group(&self, status: &RigidBodyType) -> i16 {
1270 if status.is_dynamic_or_kinematic() {
1271 self.0 as i16
1272 } else {
1273 i8::MAX as i16 + 1
1274 }
1275 }
1276}
1277
1278#[derive(Copy, Clone, Debug, PartialEq)]
1296#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1297pub struct RigidBodyActivation {
1298 pub normalized_linear_threshold: Real,
1304
1305 pub angular_threshold: Real,
1311
1312 pub time_until_sleep: Real,
1316
1317 pub time_since_can_sleep: Real,
1319
1320 pub sleeping: bool,
1322
1323 pub(crate) sleep_prev_pose: Pose,
1326}
1327
1328impl Default for RigidBodyActivation {
1329 fn default() -> Self {
1330 Self::active()
1331 }
1332}
1333
1334impl RigidBodyActivation {
1335 pub fn default_normalized_linear_threshold() -> Real {
1339 0.05
1340 }
1341
1342 pub fn default_angular_threshold() -> Real {
1344 0.5
1345 }
1346
1347 pub fn default_time_until_sleep() -> Real {
1352 0.5
1353 }
1354
1355 pub fn active() -> Self {
1357 RigidBodyActivation {
1358 normalized_linear_threshold: Self::default_normalized_linear_threshold(),
1359 angular_threshold: Self::default_angular_threshold(),
1360 time_until_sleep: Self::default_time_until_sleep(),
1361 time_since_can_sleep: 0.0,
1362 sleeping: false,
1363 sleep_prev_pose: Pose::IDENTITY,
1364 }
1365 }
1366
1367 pub fn inactive() -> Self {
1369 RigidBodyActivation {
1370 normalized_linear_threshold: Self::default_normalized_linear_threshold(),
1371 angular_threshold: Self::default_angular_threshold(),
1372 time_until_sleep: Self::default_time_until_sleep(),
1373 time_since_can_sleep: Self::default_time_until_sleep(),
1374 sleeping: true,
1375 sleep_prev_pose: Pose::IDENTITY,
1376 }
1377 }
1378
1379 pub fn cannot_sleep() -> Self {
1381 RigidBodyActivation {
1382 normalized_linear_threshold: -1.0,
1383 angular_threshold: -1.0,
1384 ..Self::active()
1385 }
1386 }
1387
1388 #[inline]
1390 pub fn is_active(&self) -> bool {
1391 !self.sleeping
1392 }
1393
1394 #[inline]
1396 pub fn wake_up(&mut self, strong: bool) {
1397 self.sleeping = false;
1398
1399 if strong {
1400 self.time_since_can_sleep = 0.0;
1401 }
1402 }
1403
1404 #[inline]
1406 pub fn sleep(&mut self) {
1407 self.sleeping = true;
1408 self.time_since_can_sleep = self.time_until_sleep;
1409 }
1410
1411 pub fn is_eligible_for_sleep(&self) -> bool {
1414 self.time_since_can_sleep >= self.time_until_sleep
1415 }
1416
1417 pub(crate) fn update_energy(
1418 &mut self,
1419 body_type: RigidBodyType,
1420 length_unit: Real,
1421 sq_linvel: Real,
1422 sq_angvel: Real,
1423 max_extent: Real,
1424 pose: &Pose,
1425 dt: Real,
1426 ) {
1427 if self.sleeping {
1431 self.time_since_can_sleep = self.time_until_sleep;
1432 return;
1433 }
1434
1435 let can_sleep = match body_type {
1436 RigidBodyType::Dynamic => {
1437 let linear_threshold = self.normalized_linear_threshold * length_unit;
1438 let prev_pose = core::mem::replace(&mut self.sleep_prev_pose, *pose);
1439 let angular_ok = if max_extent > 0.0 {
1440 use crate::num::FloatConst;
1441 self.angular_threshold >= 0.0
1446 && sq_angvel < Real::FRAC_PI_2() * Real::FRAC_PI_2()
1447 } else {
1448 sq_angvel < self.angular_threshold * self.angular_threshold.abs()
1452 };
1453
1454 let drift = crate::geometry::relative_pose_drift(&prev_pose, pose, max_extent);
1455 angular_ok && drift * 0.5 < linear_threshold * dt
1456 }
1457 RigidBodyType::KinematicPositionBased | RigidBodyType::KinematicVelocityBased => {
1458 sq_linvel == 0.0 && sq_angvel == 0.0
1461 }
1462 RigidBodyType::Fixed => true,
1463 };
1464
1465 if can_sleep {
1466 self.time_since_can_sleep += dt;
1467 } else {
1468 self.time_since_can_sleep = 0.0;
1469 }
1470 }
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475 use super::*;
1476 use crate::math::Real;
1477
1478 #[test]
1479 fn test_interpolate_velocity() {
1480 #[cfg(feature = "f32")]
1483 let mut rng = oorandom::Rand32::new(0);
1484 #[cfg(feature = "f64")]
1485 let mut rng = oorandom::Rand64::new(0);
1486
1487 for i in -10..=10 {
1488 let mult = i as Real;
1489 let (local_com, curr_pos, next_pos);
1490 #[cfg(feature = "dim2")]
1491 {
1492 local_com = Vector::new(rng.rand_float(), rng.rand_float());
1493 curr_pos = Pose::new(
1494 Vector::new(rng.rand_float(), rng.rand_float()) * mult,
1495 rng.rand_float(),
1496 );
1497 next_pos = Pose::new(
1498 Vector::new(rng.rand_float(), rng.rand_float()) * mult,
1499 rng.rand_float(),
1500 );
1501 }
1502 #[cfg(feature = "dim3")]
1503 {
1504 local_com = Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float());
1505 curr_pos = Pose::new(
1506 Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()) * mult,
1507 Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()),
1508 );
1509 next_pos = Pose::new(
1510 Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()) * mult,
1511 Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()),
1512 );
1513 }
1514
1515 let dt = 0.016;
1516 let rb_pos = RigidBodyPosition {
1517 position: curr_pos,
1518 next_position: next_pos,
1519 };
1520 let vel = rb_pos.interpolate_velocity(1.0 / dt, local_com);
1521 let interp_pos = vel.integrate(dt, &curr_pos, &local_com);
1522 approx::assert_relative_eq!(interp_pos, next_pos, epsilon = 1.0e-5);
1523 }
1524 }
1525
1526 fn creep(shift_per_step: Real, steps: usize, dt: Real) -> RigidBodyActivation {
1529 let mut activation = RigidBodyActivation::active();
1530 let mut shift = 0.0;
1531
1532 for _ in 0..steps {
1533 shift += shift_per_step;
1534 let pose = Pose::from_translation(Vector::X * shift);
1535 activation.update_energy(RigidBodyType::Dynamic, 1.0, 0.0, 0.0, 1.0, &pose, dt);
1536 }
1537
1538 activation
1539 }
1540
1541 fn pinned_with_velocity(linvel: Real, steps: usize, dt: Real) -> RigidBodyActivation {
1544 let mut activation = RigidBodyActivation::active();
1545 let pose = Pose::from_translation(Vector::X * 3.0);
1546
1547 for _ in 0..steps {
1548 activation.update_energy(
1549 RigidBodyType::Dynamic,
1550 1.0,
1551 linvel * linvel,
1552 0.0,
1553 1.0,
1554 &pose,
1555 dt,
1556 );
1557 }
1558
1559 activation
1560 }
1561
1562 #[test]
1563 fn test_sleep_allows_pinned_body_with_residual_velocity() {
1564 let dt = 1.0 / 60.0;
1565 let threshold = RigidBodyActivation::default_normalized_linear_threshold();
1566 let steps = (10.0 * RigidBodyActivation::default_time_until_sleep() / dt) as usize;
1567
1568 assert!(pinned_with_velocity(threshold * 5.0, steps, dt).is_eligible_for_sleep());
1571 }
1572
1573 #[test]
1574 fn test_sleep_gates_position_corrections() {
1575 let dt = 1.0 / 60.0;
1576 let budget = 2.0 * RigidBodyActivation::default_normalized_linear_threshold() * dt;
1578 let steps = (10.0 * RigidBodyActivation::default_time_until_sleep() / dt) as usize;
1579
1580 assert!(!creep(budget * 1.5, steps, dt).is_eligible_for_sleep());
1582
1583 assert!(creep(budget * 0.5, steps, dt).is_eligible_for_sleep());
1585 assert!(creep(0.0, steps, dt).is_eligible_for_sleep());
1586 }
1587}