1#![allow(clippy::bad_bit_mask)] #![allow(clippy::unnecessary_cast)] #[cfg(feature = "alloc")]
5use crate::dynamics::RigidBody;
6use crate::dynamics::integration_parameters::SpringCoefficients;
7#[cfg(feature = "alloc")]
8use crate::dynamics::solver::MotorParameters;
9use crate::dynamics::{FixedJoint, MotorModel, PrismaticJoint, RevoluteJoint, RopeJoint};
10use crate::math::{Pose, Real, Rotation, SPATIAL_DIM, Vector};
11#[cfg(feature = "dim2")]
12use crate::utils::OrthonormalBasis;
13use crate::utils::SimdRealCopy;
14#[cfg(feature = "dim2")]
15use parry::math::Matrix;
16
17#[cfg(feature = "dim3")]
18use crate::dynamics::SphericalJoint;
19
20#[cfg(feature = "dim3")]
21bitflags::bitflags! {
22 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
24 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
25 pub struct JointAxesMask: u8 {
26 const LIN_X = 1 << 0;
28 const LIN_Y = 1 << 1;
30 const LIN_Z = 1 << 2;
32 const ANG_X = 1 << 3;
34 const ANG_Y = 1 << 4;
36 const ANG_Z = 1 << 5;
38 const LOCKED_REVOLUTE_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::LIN_Z.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
40 const LOCKED_PRISMATIC_AXES = Self::LIN_Y.bits() | Self::LIN_Z.bits() | Self::ANG_X.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
42 const LOCKED_FIXED_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::LIN_Z.bits() | Self::ANG_X.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
44 const LOCKED_SPHERICAL_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::LIN_Z.bits();
46 const FREE_REVOLUTE_AXES = Self::ANG_X.bits();
48 const FREE_PRISMATIC_AXES = Self::LIN_X.bits();
50 const FREE_FIXED_AXES = 0;
52 const FREE_SPHERICAL_AXES = Self::ANG_X.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
54 const LIN_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::LIN_Z.bits();
56 const ANG_AXES = Self::ANG_X.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
58 }
59}
60
61#[cfg(feature = "dim2")]
62bitflags::bitflags! {
63 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
65 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
66 pub struct JointAxesMask: u8 {
67 const LIN_X = 1 << 0;
69 const LIN_Y = 1 << 1;
71 const ANG_X = 1 << 2;
73 const LOCKED_REVOLUTE_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits();
75 const LOCKED_PRISMATIC_AXES = Self::LIN_Y.bits() | Self::ANG_X.bits();
77 const LOCKED_PIN_SLOT_AXES = Self::LIN_Y.bits();
79 const LOCKED_FIXED_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::ANG_X.bits();
81 const FREE_REVOLUTE_AXES = Self::ANG_X.bits();
83 const FREE_PRISMATIC_AXES = Self::LIN_X.bits();
85 const FREE_FIXED_AXES = 0;
87 const LIN_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits();
89 const ANG_AXES = Self::ANG_X.bits();
91 }
92}
93
94impl Default for JointAxesMask {
95 fn default() -> Self {
96 Self::empty()
97 }
98}
99
100#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
102#[derive(Copy, Clone, Debug, PartialEq)]
103pub enum JointAxis {
104 LinX = 0,
106 LinY,
108 #[cfg(feature = "dim3")]
110 LinZ,
111 AngX,
113 #[cfg(feature = "dim3")]
115 AngY,
116 #[cfg(feature = "dim3")]
118 AngZ,
119}
120
121impl From<JointAxis> for JointAxesMask {
122 fn from(axis: JointAxis) -> Self {
123 JointAxesMask::from_bits(1 << axis as usize).unwrap()
124 }
125}
126
127#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
141#[derive(Copy, Clone, Debug, PartialEq)]
142pub struct JointLimits<N> {
143 pub min: N,
145 pub max: N,
147 pub impulse: N,
149}
150
151impl<N: SimdRealCopy> Default for JointLimits<N> {
152 fn default() -> Self {
153 Self {
154 min: -N::splat(Real::MAX),
155 max: N::splat(Real::MAX),
156 impulse: N::splat(0.0),
157 }
158 }
159}
160
161impl<N: SimdRealCopy> From<[N; 2]> for JointLimits<N> {
162 fn from(value: [N; 2]) -> Self {
163 Self {
164 min: value[0],
165 max: value[1],
166 impulse: N::splat(0.0),
167 }
168 }
169}
170
171#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
202#[derive(Copy, Clone, Debug, PartialEq)]
203pub struct JointMotor {
204 pub target_vel: Real,
206 pub target_pos: Real,
208 pub stiffness: Real,
210 pub damping: Real,
212 pub max_force: Real,
214 pub impulse: Real,
216 pub model: MotorModel,
218}
219
220impl Default for JointMotor {
221 fn default() -> Self {
222 Self {
223 target_pos: 0.0,
224 target_vel: 0.0,
225 stiffness: 0.0,
226 damping: 0.0,
227 max_force: Real::MAX,
228 impulse: 0.0,
229 model: MotorModel::AccelerationBased,
230 }
231 }
232}
233
234#[cfg(feature = "alloc")]
235impl JointMotor {
236 pub(crate) fn motor_params(&self, dt: Real) -> MotorParameters<Real> {
237 let (erp_inv_dt, cfm_coeff, cfm_gain) =
238 self.model
239 .combine_coefficients(dt, self.stiffness, self.damping);
240 MotorParameters {
241 erp_inv_dt,
242 cfm_coeff,
243 cfm_gain,
244 target_pos: self.target_pos,
246 target_vel: self.target_vel,
247 max_impulse: Self::max_impulse(self.max_force, dt),
248 }
249 }
250
251 pub(crate) fn max_impulse(max_force: Real, dt: Real) -> Real {
256 if dt == 0.0 { 0.0 } else { max_force * dt }
257 }
258}
259
260#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
261#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
262pub enum JointEnabled {
264 Enabled,
266 DisabledByAttachedBody,
269 Disabled,
271}
272
273#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
274#[derive(Copy, Clone, Debug, PartialEq)]
275pub struct GenericJoint {
277 pub local_frame1: Pose,
279 pub local_frame2: Pose,
281 pub locked_axes: JointAxesMask,
283 pub limit_axes: JointAxesMask,
285 pub motor_axes: JointAxesMask,
287 pub coupled_axes: JointAxesMask,
294 pub limits: [JointLimits<Real>; SPATIAL_DIM],
300 pub motors: [JointMotor; SPATIAL_DIM],
306 pub softness: SpringCoefficients<Real>,
308 pub contacts_enabled: bool,
310 pub enabled: JointEnabled,
312 pub user_data: u128,
314}
315
316impl Default for GenericJoint {
317 fn default() -> Self {
318 Self {
319 local_frame1: Pose::IDENTITY,
320 local_frame2: Pose::IDENTITY,
321 locked_axes: JointAxesMask::empty(),
322 limit_axes: JointAxesMask::empty(),
323 motor_axes: JointAxesMask::empty(),
324 coupled_axes: JointAxesMask::empty(),
325 limits: [JointLimits::default(); SPATIAL_DIM],
326 motors: [JointMotor::default(); SPATIAL_DIM],
327 softness: SpringCoefficients::joint_defaults(),
328 contacts_enabled: true,
329 enabled: JointEnabled::Enabled,
330 user_data: 0,
331 }
332 }
333}
334
335impl GenericJoint {
336 #[must_use]
338 pub fn new(locked_axes: JointAxesMask) -> Self {
339 *Self::default().lock_axes(locked_axes)
340 }
341
342 #[cfg(feature = "alloc")]
349 pub(crate) fn supports_simd_constraints(&self) -> bool {
350 #[cfg(feature = "dim2")]
351 let motors_ok =
352 (self.motor_axes.bits() & !self.locked_axes.bits() & JointAxesMask::LIN_AXES.bits())
353 == 0;
354 #[cfg(feature = "dim3")]
355 let motors_ok = (self.motor_axes.bits() & !self.locked_axes.bits()) == 0;
356 motors_ok && (self.limit_axes & self.coupled_axes).is_empty()
357 }
358
359 #[cfg(feature = "alloc")]
363 pub(crate) fn simd_row_signature(&self) -> u32 {
364 let locked = self.locked_axes.bits() as u32;
365 let limits = (self.limit_axes.bits() & !self.locked_axes.bits()) as u32;
366 #[cfg(feature = "dim2")]
367 {
368 let motors = (self.motor_axes.bits() & !self.locked_axes.bits()) as u32;
371 let model = (self.motors[crate::math::DIM].model
372 == crate::dynamics::MotorModel::ForceBased) as u32;
373 locked | (limits << 8) | (motors << 16) | (model << 24)
374 }
375 #[cfg(feature = "dim3")]
376 {
377 locked | (limits << 8)
378 }
379 }
380
381 #[doc(hidden)]
382 pub fn complete_ang_frame(axis: Vector) -> Rotation {
383 #[cfg(feature = "dim2")]
384 {
385 let basis = axis.orthonormal_basis();
386 let mat = Matrix::from_cols(axis, basis[0]);
387 Rotation::from_matrix_unchecked(mat)
388 }
389
390 #[cfg(feature = "dim3")]
391 {
392 Rotation::from_rotation_arc(Vector::X, axis)
396 }
397 }
398
399 pub fn is_enabled(&self) -> bool {
401 self.enabled == JointEnabled::Enabled
402 }
403
404 pub fn set_enabled(&mut self, enabled: bool) {
406 match self.enabled {
407 JointEnabled::Enabled | JointEnabled::DisabledByAttachedBody => {
408 if !enabled {
409 self.enabled = JointEnabled::Disabled;
410 }
411 }
412 JointEnabled::Disabled => {
413 if enabled {
414 self.enabled = JointEnabled::Enabled;
415 }
416 }
417 }
418 }
419
420 pub fn lock_axes(&mut self, axes: JointAxesMask) -> &mut Self {
422 self.locked_axes |= axes;
423 self
424 }
425
426 pub fn set_local_frame1(&mut self, local_frame: Pose) -> &mut Self {
428 self.local_frame1 = local_frame;
429 self
430 }
431
432 pub fn set_local_frame2(&mut self, local_frame: Pose) -> &mut Self {
434 self.local_frame2 = local_frame;
435 self
436 }
437
438 #[must_use]
440 pub fn local_axis1(&self) -> Vector {
441 self.local_frame1 * Vector::X
442 }
443
444 pub fn set_local_axis1(&mut self, local_axis: Vector) -> &mut Self {
451 self.local_frame1.rotation = Self::complete_ang_frame(local_axis);
452 self
453 }
454
455 #[must_use]
457 pub fn local_axis2(&self) -> Vector {
458 self.local_frame2 * Vector::X
459 }
460
461 pub fn set_local_axis2(&mut self, local_axis: Vector) -> &mut Self {
468 self.local_frame2.rotation = Self::complete_ang_frame(local_axis);
469 self
470 }
471
472 #[must_use]
474 pub fn local_anchor1(&self) -> Vector {
475 self.local_frame1.translation
476 }
477
478 pub fn set_local_anchor1(&mut self, anchor1: Vector) -> &mut Self {
480 self.local_frame1.translation = anchor1;
481 self
482 }
483
484 #[must_use]
486 pub fn local_anchor2(&self) -> Vector {
487 self.local_frame2.translation
488 }
489
490 pub fn set_local_anchor2(&mut self, anchor2: Vector) -> &mut Self {
492 self.local_frame2.translation = anchor2;
493 self
494 }
495
496 pub fn contacts_enabled(&self) -> bool {
498 self.contacts_enabled
499 }
500
501 pub fn set_contacts_enabled(&mut self, enabled: bool) -> &mut Self {
503 self.contacts_enabled = enabled;
504 self
505 }
506
507 #[must_use]
509 pub fn set_softness(&mut self, softness: SpringCoefficients<Real>) -> &mut Self {
510 self.softness = softness;
511 self
512 }
513
514 #[must_use]
516 pub fn limits(&self, axis: JointAxis) -> Option<&JointLimits<Real>> {
517 let i = axis as usize;
518 if self.limit_axes.contains(axis.into()) {
519 Some(&self.limits[i])
520 } else {
521 None
522 }
523 }
524
525 pub fn set_limits(&mut self, axis: JointAxis, limits: [Real; 2]) -> &mut Self {
527 let i = axis as usize;
528 self.limit_axes |= axis.into();
529 self.limits[i].min = limits[0];
530 self.limits[i].max = limits[1];
531 self
532 }
533
534 #[must_use]
536 pub fn motor_model(&self, axis: JointAxis) -> Option<MotorModel> {
537 let i = axis as usize;
538 if self.motor_axes.contains(axis.into()) {
539 Some(self.motors[i].model)
540 } else {
541 None
542 }
543 }
544
545 pub fn set_motor_model(&mut self, axis: JointAxis, model: MotorModel) -> &mut Self {
547 self.motors[axis as usize].model = model;
548 self
549 }
550
551 pub fn set_motor_velocity(
553 &mut self,
554 axis: JointAxis,
555 target_vel: Real,
556 factor: Real,
557 ) -> &mut Self {
558 self.set_motor(
559 axis,
560 self.motors[axis as usize].target_pos,
561 target_vel,
562 0.0,
563 factor,
564 )
565 }
566
567 pub fn set_motor_position(
569 &mut self,
570 axis: JointAxis,
571 target_pos: Real,
572 stiffness: Real,
573 damping: Real,
574 ) -> &mut Self {
575 self.set_motor(axis, target_pos, 0.0, stiffness, damping)
576 }
577
578 pub fn set_motor_max_force(&mut self, axis: JointAxis, max_force: Real) -> &mut Self {
580 self.motors[axis as usize].max_force = max_force;
581 self
582 }
583
584 #[must_use]
586 pub fn motor(&self, axis: JointAxis) -> Option<&JointMotor> {
587 let i = axis as usize;
588 if self.motor_axes.contains(axis.into()) {
589 Some(&self.motors[i])
590 } else {
591 None
592 }
593 }
594
595 pub fn set_motor(
597 &mut self,
598 axis: JointAxis,
599 target_pos: Real,
600 target_vel: Real,
601 stiffness: Real,
602 damping: Real,
603 ) -> &mut Self {
604 self.motor_axes |= axis.into();
605 let i = axis as usize;
606 self.motors[i].target_vel = target_vel;
607 self.motors[i].target_pos = target_pos;
608 self.motors[i].stiffness = stiffness;
609 self.motors[i].damping = damping;
610 self
611 }
612
613 pub fn flip(&mut self) {
615 core::mem::swap(&mut self.local_frame1, &mut self.local_frame2);
616
617 let coupled_bits = self.coupled_axes.bits();
618
619 for dim in 0..SPATIAL_DIM {
620 if coupled_bits & (1 << dim) == 0 {
621 let limit = self.limits[dim];
622 self.limits[dim].min = -limit.max;
623 self.limits[dim].max = -limit.min;
624 }
625
626 self.motors[dim].target_vel = -self.motors[dim].target_vel;
627 self.motors[dim].target_pos = -self.motors[dim].target_pos;
628 }
629 }
630
631 #[cfg(feature = "alloc")]
632 pub(crate) fn transform_to_solver_body_space(&mut self, rb1: &RigidBody, rb2: &RigidBody) {
633 if rb1.is_fixed() {
634 self.local_frame1 = rb1.pos.position * self.local_frame1;
635 } else {
636 self.local_frame1.translation -= rb1.mprops.local_mprops.local_com;
637 }
638
639 if rb2.is_fixed() {
640 self.local_frame2 = rb2.pos.position * self.local_frame2;
641 } else {
642 self.local_frame2.translation -= rb2.mprops.local_mprops.local_com;
643 }
644 }
645}
646
647macro_rules! joint_conversion_methods(
648 ($as_joint: ident, $as_joint_mut: ident, $Joint: ty, $axes: expr) => {
649 #[must_use]
651 pub fn $as_joint(&self) -> Option<&$Joint> {
652 if self.locked_axes == $axes {
653 Some(unsafe { core::mem::transmute::<&Self, &$Joint>(self) })
656 } else {
657 None
658 }
659 }
660
661 #[must_use]
663 pub fn $as_joint_mut(&mut self) -> Option<&mut $Joint> {
664 if self.locked_axes == $axes {
665 Some(unsafe { core::mem::transmute::<&mut Self, &mut $Joint>(self) })
668 } else {
669 None
670 }
671 }
672 }
673);
674
675impl GenericJoint {
676 joint_conversion_methods!(
677 as_revolute,
678 as_revolute_mut,
679 RevoluteJoint,
680 JointAxesMask::LOCKED_REVOLUTE_AXES
681 );
682 joint_conversion_methods!(
683 as_fixed,
684 as_fixed_mut,
685 FixedJoint,
686 JointAxesMask::LOCKED_FIXED_AXES
687 );
688 joint_conversion_methods!(
689 as_prismatic,
690 as_prismatic_mut,
691 PrismaticJoint,
692 JointAxesMask::LOCKED_PRISMATIC_AXES
693 );
694 joint_conversion_methods!(
695 as_rope,
696 as_rope_mut,
697 RopeJoint,
698 JointAxesMask::FREE_FIXED_AXES
699 );
700
701 #[cfg(feature = "dim3")]
702 joint_conversion_methods!(
703 as_spherical,
704 as_spherical_mut,
705 SphericalJoint,
706 JointAxesMask::LOCKED_SPHERICAL_AXES
707 );
708}
709
710#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
712#[derive(Copy, Clone, Debug, PartialEq)]
713pub struct GenericJointBuilder(pub GenericJoint);
714
715impl GenericJointBuilder {
716 #[must_use]
718 pub fn new(locked_axes: JointAxesMask) -> Self {
719 Self(GenericJoint::new(locked_axes))
720 }
721
722 #[must_use]
724 pub fn locked_axes(mut self, axes: JointAxesMask) -> Self {
725 self.0.locked_axes = axes;
726 self
727 }
728
729 #[must_use]
731 pub fn contacts_enabled(mut self, enabled: bool) -> Self {
732 self.0.contacts_enabled = enabled;
733 self
734 }
735
736 #[must_use]
738 pub fn local_frame1(mut self, local_frame: Pose) -> Self {
739 self.0.set_local_frame1(local_frame);
740 self
741 }
742
743 #[must_use]
745 pub fn local_frame2(mut self, local_frame: Pose) -> Self {
746 self.0.set_local_frame2(local_frame);
747 self
748 }
749
750 #[must_use]
752 pub fn local_axis1(mut self, local_axis: Vector) -> Self {
753 self.0.set_local_axis1(local_axis);
754 self
755 }
756
757 #[must_use]
759 pub fn local_axis2(mut self, local_axis: Vector) -> Self {
760 self.0.set_local_axis2(local_axis);
761 self
762 }
763
764 #[must_use]
766 pub fn local_anchor1(mut self, anchor1: Vector) -> Self {
767 self.0.set_local_anchor1(anchor1);
768 self
769 }
770
771 #[must_use]
773 pub fn local_anchor2(mut self, anchor2: Vector) -> Self {
774 self.0.set_local_anchor2(anchor2);
775 self
776 }
777
778 #[must_use]
780 pub fn limits(mut self, axis: JointAxis, limits: [Real; 2]) -> Self {
781 self.0.set_limits(axis, limits);
782 self
783 }
784
785 #[must_use]
787 pub fn coupled_axes(mut self, axes: JointAxesMask) -> Self {
788 self.0.coupled_axes = axes;
789 self
790 }
791
792 #[must_use]
794 pub fn motor_model(mut self, axis: JointAxis, model: MotorModel) -> Self {
795 self.0.set_motor_model(axis, model);
796 self
797 }
798
799 #[must_use]
801 pub fn motor_velocity(mut self, axis: JointAxis, target_vel: Real, factor: Real) -> Self {
802 self.0.set_motor_velocity(axis, target_vel, factor);
803 self
804 }
805
806 #[must_use]
808 pub fn motor_position(
809 mut self,
810 axis: JointAxis,
811 target_pos: Real,
812 stiffness: Real,
813 damping: Real,
814 ) -> Self {
815 self.0
816 .set_motor_position(axis, target_pos, stiffness, damping);
817 self
818 }
819
820 #[must_use]
822 pub fn set_motor(
823 mut self,
824 axis: JointAxis,
825 target_pos: Real,
826 target_vel: Real,
827 stiffness: Real,
828 damping: Real,
829 ) -> Self {
830 self.0
831 .set_motor(axis, target_pos, target_vel, stiffness, damping);
832 self
833 }
834
835 #[must_use]
837 pub fn motor_max_force(mut self, axis: JointAxis, max_force: Real) -> Self {
838 self.0.set_motor_max_force(axis, max_force);
839 self
840 }
841
842 #[must_use]
844 pub fn softness(mut self, softness: SpringCoefficients<Real>) -> Self {
845 self.0.softness = softness;
846 self
847 }
848
849 pub fn user_data(mut self, data: u128) -> Self {
851 self.0.user_data = data;
852 self
853 }
854
855 #[must_use]
857 pub fn build(self) -> GenericJoint {
858 self.0
859 }
860}
861
862impl From<GenericJointBuilder> for GenericJoint {
863 fn from(val: GenericJointBuilder) -> GenericJoint {
864 val.0
865 }
866}
867
868#[cfg(all(test, feature = "alloc"))]
869mod test {
870 use super::JointMotor;
871 use crate::math::Real;
872
873 #[test]
874 fn infinite_motor_force_with_zero_dt_has_finite_impulse_bounds() {
875 let motor = JointMotor {
876 max_force: Real::INFINITY,
877 target_vel: 1.0,
878 damping: 1.0,
879 ..Default::default()
880 };
881 assert_eq!(motor.motor_params(0.0).max_impulse, 0.0);
882 assert_eq!(motor.motor_params(1.0 / 60.0).max_impulse, Real::INFINITY);
883
884 let motor = JointMotor::default();
885 assert_eq!(motor.motor_params(0.0).max_impulse, 0.0);
886 assert_eq!(motor.motor_params(0.5).max_impulse, Real::MAX * 0.5);
887 }
888}