Skip to main content

rapier2d/dynamics/joint/
generic_joint.rs

1#![allow(clippy::bad_bit_mask)] // Clippy will complain about the bitmasks due to JointAxesMask::FREE_FIXED_AXES being 0.
2#![allow(clippy::unnecessary_cast)] // Casts are needed for switching between f32/f64.
3
4use crate::dynamics::integration_parameters::SpringCoefficients;
5use crate::dynamics::solver::MotorParameters;
6use crate::dynamics::{
7    FixedJoint, MotorModel, PrismaticJoint, RevoluteJoint, RigidBody, RopeJoint,
8};
9use crate::math::{Pose, Real, Rotation, SPATIAL_DIM, Vector};
10use crate::utils::{OrthonormalBasis, SimdRealCopy};
11use parry::math::Matrix;
12
13#[cfg(feature = "dim3")]
14use crate::dynamics::SphericalJoint;
15
16#[cfg(feature = "dim3")]
17bitflags::bitflags! {
18    /// A bit mask identifying multiple degrees of freedom of a joint.
19    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
20    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
21    pub struct JointAxesMask: u8 {
22        /// The linear (translational) degree of freedom along the local X axis of a joint.
23        const LIN_X = 1 << 0;
24        /// The linear (translational) degree of freedom along the local Y axis of a joint.
25        const LIN_Y = 1 << 1;
26        /// The linear (translational) degree of freedom along the local Z axis of a joint.
27        const LIN_Z = 1 << 2;
28        /// The angular degree of freedom along the local X axis of a joint.
29        const ANG_X = 1 << 3;
30        /// The angular degree of freedom along the local Y axis of a joint.
31        const ANG_Y = 1 << 4;
32        /// The angular degree of freedom along the local Z axis of a joint.
33        const ANG_Z = 1 << 5;
34        /// The set of degrees of freedom locked by a revolute joint.
35        const LOCKED_REVOLUTE_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::LIN_Z.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
36        /// The set of degrees of freedom locked by a prismatic joint.
37        const LOCKED_PRISMATIC_AXES = Self::LIN_Y.bits() | Self::LIN_Z.bits() | Self::ANG_X.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
38        /// The set of degrees of freedom locked by a fixed joint.
39        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();
40        /// The set of degrees of freedom locked by a spherical joint.
41        const LOCKED_SPHERICAL_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::LIN_Z.bits();
42        /// The set of degrees of freedom left free by a revolute joint.
43        const FREE_REVOLUTE_AXES = Self::ANG_X.bits();
44        /// The set of degrees of freedom left free by a prismatic joint.
45        const FREE_PRISMATIC_AXES = Self::LIN_X.bits();
46        /// The set of degrees of freedom left free by a fixed joint.
47        const FREE_FIXED_AXES = 0;
48        /// The set of degrees of freedom left free by a spherical joint.
49        const FREE_SPHERICAL_AXES = Self::ANG_X.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
50        /// The set of all translational degrees of freedom.
51        const LIN_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::LIN_Z.bits();
52        /// The set of all angular degrees of freedom.
53        const ANG_AXES = Self::ANG_X.bits() | Self::ANG_Y.bits() | Self::ANG_Z.bits();
54    }
55}
56
57#[cfg(feature = "dim2")]
58bitflags::bitflags! {
59    /// A bit mask identifying multiple degrees of freedom of a joint.
60    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
61    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
62    pub struct JointAxesMask: u8 {
63        /// The linear (translational) degree of freedom along the local X axis of a joint.
64        const LIN_X = 1 << 0;
65        /// The linear (translational) degree of freedom along the local Y axis of a joint.
66        const LIN_Y = 1 << 1;
67        /// The angular degree of freedom of a joint.
68        const ANG_X = 1 << 2;
69        /// The set of degrees of freedom locked by a revolute joint.
70        const LOCKED_REVOLUTE_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits();
71        /// The set of degrees of freedom locked by a prismatic joint.
72        const LOCKED_PRISMATIC_AXES = Self::LIN_Y.bits() | Self::ANG_X.bits();
73        /// The set of degrees of freedom locked by a pin slot joint.
74        const LOCKED_PIN_SLOT_AXES = Self::LIN_Y.bits();
75        /// The set of degrees of freedom locked by a fixed joint.
76        const LOCKED_FIXED_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits() | Self::ANG_X.bits();
77        /// The set of degrees of freedom left free by a revolute joint.
78        const FREE_REVOLUTE_AXES = Self::ANG_X.bits();
79        /// The set of degrees of freedom left free by a prismatic joint.
80        const FREE_PRISMATIC_AXES = Self::LIN_X.bits();
81        /// The set of degrees of freedom left free by a fixed joint.
82        const FREE_FIXED_AXES = 0;
83        /// The set of all translational degrees of freedom.
84        const LIN_AXES = Self::LIN_X.bits() | Self::LIN_Y.bits();
85        /// The set of all angular degrees of freedom.
86        const ANG_AXES = Self::ANG_X.bits();
87    }
88}
89
90impl Default for JointAxesMask {
91    fn default() -> Self {
92        Self::empty()
93    }
94}
95
96/// Identifiers of degrees of freedoms of a joint.
97#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
98#[derive(Copy, Clone, Debug, PartialEq)]
99pub enum JointAxis {
100    /// The linear (translational) degree of freedom along the joint’s local X axis.
101    LinX = 0,
102    /// The linear (translational) degree of freedom along the joint’s local Y axis.
103    LinY,
104    /// The linear (translational) degree of freedom along the joint’s local Z axis.
105    #[cfg(feature = "dim3")]
106    LinZ,
107    /// The rotational degree of freedom along the joint’s local X axis.
108    AngX,
109    /// The rotational degree of freedom along the joint’s local Y axis.
110    #[cfg(feature = "dim3")]
111    AngY,
112    /// The rotational degree of freedom along the joint’s local Z axis.
113    #[cfg(feature = "dim3")]
114    AngZ,
115}
116
117impl From<JointAxis> for JointAxesMask {
118    fn from(axis: JointAxis) -> Self {
119        JointAxesMask::from_bits(1 << axis as usize).unwrap()
120    }
121}
122
123/// Limits that restrict a joint's range of motion along one axis.
124///
125/// Use to constrain how far a joint can move/rotate. Examples:
126/// - Door that only opens 90°: revolute joint with limits `[0.0, PI/2.0]`
127/// - Piston with 2-unit stroke: prismatic joint with limits `[0.0, 2.0]`
128/// - Elbow that bends 0-150°: revolute joint with limits `[0.0, 5*PI/6]`
129///
130/// When a joint hits its limit, forces are applied to prevent further movement in that direction.
131#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
132#[derive(Copy, Clone, Debug, PartialEq)]
133pub struct JointLimits<N> {
134    /// Minimum allowed value (angle for revolute, distance for prismatic).
135    pub min: N,
136    /// Maximum allowed value (angle for revolute, distance for prismatic).
137    pub max: N,
138    /// Internal: impulse being applied to enforce the limit.
139    pub impulse: N,
140}
141
142impl<N: SimdRealCopy> Default for JointLimits<N> {
143    fn default() -> Self {
144        Self {
145            min: -N::splat(Real::MAX),
146            max: N::splat(Real::MAX),
147            impulse: N::splat(0.0),
148        }
149    }
150}
151
152impl<N: SimdRealCopy> From<[N; 2]> for JointLimits<N> {
153    fn from(value: [N; 2]) -> Self {
154        Self {
155            min: value[0],
156            max: value[1],
157            impulse: N::splat(0.0),
158        }
159    }
160}
161
162/// A powered motor that drives a joint toward a target position/velocity.
163///
164/// Motors add actuation to joints - they apply forces to make the joint move toward
165/// a desired state. Think of them as servos, electric motors, or hydraulic actuators.
166///
167/// ## Two control modes
168///
169/// 1. **Velocity control**: Set `target_vel` to make the motor spin/slide at constant speed
170/// 2. **Position control**: Set `target_pos` with `stiffness`/`damping` to reach a target angle/position
171///
172/// You can combine both for precise control.
173///
174/// ## Parameters
175///
176/// - `stiffness`: How strongly to pull toward target (spring constant)
177/// - `damping`: Resistance to motion (prevents oscillation)
178/// - `max_force`: Maximum force/torque the motor can apply
179///
180/// # Example
181/// ```
182/// # use rapier3d::prelude::*;
183/// # use rapier3d::dynamics::{RevoluteJoint, PrismaticJoint};
184/// # let mut revolute_joint = RevoluteJoint::new(Vector::X);
185/// # let mut prismatic_joint = PrismaticJoint::new(Vector::X);
186/// // Motor that spins a wheel at 10 rad/s
187/// revolute_joint.set_motor_velocity(10.0, 0.8);
188///
189/// // Motor that moves to position 5.0
190/// prismatic_joint.set_motor_position(5.0, 100.0, 10.0);  // stiffness=100, damping=10
191/// ```
192#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
193#[derive(Copy, Clone, Debug, PartialEq)]
194pub struct JointMotor {
195    /// Target velocity (units/sec for prismatic, rad/sec for revolute).
196    pub target_vel: Real,
197    /// Target position (units for prismatic, radians for revolute).
198    pub target_pos: Real,
199    /// Spring constant - how strongly to pull toward target position.
200    pub stiffness: Real,
201    /// Damping coefficient - resistance to motion (prevents oscillation).
202    pub damping: Real,
203    /// Maximum force the motor can apply (Newtons for prismatic, Nm for revolute).
204    pub max_force: Real,
205    /// Internal: current impulse being applied.
206    pub impulse: Real,
207    /// Force-based or acceleration-based motor model.
208    pub model: MotorModel,
209}
210
211impl Default for JointMotor {
212    fn default() -> Self {
213        Self {
214            target_pos: 0.0,
215            target_vel: 0.0,
216            stiffness: 0.0,
217            damping: 0.0,
218            max_force: Real::MAX,
219            impulse: 0.0,
220            model: MotorModel::AccelerationBased,
221        }
222    }
223}
224
225impl JointMotor {
226    pub(crate) fn motor_params(&self, dt: Real) -> MotorParameters<Real> {
227        let (erp_inv_dt, cfm_coeff, cfm_gain) =
228            self.model
229                .combine_coefficients(dt, self.stiffness, self.damping);
230        MotorParameters {
231            erp_inv_dt,
232            cfm_coeff,
233            cfm_gain,
234            // keep_lhs,
235            target_pos: self.target_pos,
236            target_vel: self.target_vel,
237            max_impulse: self.max_force * dt,
238        }
239    }
240}
241
242#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
243#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
244/// Enum indicating whether or not a joint is enabled.
245pub enum JointEnabled {
246    /// The joint is enabled.
247    Enabled,
248    /// The joint wasn’t disabled by the user explicitly but it is attached to
249    /// a disabled rigid-body.
250    DisabledByAttachedBody,
251    /// The joint is disabled by the user explicitly.
252    Disabled,
253}
254
255#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
256#[derive(Copy, Clone, Debug, PartialEq)]
257/// A generic joint.
258pub struct GenericJoint {
259    /// The joint’s frame, expressed in the first rigid-body’s local-space.
260    pub local_frame1: Pose,
261    /// The joint’s frame, expressed in the second rigid-body’s local-space.
262    pub local_frame2: Pose,
263    /// The degrees-of-freedoms locked by this joint.
264    pub locked_axes: JointAxesMask,
265    /// The degrees-of-freedoms limited by this joint.
266    pub limit_axes: JointAxesMask,
267    /// The degrees-of-freedoms motorised by this joint.
268    pub motor_axes: JointAxesMask,
269    /// The coupled degrees of freedom of this joint.
270    ///
271    /// Note that coupling degrees of freedoms (DoF) changes the interpretation of the coupled joint’s limits and motors.
272    /// If multiple linear DoF are limited/motorized, only the limits/motor configuration for the first
273    /// coupled linear DoF is applied to all coupled linear DoF. Similarly, if multiple angular DoF are limited/motorized
274    /// only the limits/motor configuration for the first coupled angular DoF is applied to all coupled angular DoF.
275    pub coupled_axes: JointAxesMask,
276    /// The limits, along each degree of freedoms of this joint.
277    ///
278    /// Note that the limit must also be explicitly enabled by the `limit_axes` bitmask.
279    /// For coupled degrees of freedoms (DoF), only the first linear (resp. angular) coupled DoF limit and `limit_axis`
280    /// bitmask is applied to the coupled linear (resp. angular) axes.
281    pub limits: [JointLimits<Real>; SPATIAL_DIM],
282    /// The motors, along each degree of freedoms of this joint.
283    ///
284    /// Note that the motor must also be explicitly enabled by the `motor_axes` bitmask.
285    /// For coupled degrees of freedoms (DoF), only the first linear (resp. angular) coupled DoF motor and `motor_axes`
286    /// bitmask is applied to the coupled linear (resp. angular) axes.
287    pub motors: [JointMotor; SPATIAL_DIM],
288    /// The coefficients controlling the joint constraints’ softness.
289    pub softness: SpringCoefficients<Real>,
290    /// Are contacts between the attached rigid-bodies enabled?
291    pub contacts_enabled: bool,
292    /// Whether the joint is enabled.
293    pub enabled: JointEnabled,
294    /// User-defined data associated to this joint.
295    pub user_data: u128,
296}
297
298impl Default for GenericJoint {
299    fn default() -> Self {
300        Self {
301            local_frame1: Pose::IDENTITY,
302            local_frame2: Pose::IDENTITY,
303            locked_axes: JointAxesMask::empty(),
304            limit_axes: JointAxesMask::empty(),
305            motor_axes: JointAxesMask::empty(),
306            coupled_axes: JointAxesMask::empty(),
307            limits: [JointLimits::default(); SPATIAL_DIM],
308            motors: [JointMotor::default(); SPATIAL_DIM],
309            softness: SpringCoefficients::joint_defaults(),
310            contacts_enabled: true,
311            enabled: JointEnabled::Enabled,
312            user_data: 0,
313        }
314    }
315}
316
317impl GenericJoint {
318    /// Creates a new generic joint that locks the specified degrees of freedom.
319    #[must_use]
320    pub fn new(locked_axes: JointAxesMask) -> Self {
321        *Self::default().lock_axes(locked_axes)
322    }
323
324    #[cfg(feature = "simd-is-enabled")]
325    /// Can this joint use SIMD-accelerated constraint formulations?
326    pub(crate) fn supports_simd_constraints(&self) -> bool {
327        self.limit_axes.is_empty() && self.motor_axes.is_empty()
328    }
329
330    #[doc(hidden)]
331    pub fn complete_ang_frame(axis: Vector) -> Rotation {
332        let basis = axis.orthonormal_basis();
333
334        #[cfg(feature = "dim2")]
335        {
336            let mat = Matrix::from_cols(axis, basis[0]);
337            Rotation::from_matrix_unchecked(mat)
338        }
339
340        #[cfg(feature = "dim3")]
341        {
342            let mat = Matrix::from_cols(axis, basis[0], basis[1]);
343            Rotation::from_mat3(&mat)
344        }
345    }
346
347    /// Is this joint enabled?
348    pub fn is_enabled(&self) -> bool {
349        self.enabled == JointEnabled::Enabled
350    }
351
352    /// Set whether this joint is enabled or not.
353    pub fn set_enabled(&mut self, enabled: bool) {
354        match self.enabled {
355            JointEnabled::Enabled | JointEnabled::DisabledByAttachedBody => {
356                if !enabled {
357                    self.enabled = JointEnabled::Disabled;
358                }
359            }
360            JointEnabled::Disabled => {
361                if enabled {
362                    self.enabled = JointEnabled::Enabled;
363                }
364            }
365        }
366    }
367
368    /// Add the specified axes to the set of axes locked by this joint.
369    pub fn lock_axes(&mut self, axes: JointAxesMask) -> &mut Self {
370        self.locked_axes |= axes;
371        self
372    }
373
374    /// Sets the joint’s frame, expressed in the first rigid-body’s local-space.
375    pub fn set_local_frame1(&mut self, local_frame: Pose) -> &mut Self {
376        self.local_frame1 = local_frame;
377        self
378    }
379
380    /// Sets the joint’s frame, expressed in the second rigid-body’s local-space.
381    pub fn set_local_frame2(&mut self, local_frame: Pose) -> &mut Self {
382        self.local_frame2 = local_frame;
383        self
384    }
385
386    /// The principal (local X) axis of this joint, expressed in the first rigid-body’s local-space.
387    #[must_use]
388    pub fn local_axis1(&self) -> Vector {
389        self.local_frame1 * Vector::X
390    }
391
392    /// Sets the principal (local X) axis of this joint, expressed in the first rigid-body’s local-space.
393    pub fn set_local_axis1(&mut self, local_axis: Vector) -> &mut Self {
394        self.local_frame1.rotation = Self::complete_ang_frame(local_axis);
395        self
396    }
397
398    /// The principal (local X) axis of this joint, expressed in the second rigid-body’s local-space.
399    #[must_use]
400    pub fn local_axis2(&self) -> Vector {
401        self.local_frame2 * Vector::X
402    }
403
404    /// Sets the principal (local X) axis of this joint, expressed in the second rigid-body’s local-space.
405    pub fn set_local_axis2(&mut self, local_axis: Vector) -> &mut Self {
406        self.local_frame2.rotation = Self::complete_ang_frame(local_axis);
407        self
408    }
409
410    /// The anchor of this joint, expressed in the first rigid-body’s local-space.
411    #[must_use]
412    pub fn local_anchor1(&self) -> Vector {
413        self.local_frame1.translation
414    }
415
416    /// Sets anchor of this joint, expressed in the first rigid-body's local-space.
417    pub fn set_local_anchor1(&mut self, anchor1: Vector) -> &mut Self {
418        self.local_frame1.translation = anchor1;
419        self
420    }
421
422    /// The anchor of this joint, expressed in the second rigid-body's local-space.
423    #[must_use]
424    pub fn local_anchor2(&self) -> Vector {
425        self.local_frame2.translation
426    }
427
428    /// Sets anchor of this joint, expressed in the second rigid-body's local-space.
429    pub fn set_local_anchor2(&mut self, anchor2: Vector) -> &mut Self {
430        self.local_frame2.translation = anchor2;
431        self
432    }
433
434    /// Are contacts between the attached rigid-bodies enabled?
435    pub fn contacts_enabled(&self) -> bool {
436        self.contacts_enabled
437    }
438
439    /// Sets whether contacts between the attached rigid-bodies are enabled.
440    pub fn set_contacts_enabled(&mut self, enabled: bool) -> &mut Self {
441        self.contacts_enabled = enabled;
442        self
443    }
444
445    /// Sets the spring coefficients controlling this joint constraint’s softness.
446    #[must_use]
447    pub fn set_softness(&mut self, softness: SpringCoefficients<Real>) -> &mut Self {
448        self.softness = softness;
449        self
450    }
451
452    /// The joint limits along the specified axis.
453    #[must_use]
454    pub fn limits(&self, axis: JointAxis) -> Option<&JointLimits<Real>> {
455        let i = axis as usize;
456        if self.limit_axes.contains(axis.into()) {
457            Some(&self.limits[i])
458        } else {
459            None
460        }
461    }
462
463    /// Sets the joint limits along the specified axis.
464    pub fn set_limits(&mut self, axis: JointAxis, limits: [Real; 2]) -> &mut Self {
465        let i = axis as usize;
466        self.limit_axes |= axis.into();
467        self.limits[i].min = limits[0];
468        self.limits[i].max = limits[1];
469        self
470    }
471
472    /// The spring-like motor model along the specified axis of this joint.
473    #[must_use]
474    pub fn motor_model(&self, axis: JointAxis) -> Option<MotorModel> {
475        let i = axis as usize;
476        if self.motor_axes.contains(axis.into()) {
477            Some(self.motors[i].model)
478        } else {
479            None
480        }
481    }
482
483    /// Set the spring-like model used by the motor to reach the desired target velocity and position.
484    pub fn set_motor_model(&mut self, axis: JointAxis, model: MotorModel) -> &mut Self {
485        self.motors[axis as usize].model = model;
486        self
487    }
488
489    /// Sets the target velocity this motor needs to reach.
490    pub fn set_motor_velocity(
491        &mut self,
492        axis: JointAxis,
493        target_vel: Real,
494        factor: Real,
495    ) -> &mut Self {
496        self.set_motor(
497            axis,
498            self.motors[axis as usize].target_pos,
499            target_vel,
500            0.0,
501            factor,
502        )
503    }
504
505    /// Sets the target angle this motor needs to reach.
506    pub fn set_motor_position(
507        &mut self,
508        axis: JointAxis,
509        target_pos: Real,
510        stiffness: Real,
511        damping: Real,
512    ) -> &mut Self {
513        self.set_motor(axis, target_pos, 0.0, stiffness, damping)
514    }
515
516    /// Sets the maximum force the motor can deliver along the specified axis.
517    pub fn set_motor_max_force(&mut self, axis: JointAxis, max_force: Real) -> &mut Self {
518        self.motors[axis as usize].max_force = max_force;
519        self
520    }
521
522    /// The motor affecting the joint’s degree of freedom along the specified axis.
523    #[must_use]
524    pub fn motor(&self, axis: JointAxis) -> Option<&JointMotor> {
525        let i = axis as usize;
526        if self.motor_axes.contains(axis.into()) {
527            Some(&self.motors[i])
528        } else {
529            None
530        }
531    }
532
533    /// Configure both the target angle and target velocity of the motor.
534    pub fn set_motor(
535        &mut self,
536        axis: JointAxis,
537        target_pos: Real,
538        target_vel: Real,
539        stiffness: Real,
540        damping: Real,
541    ) -> &mut Self {
542        self.motor_axes |= axis.into();
543        let i = axis as usize;
544        self.motors[i].target_vel = target_vel;
545        self.motors[i].target_pos = target_pos;
546        self.motors[i].stiffness = stiffness;
547        self.motors[i].damping = damping;
548        self
549    }
550
551    /// Flips the orientation of the joint, including limits and motors.
552    pub fn flip(&mut self) {
553        std::mem::swap(&mut self.local_frame1, &mut self.local_frame2);
554
555        let coupled_bits = self.coupled_axes.bits();
556
557        for dim in 0..SPATIAL_DIM {
558            if coupled_bits & (1 << dim) == 0 {
559                let limit = self.limits[dim];
560                self.limits[dim].min = -limit.max;
561                self.limits[dim].max = -limit.min;
562            }
563
564            self.motors[dim].target_vel = -self.motors[dim].target_vel;
565            self.motors[dim].target_pos = -self.motors[dim].target_pos;
566        }
567    }
568
569    pub(crate) fn transform_to_solver_body_space(&mut self, rb1: &RigidBody, rb2: &RigidBody) {
570        if rb1.is_fixed() {
571            self.local_frame1 = rb1.pos.position * self.local_frame1;
572        } else {
573            self.local_frame1.translation -= rb1.mprops.local_mprops.local_com;
574        }
575
576        if rb2.is_fixed() {
577            self.local_frame2 = rb2.pos.position * self.local_frame2;
578        } else {
579            self.local_frame2.translation -= rb2.mprops.local_mprops.local_com;
580        }
581    }
582}
583
584macro_rules! joint_conversion_methods(
585    ($as_joint: ident, $as_joint_mut: ident, $Joint: ty, $axes: expr) => {
586        /// Converts the joint to its specific variant, if it is one.
587        #[must_use]
588        pub fn $as_joint(&self) -> Option<&$Joint> {
589            if self.locked_axes == $axes {
590                // SAFETY: this is OK because the target joint type is
591                //         a `repr(transparent)` newtype of `Joint`.
592                Some(unsafe { std::mem::transmute::<&Self, &$Joint>(self) })
593            } else {
594                None
595            }
596        }
597
598        /// Converts the joint to its specific mutable variant, if it is one.
599        #[must_use]
600        pub fn $as_joint_mut(&mut self) -> Option<&mut $Joint> {
601            if self.locked_axes == $axes {
602                // SAFETY: this is OK because the target joint type is
603                //         a `repr(transparent)` newtype of `Joint`.
604                Some(unsafe { std::mem::transmute::<&mut Self, &mut $Joint>(self) })
605            } else {
606                None
607            }
608        }
609    }
610);
611
612impl GenericJoint {
613    joint_conversion_methods!(
614        as_revolute,
615        as_revolute_mut,
616        RevoluteJoint,
617        JointAxesMask::LOCKED_REVOLUTE_AXES
618    );
619    joint_conversion_methods!(
620        as_fixed,
621        as_fixed_mut,
622        FixedJoint,
623        JointAxesMask::LOCKED_FIXED_AXES
624    );
625    joint_conversion_methods!(
626        as_prismatic,
627        as_prismatic_mut,
628        PrismaticJoint,
629        JointAxesMask::LOCKED_PRISMATIC_AXES
630    );
631    joint_conversion_methods!(
632        as_rope,
633        as_rope_mut,
634        RopeJoint,
635        JointAxesMask::FREE_FIXED_AXES
636    );
637
638    #[cfg(feature = "dim3")]
639    joint_conversion_methods!(
640        as_spherical,
641        as_spherical_mut,
642        SphericalJoint,
643        JointAxesMask::LOCKED_SPHERICAL_AXES
644    );
645}
646
647/// Create generic joints using the builder pattern.
648#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
649#[derive(Copy, Clone, Debug, PartialEq)]
650pub struct GenericJointBuilder(pub GenericJoint);
651
652impl GenericJointBuilder {
653    /// Creates a new generic joint builder.
654    #[must_use]
655    pub fn new(locked_axes: JointAxesMask) -> Self {
656        Self(GenericJoint::new(locked_axes))
657    }
658
659    /// Sets the degrees of freedom locked by the joint.
660    #[must_use]
661    pub fn locked_axes(mut self, axes: JointAxesMask) -> Self {
662        self.0.locked_axes = axes;
663        self
664    }
665
666    /// Sets whether contacts between the attached rigid-bodies are enabled.
667    #[must_use]
668    pub fn contacts_enabled(mut self, enabled: bool) -> Self {
669        self.0.contacts_enabled = enabled;
670        self
671    }
672
673    /// Sets the joint’s frame, expressed in the first rigid-body’s local-space.
674    #[must_use]
675    pub fn local_frame1(mut self, local_frame: Pose) -> Self {
676        self.0.set_local_frame1(local_frame);
677        self
678    }
679
680    /// Sets the joint’s frame, expressed in the second rigid-body’s local-space.
681    #[must_use]
682    pub fn local_frame2(mut self, local_frame: Pose) -> Self {
683        self.0.set_local_frame2(local_frame);
684        self
685    }
686
687    /// Sets the principal (local X) axis of this joint, expressed in the first rigid-body’s local-space.
688    #[must_use]
689    pub fn local_axis1(mut self, local_axis: Vector) -> Self {
690        self.0.set_local_axis1(local_axis);
691        self
692    }
693
694    /// Sets the principal (local X) axis of this joint, expressed in the second rigid-body’s local-space.
695    #[must_use]
696    pub fn local_axis2(mut self, local_axis: Vector) -> Self {
697        self.0.set_local_axis2(local_axis);
698        self
699    }
700
701    /// Sets the anchor of this joint, expressed in the first rigid-body’s local-space.
702    #[must_use]
703    pub fn local_anchor1(mut self, anchor1: Vector) -> Self {
704        self.0.set_local_anchor1(anchor1);
705        self
706    }
707
708    /// Sets the anchor of this joint, expressed in the second rigid-body’s local-space.
709    #[must_use]
710    pub fn local_anchor2(mut self, anchor2: Vector) -> Self {
711        self.0.set_local_anchor2(anchor2);
712        self
713    }
714
715    /// Sets the joint limits along the specified axis.
716    #[must_use]
717    pub fn limits(mut self, axis: JointAxis, limits: [Real; 2]) -> Self {
718        self.0.set_limits(axis, limits);
719        self
720    }
721
722    /// Sets the coupled degrees of freedom for this joint’s limits and motor.
723    #[must_use]
724    pub fn coupled_axes(mut self, axes: JointAxesMask) -> Self {
725        self.0.coupled_axes = axes;
726        self
727    }
728
729    /// Set the spring-like model used by the motor to reach the desired target velocity and position.
730    #[must_use]
731    pub fn motor_model(mut self, axis: JointAxis, model: MotorModel) -> Self {
732        self.0.set_motor_model(axis, model);
733        self
734    }
735
736    /// Sets the target velocity this motor needs to reach.
737    #[must_use]
738    pub fn motor_velocity(mut self, axis: JointAxis, target_vel: Real, factor: Real) -> Self {
739        self.0.set_motor_velocity(axis, target_vel, factor);
740        self
741    }
742
743    /// Sets the target angle this motor needs to reach.
744    #[must_use]
745    pub fn motor_position(
746        mut self,
747        axis: JointAxis,
748        target_pos: Real,
749        stiffness: Real,
750        damping: Real,
751    ) -> Self {
752        self.0
753            .set_motor_position(axis, target_pos, stiffness, damping);
754        self
755    }
756
757    /// Configure both the target angle and target velocity of the motor.
758    #[must_use]
759    pub fn set_motor(
760        mut self,
761        axis: JointAxis,
762        target_pos: Real,
763        target_vel: Real,
764        stiffness: Real,
765        damping: Real,
766    ) -> Self {
767        self.0
768            .set_motor(axis, target_pos, target_vel, stiffness, damping);
769        self
770    }
771
772    /// Sets the maximum force the motor can deliver along the specified axis.
773    #[must_use]
774    pub fn motor_max_force(mut self, axis: JointAxis, max_force: Real) -> Self {
775        self.0.set_motor_max_force(axis, max_force);
776        self
777    }
778
779    /// Sets the softness of this joint’s locked degrees of freedom.
780    #[must_use]
781    pub fn softness(mut self, softness: SpringCoefficients<Real>) -> Self {
782        self.0.softness = softness;
783        self
784    }
785
786    /// An arbitrary user-defined 128-bit integer associated to the joints built by this builder.
787    pub fn user_data(mut self, data: u128) -> Self {
788        self.0.user_data = data;
789        self
790    }
791
792    /// Builds the generic joint.
793    #[must_use]
794    pub fn build(self) -> GenericJoint {
795        self.0
796    }
797}
798
799impl From<GenericJointBuilder> for GenericJoint {
800    fn from(val: GenericJointBuilder) -> GenericJoint {
801        val.0
802    }
803}