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
57 SoftFrame = 4,
61 }
64
65impl RigidBodyType {
66 pub fn is_fixed(self) -> bool {
68 self == RigidBodyType::Fixed
69 }
70
71 pub fn is_dynamic(self) -> bool {
76 self == RigidBodyType::Dynamic || self == RigidBodyType::SoftFrame
77 }
78
79 pub fn is_soft_frame(self) -> bool {
81 self == RigidBodyType::SoftFrame
82 }
83
84 pub fn is_kinematic(self) -> bool {
86 self == RigidBodyType::KinematicPositionBased
87 || self == RigidBodyType::KinematicVelocityBased
88 }
89
90 pub fn is_dynamic_or_kinematic(self) -> bool {
95 self != RigidBodyType::Fixed
96 }
97}
98
99bitflags::bitflags! {
100 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
101 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
102 pub struct RigidBodyChanges: u32 {
104 const IN_MODIFIED_SET = 1 << 0;
106 const POSITION = 1 << 1;
108 const SLEEP = 1 << 2;
110 const COLLIDERS = 1 << 3;
112 const TYPE = 1 << 4;
114 const DOMINANCE = 1 << 5;
116 const LOCAL_MASS_PROPERTIES = 1 << 6;
118 const ENABLED_OR_DISABLED = 1 << 7;
120 }
121}
122
123impl Default for RigidBodyChanges {
124 fn default() -> Self {
125 RigidBodyChanges::empty()
126 }
127}
128
129#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
130#[derive(Clone, Debug, Copy, PartialEq)]
131pub struct RigidBodyPosition {
133 pub position: Pose,
135 pub next_position: Pose,
145}
146
147impl Default for RigidBodyPosition {
148 fn default() -> Self {
149 Self {
150 position: Pose::IDENTITY,
151 next_position: Pose::IDENTITY,
152 }
153 }
154}
155
156impl RigidBodyPosition {
157 #[must_use]
160 pub fn interpolate_velocity(&self, inv_dt: Real, local_com: Vector) -> RigidBodyVelocity<Real> {
161 let pose_err = self.pose_errors(local_com);
162 RigidBodyVelocity {
163 linvel: pose_err.linear * inv_dt,
164 angvel: pose_err.angular * inv_dt,
165 }
166 }
167
168 #[must_use]
172 pub fn integrate_forces_and_velocities(
173 &self,
174 dt: Real,
175 forces: &RigidBodyForces,
176 vels: &RigidBodyVelocity<Real>,
177 mprops: &RigidBodyMassProps,
178 ) -> Pose {
179 let new_vels = forces.integrate(dt, vels, mprops);
180 let local_com = mprops.local_mprops.local_com;
181 new_vels.integrate(dt, &self.position, &local_com)
182 }
183
184 pub fn pose_errors(&self, local_com: Vector) -> PdErrors {
192 let com = self.position * local_com;
193 let shift = Pose::from_translation(com);
194 let dpos = shift.inverse() * self.next_position * self.position.inverse() * shift;
195
196 let angular;
197 #[cfg(feature = "dim2")]
198 {
199 angular = dpos.rotation.angle();
200 }
201 #[cfg(feature = "dim3")]
202 {
203 angular = dpos.rotation.to_scaled_axis();
204 }
205 let linear = dpos.translation;
206
207 PdErrors { linear, angular }
208 }
209}
210
211impl<T> From<T> for RigidBodyPosition
212where
213 Pose: From<T>,
214{
215 fn from(position: T) -> Self {
216 let position = position.into();
217 Self {
218 position,
219 next_position: position,
220 }
221 }
222}
223
224bitflags::bitflags! {
225 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
226 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
227 pub struct AxesMask: u8 {
229 const LIN_X = 1 << 0;
231 const LIN_Y = 1 << 1;
233 #[cfg(feature = "dim3")]
235 const LIN_Z = 1 << 2;
236 #[cfg(feature = "dim3")]
238 const ANG_X = 1 << 3;
239 #[cfg(feature = "dim3")]
241 const ANG_Y = 1 << 4;
242 const ANG_Z = 1 << 5;
244 }
245}
246
247impl Default for AxesMask {
248 fn default() -> Self {
249 AxesMask::empty()
250 }
251}
252
253bitflags::bitflags! {
254 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
255 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
256 pub struct LockedAxes: u8 {
285 const TRANSLATION_LOCKED_X = 1 << 0;
287 const TRANSLATION_LOCKED_Y = 1 << 1;
289 const TRANSLATION_LOCKED_Z = 1 << 2;
291 const TRANSLATION_LOCKED = Self::TRANSLATION_LOCKED_X.bits() | Self::TRANSLATION_LOCKED_Y.bits() | Self::TRANSLATION_LOCKED_Z.bits();
293 const ROTATION_LOCKED_X = 1 << 3;
295 const ROTATION_LOCKED_Y = 1 << 4;
297 const ROTATION_LOCKED_Z = 1 << 5;
299 const ROTATION_LOCKED = Self::ROTATION_LOCKED_X.bits() | Self::ROTATION_LOCKED_Y.bits() | Self::ROTATION_LOCKED_Z.bits();
301 }
302}
303
304#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
306#[derive(Copy, Clone, Debug, PartialEq)]
307pub enum RigidBodyAdditionalMassProps {
308 MassProps(MassProperties),
310 Mass(Real),
313}
314
315impl Default for RigidBodyAdditionalMassProps {
316 fn default() -> Self {
317 RigidBodyAdditionalMassProps::MassProps(MassProperties::default())
318 }
319}
320
321#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
322#[derive(Clone, Debug, PartialEq)]
323pub struct RigidBodyMassProps {
326 pub world_com: Vector,
328 pub effective_inv_mass: Vector,
330 pub effective_world_inv_inertia: AngularInertia,
333 pub local_mprops: MassProperties,
335 pub flags: LockedAxes,
337 pub additional_local_mprops: Option<Box<RigidBodyAdditionalMassProps>>,
339 #[cfg_attr(feature = "serde-serialize", serde(default))]
343 pub(crate) max_extent: Real,
344}
345
346impl Default for RigidBodyMassProps {
347 fn default() -> Self {
348 Self {
349 flags: LockedAxes::empty(),
350 local_mprops: MassProperties::zero(),
351 additional_local_mprops: None,
352 world_com: Vector::ZERO,
353 effective_inv_mass: Vector::ZERO,
354 effective_world_inv_inertia: AngularInertia::zero(),
355 max_extent: 0.0,
356 }
357 }
358}
359
360impl From<LockedAxes> for RigidBodyMassProps {
361 fn from(flags: LockedAxes) -> Self {
362 Self {
363 flags,
364 ..Self::default()
365 }
366 }
367}
368
369impl From<MassProperties> for RigidBodyMassProps {
370 fn from(local_mprops: MassProperties) -> Self {
371 Self {
372 local_mprops,
373 ..Default::default()
374 }
375 }
376}
377
378impl RigidBodyMassProps {
379 #[must_use]
381 pub fn mass(&self) -> Real {
382 crate::utils::inv(self.local_mprops.inv_mass)
383 }
384
385 #[must_use]
388 pub fn effective_mass(&self) -> Vector {
389 self.effective_inv_mass.map(crate::utils::inv)
390 }
391
392 #[must_use]
395 pub fn effective_angular_inertia(&self) -> AngularInertia {
396 #[allow(unused_mut)] let mut ang_inertia = self.effective_world_inv_inertia;
398
399 #[cfg(feature = "dim3")]
401 {
402 if self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
403 ang_inertia.m11 = 1.0;
404 }
405 if self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
406 ang_inertia.m22 = 1.0;
407 }
408 if self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
409 ang_inertia.m33 = 1.0;
410 }
411 }
412
413 #[allow(unused_mut)] let mut result = ang_inertia.inverse();
415
416 #[cfg(feature = "dim3")]
418 {
419 if self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
420 result.m11 = 0.0;
421 }
422 if self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
423 result.m22 = 0.0;
424 }
425 if self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
426 result.m33 = 0.0;
427 }
428 }
429
430 result
431 }
432
433 pub fn recompute_mass_properties_from_colliders(
435 &mut self,
436 colliders: &ColliderSet,
437 attached_colliders: &RigidBodyColliders,
438 body_type: RigidBodyType,
439 position: &Pose,
440 ) {
441 if body_type.is_soft_frame() {
442 self.recompute_max_extent(colliders, attached_colliders);
445 return;
446 }
447 let added_mprops = self
448 .additional_local_mprops
449 .as_ref()
450 .map(|mprops| **mprops)
451 .unwrap_or_else(|| RigidBodyAdditionalMassProps::MassProps(MassProperties::default()));
452
453 self.local_mprops = MassProperties::default();
454
455 for handle in &attached_colliders.0 {
456 if let Some(co) = colliders.get(*handle) {
457 if co.is_enabled() {
458 if let Some(co_parent) = co.parent {
459 let to_add = co
460 .mprops
461 .mass_properties(&*co.shape)
462 .transform_by(&co_parent.pos_wrt_parent);
463 self.local_mprops += to_add;
464 }
465 }
466 }
467 }
468
469 match added_mprops {
470 RigidBodyAdditionalMassProps::MassProps(mprops) => {
471 self.local_mprops += mprops;
472 }
473 RigidBodyAdditionalMassProps::Mass(mass) => {
474 let prev_mass = self.local_mprops.mass();
475 if prev_mass > 0.0 {
476 self.local_mprops.set_mass(prev_mass + mass, true);
477 } else {
478 let mut unit_mprops = MassProperties::default();
482 for handle in &attached_colliders.0 {
483 if let Some(co) = colliders.get(*handle) {
484 if co.is_enabled() {
485 if let Some(co_parent) = co.parent {
486 unit_mprops += co
487 .shape
488 .mass_properties(1.0)
489 .transform_by(&co_parent.pos_wrt_parent);
490 }
491 }
492 }
493 }
494
495 if unit_mprops.mass() > 0.0 {
496 unit_mprops.set_mass(mass, true);
497 self.local_mprops += unit_mprops;
498 } else {
499 self.local_mprops.set_mass(mass, true);
501 }
502 }
503 }
504 }
505
506 self.recompute_max_extent(colliders, attached_colliders);
507 self.update_world_mass_properties(body_type, position);
508 }
509
510 pub(crate) fn recompute_max_extent(
513 &mut self,
514 colliders: &ColliderSet,
515 attached_colliders: &RigidBodyColliders,
516 ) {
517 let local_com = self.local_mprops.local_com;
518 let mut max_extent: Real = 0.0;
519 for handle in &attached_colliders.0 {
520 if let Some(co) = colliders.get(*handle) {
521 if co.is_enabled() {
522 if let Some(co_parent) = co.parent {
523 let sphere = co
524 .shape
525 .compute_local_bounding_sphere()
526 .transform_by(&co_parent.pos_wrt_parent);
527 let extent = (sphere.center - local_com).length() + sphere.radius;
528 max_extent = max_extent.max(extent);
529 }
530 }
531 }
532 }
533 self.max_extent = max_extent;
534 }
535
536 #[inline]
542 pub fn max_extent(&self) -> Real {
543 self.max_extent
544 }
545
546 pub fn update_world_mass_properties(&mut self, body_type: RigidBodyType, position: &Pose) {
548 if body_type.is_soft_frame() {
549 return;
552 }
553 self.world_com = self.local_mprops.world_com(position);
554 self.effective_inv_mass = Vector::splat(self.local_mprops.inv_mass);
555 self.effective_world_inv_inertia = self.local_mprops.world_inv_inertia(&position.rotation);
556
557 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_X) {
559 self.effective_inv_mass.x = 0.0;
560 }
561
562 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_Y) {
563 self.effective_inv_mass.y = 0.0;
564 }
565
566 #[cfg(feature = "dim3")]
567 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_Z) {
568 self.effective_inv_mass.z = 0.0;
569 }
570
571 #[cfg(feature = "dim2")]
572 {
573 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
574 self.effective_world_inv_inertia = 0.0;
575 }
576 }
577 #[cfg(feature = "dim3")]
578 {
579 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
580 self.effective_world_inv_inertia.m11 = 0.0;
581 self.effective_world_inv_inertia.m12 = 0.0;
582 self.effective_world_inv_inertia.m13 = 0.0;
583 }
584
585 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
586 self.effective_world_inv_inertia.m22 = 0.0;
587 self.effective_world_inv_inertia.m12 = 0.0;
588 self.effective_world_inv_inertia.m23 = 0.0;
589 }
590 if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
591 self.effective_world_inv_inertia.m33 = 0.0;
592 self.effective_world_inv_inertia.m13 = 0.0;
593 self.effective_world_inv_inertia.m23 = 0.0;
594 }
595 }
596 }
597}
598
599#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
600#[derive(Clone, Debug, Copy, PartialEq)]
601#[repr(C)]
605pub struct RigidBodyVelocity<T: ScalarType> {
606 pub linvel: T::Vector,
608 pub angvel: T::AngVector,
610}
611
612impl Default for RigidBodyVelocity<Real> {
613 fn default() -> Self {
614 Self::zero()
615 }
616}
617
618impl RigidBodyVelocity<Real> {
619 #[must_use]
621 #[cfg(feature = "dim2")]
622 pub fn new(linvel: Vector, angvel: AngVector) -> Self {
623 Self { linvel, angvel }
624 }
625
626 #[must_use]
628 #[cfg(feature = "dim3")]
629 pub fn new(linvel: Vector, angvel: AngVector) -> Self {
630 Self {
631 linvel: Vector::new(linvel.x, linvel.y, linvel.z),
632 angvel: AngVector::new(angvel.x, angvel.y, angvel.z),
633 }
634 }
635
636 #[must_use]
641 #[cfg(feature = "dim2")]
642 pub fn from_slice(slice: &[Real]) -> Self {
643 Self {
644 linvel: Vector::new(slice[0], slice[1]),
645 angvel: slice[2],
646 }
647 }
648
649 #[must_use]
654 #[cfg(feature = "dim3")]
655 pub fn from_slice(slice: &[Real]) -> Self {
656 Self {
657 linvel: Vector::new(slice[0], slice[1], slice[2]),
658 angvel: AngVector::new(slice[3], slice[4], slice[5]),
659 }
660 }
661
662 #[must_use]
664 pub fn zero() -> Self {
665 Self {
666 linvel: Default::default(),
667 angvel: Default::default(),
668 }
669 }
670
671 #[must_use]
673 pub fn is_finite(&self) -> bool {
674 self.linvel.is_finite() && self.angvel.is_finite()
675 }
676
677 #[inline]
681 pub fn as_slice(&self) -> &[Real] {
682 self.as_vector().as_slice()
683 }
684
685 #[inline]
689 pub fn as_mut_slice(&mut self) -> &mut [Real] {
690 self.as_vector_mut().as_mut_slice()
691 }
692
693 #[inline]
697 #[cfg(feature = "dim2")]
698 pub fn as_vector(&self) -> &na::Vector3<Real> {
699 unsafe { core::mem::transmute(self) }
700 }
701
702 #[inline]
706 #[cfg(feature = "dim2")]
707 pub fn as_vector_mut(&mut self) -> &mut na::Vector3<Real> {
708 unsafe { core::mem::transmute(self) }
709 }
710
711 #[inline]
715 #[cfg(feature = "dim3")]
716 pub fn as_vector(&self) -> &na::Vector6<Real> {
717 unsafe { core::mem::transmute(self) }
718 }
719
720 #[inline]
724 #[cfg(feature = "dim3")]
725 pub fn as_vector_mut(&mut self) -> &mut na::Vector6<Real> {
726 unsafe { core::mem::transmute(self) }
727 }
728
729 #[must_use]
731 #[cfg(feature = "dim2")]
732 pub fn transformed(self, rotation: &Rotation) -> Self {
733 Self {
734 linvel: *rotation * self.linvel,
735 angvel: self.angvel,
736 }
737 }
738
739 #[must_use]
741 #[cfg(feature = "dim3")]
742 pub fn transformed(self, rotation: &Rotation) -> Self {
743 Self {
744 linvel: *rotation * self.linvel,
745 angvel: *rotation * self.angvel,
746 }
747 }
748
749 #[must_use]
755 pub fn pseudo_kinetic_energy(&self) -> Real {
756 0.5 * (self.linvel.length_squared() + self.angvel.gdot(self.angvel))
757 }
758
759 #[must_use]
761 #[cfg(feature = "dim2")]
762 pub fn velocity_at_point(&self, point: Vector, world_com: Vector) -> Vector {
763 let dpt = point - world_com;
764 self.linvel + self.angvel.gcross(dpt)
765 }
766
767 #[must_use]
769 #[cfg(feature = "dim3")]
770 pub fn velocity_at_point(&self, point: Vector, world_com: Vector) -> Vector {
771 let dpt = point - world_com;
772 self.linvel + self.angvel.gcross(dpt)
773 }
774
775 #[must_use]
777 pub fn is_zero(&self) -> bool {
778 self.linvel == Vector::ZERO && self.angvel == AngVector::default()
779 }
780
781 #[must_use]
783 #[profiling::function]
784 pub fn kinetic_energy(&self, rb_mprops: &RigidBodyMassProps) -> Real {
785 let mut energy = (rb_mprops.mass() * self.linvel.length_squared()) / 2.0;
786
787 #[cfg(feature = "dim2")]
788 if !num::Zero::is_zero(&rb_mprops.effective_world_inv_inertia) {
789 let inertia = 1.0 / rb_mprops.effective_world_inv_inertia;
790 energy += inertia * self.angvel * self.angvel / 2.0;
791 }
792
793 #[cfg(feature = "dim3")]
794 if !rb_mprops.effective_world_inv_inertia.is_zero() {
795 let inertia = rb_mprops.effective_world_inv_inertia.inverse_unchecked();
796 energy += self.angvel.gdot(inertia * self.angvel) / 2.0;
797 }
798
799 energy
800 }
801
802 pub fn apply_impulse(&mut self, rb_mprops: &RigidBodyMassProps, impulse: Vector) {
806 self.linvel += impulse * rb_mprops.effective_inv_mass;
807 }
808
809 #[cfg(feature = "dim2")]
813 pub fn apply_torque_impulse(&mut self, rb_mprops: &RigidBodyMassProps, torque_impulse: Real) {
814 self.angvel += rb_mprops.effective_world_inv_inertia * torque_impulse;
815 }
816
817 #[cfg(feature = "dim3")]
821 pub fn apply_torque_impulse(&mut self, rb_mprops: &RigidBodyMassProps, torque_impulse: Vector) {
822 self.angvel += rb_mprops.effective_world_inv_inertia * torque_impulse;
823 }
824
825 #[cfg(feature = "dim2")]
829 pub fn apply_impulse_at_point(
830 &mut self,
831 rb_mprops: &RigidBodyMassProps,
832 impulse: Vector,
833 point: Vector,
834 ) {
835 let torque_impulse = (point - rb_mprops.world_com).perp_dot(impulse);
836 self.apply_impulse(rb_mprops, impulse);
837 self.apply_torque_impulse(rb_mprops, torque_impulse);
838 }
839
840 #[cfg(feature = "dim3")]
844 pub fn apply_impulse_at_point(
845 &mut self,
846 rb_mprops: &RigidBodyMassProps,
847 impulse: Vector,
848 point: Vector,
849 ) {
850 let torque_impulse = (point - rb_mprops.world_com).cross(impulse);
851 self.apply_impulse(rb_mprops, impulse);
852 self.apply_torque_impulse(rb_mprops, torque_impulse);
853 }
854}
855
856impl<T: ScalarType> RigidBodyVelocity<T> {
857 #[must_use]
859 pub fn apply_damping(&self, dt: T, damping: &RigidBodyDamping<T>) -> Self {
860 let one = T::one();
861 RigidBodyVelocity {
862 linvel: self.linvel * (one / (one + dt * damping.linear_damping)),
863 angvel: self.angvel * (one / (one + dt * damping.angular_damping)),
864 }
865 }
866
867 #[must_use]
870 #[inline]
871 #[allow(clippy::let_and_return)] pub fn integrate(&self, dt: T, init_pos: &T::Pose, local_com: &T::Vector) -> T::Pose {
873 let com = *init_pos * *local_com;
874 let result = init_pos
875 .append_translation(-com)
876 .append_rotation(self.angvel * dt)
877 .append_translation(com + self.linvel * dt);
878 result
881 }
882}
883
884impl RigidBodyVelocity<Real> {
885 #[inline]
888 #[cfg(feature = "dim2")]
889 pub(crate) fn integrate_linearized(
890 &self,
891 dt: Real,
892 translation: &mut Vector,
893 rotation: &mut Rotation,
894 ) {
895 let dang = self.angvel * dt;
896 let new_cos = rotation.re - dang * rotation.im;
897 let new_sin = rotation.im + dang * rotation.re;
898 *rotation = Rot2::from_cos_sin_unchecked(new_cos, new_sin);
899 rotation.normalize_mut();
901 *translation += self.linvel * dt;
902 }
903
904 #[inline]
907 #[cfg(feature = "dim3")]
908 pub(crate) fn integrate_linearized(
909 &self,
910 dt: Real,
911 translation: &mut Vector,
912 rotation: &mut Rotation,
913 ) {
914 let hang = self.angvel * (dt * 0.5);
917 let id_plus_hang = Rotation::from_xyzw(hang.x, hang.y, hang.z, 1.0);
919 *rotation = id_plus_hang * *rotation;
920 *rotation = rotation.normalize();
921 *translation += self.linvel * dt;
922 }
923}
924
925impl core::ops::Mul<Real> for RigidBodyVelocity<Real> {
926 type Output = Self;
927
928 fn mul(self, rhs: Real) -> Self {
929 RigidBodyVelocity {
930 linvel: self.linvel * rhs,
931 angvel: self.angvel * rhs,
932 }
933 }
934}
935
936impl core::ops::Add<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
937 type Output = Self;
938
939 fn add(self, rhs: Self) -> Self {
940 RigidBodyVelocity {
941 linvel: self.linvel + rhs.linvel,
942 angvel: self.angvel + rhs.angvel,
943 }
944 }
945}
946
947impl core::ops::AddAssign<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
948 fn add_assign(&mut self, rhs: Self) {
949 self.linvel += rhs.linvel;
950 self.angvel += rhs.angvel;
951 }
952}
953
954impl core::ops::Sub<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
955 type Output = Self;
956
957 fn sub(self, rhs: Self) -> Self {
958 RigidBodyVelocity {
959 linvel: self.linvel - rhs.linvel,
960 angvel: self.angvel - rhs.angvel,
961 }
962 }
963}
964
965impl core::ops::SubAssign<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
966 fn sub_assign(&mut self, rhs: Self) {
967 self.linvel -= rhs.linvel;
968 self.angvel -= rhs.angvel;
969 }
970}
971
972#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
973#[derive(Clone, Debug, Copy, PartialEq)]
974pub struct RigidBodyDamping<T> {
976 pub linear_damping: T,
978 pub angular_damping: T,
980}
981
982impl<T: SimdRealCopy> Default for RigidBodyDamping<T> {
983 fn default() -> Self {
984 Self {
985 linear_damping: T::zero(),
986 angular_damping: T::zero(),
987 }
988 }
989}
990
991#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
992#[derive(Clone, Debug, Copy, PartialEq)]
993pub struct RigidBodyForces {
995 pub force: Vector,
997 pub torque: AngVector,
999 pub gravity_scale: Real,
1002 pub user_force: Vector,
1004 pub user_torque: AngVector,
1006 #[cfg(feature = "dim3")]
1008 pub gyroscopic_forces_enabled: bool,
1009}
1010
1011impl Default for RigidBodyForces {
1012 fn default() -> Self {
1013 #[cfg(feature = "dim2")]
1014 return Self {
1015 force: Vector::ZERO,
1016 torque: 0.0,
1017 gravity_scale: 1.0,
1018 user_force: Vector::ZERO,
1019 user_torque: 0.0,
1020 };
1021
1022 #[cfg(feature = "dim3")]
1023 return Self {
1024 force: Vector::ZERO,
1025 torque: AngVector::ZERO,
1026 gravity_scale: 1.0,
1027 user_force: Vector::ZERO,
1028 user_torque: AngVector::ZERO,
1029 gyroscopic_forces_enabled: true,
1030 };
1031 }
1032}
1033
1034impl RigidBodyForces {
1035 #[must_use]
1037 pub fn integrate(
1038 &self,
1039 dt: Real,
1040 init_vels: &RigidBodyVelocity<Real>,
1041 mprops: &RigidBodyMassProps,
1042 ) -> RigidBodyVelocity<Real> {
1043 let linear_acc = self.force * mprops.effective_inv_mass;
1044 let angular_acc = mprops.effective_world_inv_inertia * self.torque;
1045
1046 RigidBodyVelocity {
1047 linvel: init_vels.linvel + linear_acc * dt,
1048 angvel: init_vels.angvel + angular_acc * dt,
1049 }
1050 }
1051
1052 pub fn compute_effective_force_and_torque(&mut self, gravity: Vector, mass: Vector) {
1055 self.force = self.user_force + gravity * mass * self.gravity_scale;
1056 self.torque = self.user_torque;
1057 }
1058
1059 pub fn apply_force_at_point(
1061 &mut self,
1062 rb_mprops: &RigidBodyMassProps,
1063 force: Vector,
1064 point: Vector,
1065 ) {
1066 self.user_force += force;
1067 self.user_torque += (point - rb_mprops.world_com).gcross(force);
1068 }
1069}
1070
1071#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1072#[derive(Clone, Debug, Copy, PartialEq)]
1073pub struct RigidBodyCcd {
1075 pub ccd_thickness: Real,
1078 pub ccd_active: bool,
1083 pub ccd_enabled: bool,
1088 pub soft_ccd_prediction: Real,
1090 pub allow_fast_rotation: bool,
1095}
1096
1097impl Default for RigidBodyCcd {
1098 fn default() -> Self {
1099 Self {
1100 ccd_thickness: Real::MAX,
1101 ccd_active: false,
1102 ccd_enabled: false,
1103 soft_ccd_prediction: 0.0,
1104 allow_fast_rotation: false,
1105 }
1106 }
1107}
1108
1109impl RigidBodyCcd {
1110 pub fn max_point_velocity(&self, vels: &RigidBodyVelocity<Real>, max_extent: Real) -> Real {
1116 #[cfg(feature = "dim2")]
1117 return vels.linvel.length() + vels.angvel.abs() * max_extent;
1118 #[cfg(feature = "dim3")]
1119 return vels.linvel.length() + vels.angvel.length() * max_extent;
1120 }
1121
1122 pub fn is_moving_fast(
1127 &self,
1128 dt: Real,
1129 vels: &RigidBodyVelocity<Real>,
1130 forces: Option<(&RigidBodyForces, &RigidBodyMassProps)>,
1131 max_extent: Real,
1132 ) -> bool {
1133 let max_point_velocity = if let Some((forces, mprops)) = forces {
1135 self.max_point_velocity(&forces.integrate(dt, vels, mprops), max_extent)
1136 } else {
1137 self.max_point_velocity(vels, max_extent)
1138 };
1139
1140 max_point_velocity * dt > Self::FAST_BODY_SAFETY_FACTOR * self.ccd_thickness
1141 }
1142
1143 pub const FAST_BODY_SAFETY_FACTOR: Real = 0.5;
1146
1147 pub fn is_moving_fast_with_next_position(
1152 &self,
1153 dt: Real,
1154 vels: &RigidBodyVelocity<Real>,
1155 pos: &RigidBodyPosition,
1156 local_com: Vector,
1157 max_extent: Real,
1158 ) -> bool {
1159 let com1 = pos.position * local_com;
1160 let com2 = pos.next_position * local_com;
1161
1162 let delta_rot = pos.next_position.rotation * pos.position.rotation.inverse();
1165 #[cfg(feature = "dim2")]
1166 let angular_delta = delta_rot.sin().abs() * max_extent;
1167 #[cfg(feature = "dim3")]
1168 let angular_delta =
1169 2.0 * Vector::new(delta_rot.x, delta_rot.y, delta_rot.z).length() * max_extent;
1170
1171 let max_delta_position = (com2 - com1).length() + angular_delta;
1172 let max_velocity = self.max_point_velocity(vels, max_extent);
1173 let max_motion = max_delta_position.max(max_velocity * dt);
1174
1175 max_motion > Self::FAST_BODY_SAFETY_FACTOR * self.ccd_thickness
1176 }
1177}
1178
1179#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1180#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
1181pub struct RigidBodyIds {
1183 pub(crate) active_island_id: u32,
1184 pub(crate) active_set_id: u32,
1185 pub(crate) island_id: u32,
1188 pub(crate) island_index: u32,
1191}
1192
1193impl Default for RigidBodyIds {
1194 fn default() -> Self {
1195 Self {
1196 active_island_id: u32::MAX,
1197 active_set_id: u32::MAX,
1198 island_id: crate::dynamics::INVALID_ISLAND,
1199 island_index: u32::MAX,
1200 }
1201 }
1202}
1203
1204#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1205#[derive(Default, Clone, Debug, PartialEq, Eq)]
1206pub struct RigidBodyColliders(pub Vec<ColliderHandle>);
1212
1213impl RigidBodyColliders {
1214 pub fn detach_collider(
1216 &mut self,
1217 rb_changes: &mut RigidBodyChanges,
1218 co_handle: ColliderHandle,
1219 ) {
1220 if let Some(i) = self.0.iter().position(|e| *e == co_handle) {
1221 rb_changes.set(RigidBodyChanges::COLLIDERS, true);
1222 self.0.swap_remove(i);
1223 }
1224 }
1225
1226 pub fn attach_collider(
1228 &mut self,
1229 rb_type: RigidBodyType,
1230 rb_changes: &mut RigidBodyChanges,
1231 rb_ccd: &mut RigidBodyCcd,
1232 rb_mprops: &mut RigidBodyMassProps,
1233 rb_pos: &RigidBodyPosition,
1234 co_handle: ColliderHandle,
1235 co_pos: &mut ColliderPosition,
1236 co_parent: &ColliderParent,
1237 co_shape: &ColliderShape,
1238 co_mprops: &ColliderMassProps,
1239 ) {
1240 rb_changes.set(RigidBodyChanges::COLLIDERS, true);
1241
1242 co_pos.0 = rb_pos.position * co_parent.pos_wrt_parent;
1243 if !crate::dynamics::ccd::shape_never_ccd_swept(&**co_shape) {
1247 rb_ccd.ccd_thickness = rb_ccd.ccd_thickness.min(co_shape.ccd_thickness());
1248 }
1249
1250 self.0.push(co_handle);
1251 if !rb_type.is_soft_frame() {
1254 let mass_properties = co_mprops
1255 .mass_properties(&**co_shape)
1256 .transform_by(&co_parent.pos_wrt_parent);
1257 rb_mprops.local_mprops += mass_properties;
1258 rb_mprops.update_world_mass_properties(rb_type, &rb_pos.position);
1259 }
1260 }
1261
1262 pub(crate) fn update_positions(
1264 &self,
1265 colliders: &mut ColliderSet,
1266 modified_colliders: &mut ModifiedColliders,
1267 parent_pos: &Pose,
1268 ) {
1269 for handle in &self.0 {
1270 let co = colliders.index_mut_internal(*handle);
1274 let new_pos = parent_pos * co.parent.as_ref().unwrap().pos_wrt_parent;
1275
1276 modified_colliders.push_once(*handle, co);
1279
1280 co.changes |= ColliderChanges::POSITION;
1281 co.pos = ColliderPosition(new_pos);
1282 }
1283 }
1284}
1285
1286#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1287#[derive(Default, Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1288pub struct RigidBodyDominance(pub i8);
1290
1291impl RigidBodyDominance {
1292 pub fn effective_group(&self, status: &RigidBodyType) -> i16 {
1294 if status.is_dynamic_or_kinematic() {
1295 self.0 as i16
1296 } else {
1297 i8::MAX as i16 + 1
1298 }
1299 }
1300}
1301
1302#[derive(Copy, Clone, Debug, PartialEq)]
1320#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1321pub struct RigidBodyActivation {
1322 pub normalized_linear_threshold: Real,
1328
1329 pub angular_threshold: Real,
1335
1336 pub time_until_sleep: Real,
1340
1341 pub time_since_can_sleep: Real,
1343
1344 pub sleeping: bool,
1346
1347 pub(crate) sleep_prev_pose: Pose,
1350}
1351
1352impl Default for RigidBodyActivation {
1353 fn default() -> Self {
1354 Self::active()
1355 }
1356}
1357
1358impl RigidBodyActivation {
1359 pub fn default_normalized_linear_threshold() -> Real {
1363 0.05
1364 }
1365
1366 pub fn default_angular_threshold() -> Real {
1368 0.5
1369 }
1370
1371 pub fn default_time_until_sleep() -> Real {
1376 0.5
1377 }
1378
1379 pub fn active() -> Self {
1381 RigidBodyActivation {
1382 normalized_linear_threshold: Self::default_normalized_linear_threshold(),
1383 angular_threshold: Self::default_angular_threshold(),
1384 time_until_sleep: Self::default_time_until_sleep(),
1385 time_since_can_sleep: 0.0,
1386 sleeping: false,
1387 sleep_prev_pose: Pose::IDENTITY,
1388 }
1389 }
1390
1391 pub fn inactive() -> Self {
1393 RigidBodyActivation {
1394 normalized_linear_threshold: Self::default_normalized_linear_threshold(),
1395 angular_threshold: Self::default_angular_threshold(),
1396 time_until_sleep: Self::default_time_until_sleep(),
1397 time_since_can_sleep: Self::default_time_until_sleep(),
1398 sleeping: true,
1399 sleep_prev_pose: Pose::IDENTITY,
1400 }
1401 }
1402
1403 pub fn cannot_sleep() -> Self {
1405 RigidBodyActivation {
1406 normalized_linear_threshold: -1.0,
1407 angular_threshold: -1.0,
1408 ..Self::active()
1409 }
1410 }
1411
1412 #[inline]
1414 pub fn is_active(&self) -> bool {
1415 !self.sleeping
1416 }
1417
1418 #[inline]
1420 pub fn wake_up(&mut self, strong: bool) {
1421 self.sleeping = false;
1422
1423 if strong {
1424 self.time_since_can_sleep = 0.0;
1425 }
1426 }
1427
1428 #[inline]
1430 pub fn sleep(&mut self) {
1431 self.sleeping = true;
1432 self.time_since_can_sleep = self.time_until_sleep;
1433 }
1434
1435 pub fn is_eligible_for_sleep(&self) -> bool {
1438 self.time_since_can_sleep >= self.time_until_sleep
1439 }
1440
1441 pub(crate) fn update_energy(
1442 &mut self,
1443 body_type: RigidBodyType,
1444 length_unit: Real,
1445 sq_linvel: Real,
1446 sq_angvel: Real,
1447 max_extent: Real,
1448 pose: &Pose,
1449 dt: Real,
1450 ) {
1451 if self.sleeping {
1455 self.time_since_can_sleep = self.time_until_sleep;
1456 return;
1457 }
1458
1459 let can_sleep = match body_type {
1460 RigidBodyType::SoftFrame => {
1464 let linear_threshold = self.normalized_linear_threshold * length_unit;
1465 self.normalized_linear_threshold >= 0.0
1466 && sq_linvel * 0.25 < linear_threshold * linear_threshold
1467 }
1468 RigidBodyType::Dynamic => {
1469 let linear_threshold = self.normalized_linear_threshold * length_unit;
1470 let prev_pose = core::mem::replace(&mut self.sleep_prev_pose, *pose);
1471 let angular_ok = if max_extent > 0.0 {
1472 use crate::num::FloatConst;
1473 self.angular_threshold >= 0.0
1478 && sq_angvel < Real::FRAC_PI_2() * Real::FRAC_PI_2()
1479 } else {
1480 sq_angvel < self.angular_threshold * self.angular_threshold.abs()
1484 };
1485
1486 let drift = crate::geometry::relative_pose_drift(&prev_pose, pose, max_extent);
1487 angular_ok && drift * 0.5 < linear_threshold * dt
1488 }
1489 RigidBodyType::KinematicPositionBased | RigidBodyType::KinematicVelocityBased => {
1490 sq_linvel == 0.0 && sq_angvel == 0.0
1493 }
1494 RigidBodyType::Fixed => true,
1495 };
1496
1497 if can_sleep {
1498 self.time_since_can_sleep += dt;
1499 } else {
1500 self.time_since_can_sleep = 0.0;
1501 }
1502 }
1503}
1504
1505#[cfg(test)]
1506mod tests {
1507 use super::*;
1508 use crate::math::Real;
1509
1510 #[test]
1511 fn heavy_body_under_gravity_is_not_moving_fast() {
1512 let (dt, mass, extent) = (1.0 / 60.0, 1000.0, 0.5);
1513 #[cfg(feature = "dim2")]
1514 let local_mprops = MassProperties::new(Vector::ZERO, mass, 1.0);
1515 #[cfg(feature = "dim3")]
1516 let local_mprops = MassProperties::new(Vector::ZERO, mass, Vector::splat(1.0));
1517 let mut mprops = RigidBodyMassProps::from(local_mprops);
1518 mprops.update_world_mass_properties(RigidBodyType::Dynamic, &Pose::default());
1519 let mut forces = RigidBodyForces::default();
1520 forces.compute_effective_force_and_torque(Vector::Y * -9.81, Vector::splat(mass));
1521 let ccd = RigidBodyCcd {
1522 ccd_thickness: extent,
1523 ..Default::default()
1524 };
1525
1526 let resting = RigidBodyVelocity::default();
1528 assert!(!ccd.is_moving_fast(dt, &resting, Some((&forces, &mprops)), extent));
1529
1530 let fast = RigidBodyVelocity {
1531 linvel: Vector::X * 60.0,
1532 ..Default::default()
1533 };
1534 assert!(ccd.is_moving_fast(dt, &fast, Some((&forces, &mprops)), extent));
1535 }
1536
1537 #[test]
1538 fn test_interpolate_velocity() {
1539 #[cfg(feature = "f32")]
1542 let mut rng = oorandom::Rand32::new(0);
1543 #[cfg(feature = "f64")]
1544 let mut rng = oorandom::Rand64::new(0);
1545
1546 for i in -10..=10 {
1547 let mult = i as Real;
1548 let (local_com, curr_pos, next_pos);
1549 #[cfg(feature = "dim2")]
1550 {
1551 local_com = Vector::new(rng.rand_float(), rng.rand_float());
1552 curr_pos = Pose::new(
1553 Vector::new(rng.rand_float(), rng.rand_float()) * mult,
1554 rng.rand_float(),
1555 );
1556 next_pos = Pose::new(
1557 Vector::new(rng.rand_float(), rng.rand_float()) * mult,
1558 rng.rand_float(),
1559 );
1560 }
1561 #[cfg(feature = "dim3")]
1562 {
1563 local_com = Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float());
1564 curr_pos = Pose::new(
1565 Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()) * mult,
1566 Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()),
1567 );
1568 next_pos = Pose::new(
1569 Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()) * mult,
1570 Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()),
1571 );
1572 }
1573
1574 let dt = 0.016;
1575 let rb_pos = RigidBodyPosition {
1576 position: curr_pos,
1577 next_position: next_pos,
1578 };
1579 let vel = rb_pos.interpolate_velocity(1.0 / dt, local_com);
1580 let interp_pos = vel.integrate(dt, &curr_pos, &local_com);
1581 approx::assert_relative_eq!(interp_pos, next_pos, epsilon = 1.0e-5);
1582 }
1583 }
1584
1585 fn creep(shift_per_step: Real, steps: usize, dt: Real) -> RigidBodyActivation {
1588 let mut activation = RigidBodyActivation::active();
1589 let mut shift = 0.0;
1590
1591 for _ in 0..steps {
1592 shift += shift_per_step;
1593 let pose = Pose::from_translation(Vector::X * shift);
1594 activation.update_energy(RigidBodyType::Dynamic, 1.0, 0.0, 0.0, 1.0, &pose, dt);
1595 }
1596
1597 activation
1598 }
1599
1600 fn pinned_with_velocity(linvel: Real, steps: usize, dt: Real) -> RigidBodyActivation {
1603 let mut activation = RigidBodyActivation::active();
1604 let pose = Pose::from_translation(Vector::X * 3.0);
1605
1606 for _ in 0..steps {
1607 activation.update_energy(
1608 RigidBodyType::Dynamic,
1609 1.0,
1610 linvel * linvel,
1611 0.0,
1612 1.0,
1613 &pose,
1614 dt,
1615 );
1616 }
1617
1618 activation
1619 }
1620
1621 #[test]
1622 fn test_sleep_allows_pinned_body_with_residual_velocity() {
1623 let dt = 1.0 / 60.0;
1624 let threshold = RigidBodyActivation::default_normalized_linear_threshold();
1625 let steps = (10.0 * RigidBodyActivation::default_time_until_sleep() / dt) as usize;
1626
1627 assert!(pinned_with_velocity(threshold * 5.0, steps, dt).is_eligible_for_sleep());
1630 }
1631
1632 #[test]
1633 fn test_sleep_gates_position_corrections() {
1634 let dt = 1.0 / 60.0;
1635 let budget = 2.0 * RigidBodyActivation::default_normalized_linear_threshold() * dt;
1637 let steps = (10.0 * RigidBodyActivation::default_time_until_sleep() / dt) as usize;
1638
1639 assert!(!creep(budget * 1.5, steps, dt).is_eligible_for_sleep());
1641
1642 assert!(creep(budget * 0.5, steps, dt).is_eligible_for_sleep());
1644 assert!(creep(0.0, steps, dt).is_eligible_for_sleep());
1645 }
1646}