Skip to main content

bevy_rapier3d/dynamics/
joint.rs

1use crate::math::{AngVector, Real, Vect};
2use bevy::prelude::*;
3use rapier::dynamics::{
4    ImpulseJoint as RapierImpulseJoint, ImpulseJointHandle, MultibodyJointHandle,
5};
6
7pub use rapier::dynamics::{JointAxesMask, JointAxis, MotorModel, SpringCoefficients};
8
9use super::{
10    FixedJoint, GenericJoint, MultibodyJointState, PrismaticJoint, RevoluteJoint, RopeJoint,
11    SpringJoint,
12};
13
14#[cfg(doc)]
15use super::{
16    KinematicMultibodyJoint, MultibodyJointArmature, MultibodyJointCouplings,
17    MultibodyJointDamping, MultibodyJointFriction, MultibodyJointSprings,
18};
19
20#[cfg(feature = "dim2")]
21use super::PinSlotJoint;
22#[cfg(feature = "dim3")]
23use super::SphericalJoint;
24
25/// Wrapper enum over a specific joint.
26#[derive(Clone, Copy, Debug, PartialEq)]
27pub enum TypedJoint {
28    /// See [`FixedJoint`]
29    FixedJoint(FixedJoint),
30    /// See [`GenericJoint`]
31    GenericJoint(GenericJoint),
32    /// See [`PrismaticJoint`]
33    PrismaticJoint(PrismaticJoint),
34    /// See [`RevoluteJoint`]
35    RevoluteJoint(RevoluteJoint),
36    /// See [`RopeJoint`]
37    RopeJoint(RopeJoint),
38    /// See [`SphericalJoint`]
39    #[cfg(feature = "dim3")]
40    SphericalJoint(SphericalJoint),
41    /// See [`SpringJoint`]
42    SpringJoint(SpringJoint),
43    /// See [`PinSlotJoint`]
44    #[cfg(feature = "dim2")]
45    PinSlotJoint(PinSlotJoint),
46}
47
48impl AsMut<GenericJoint> for TypedJoint {
49    fn as_mut(&mut self) -> &mut GenericJoint {
50        match self {
51            TypedJoint::FixedJoint(ref mut j) => &mut j.data,
52            TypedJoint::GenericJoint(ref mut j) => j,
53            TypedJoint::PrismaticJoint(ref mut j) => &mut j.data,
54            TypedJoint::RevoluteJoint(ref mut j) => &mut j.data,
55            TypedJoint::RopeJoint(ref mut j) => &mut j.data,
56            #[cfg(feature = "dim3")]
57            TypedJoint::SphericalJoint(ref mut j) => &mut j.data,
58            TypedJoint::SpringJoint(ref mut j) => &mut j.data,
59            #[cfg(feature = "dim2")]
60            TypedJoint::PinSlotJoint(ref mut j) => &mut j.data,
61        }
62    }
63}
64
65impl AsRef<GenericJoint> for TypedJoint {
66    fn as_ref(&self) -> &GenericJoint {
67        match self {
68            TypedJoint::FixedJoint(j) => &j.data,
69            TypedJoint::GenericJoint(j) => j,
70            TypedJoint::PrismaticJoint(j) => &j.data,
71            TypedJoint::RevoluteJoint(j) => &j.data,
72            TypedJoint::RopeJoint(j) => &j.data,
73            #[cfg(feature = "dim3")]
74            TypedJoint::SphericalJoint(j) => &j.data,
75            TypedJoint::SpringJoint(j) => &j.data,
76            #[cfg(feature = "dim2")]
77            TypedJoint::PinSlotJoint(j) => &j.data,
78        }
79    }
80}
81
82/// The handle of an impulse joint added to the physics scene.
83#[derive(Copy, Clone, Debug, Component)]
84pub struct RapierImpulseJointHandle(pub ImpulseJointHandle);
85
86/// The handle of a multibody joint added to the physics scene.
87#[derive(Copy, Clone, Debug, Component)]
88pub struct RapierMultibodyJointHandle(pub MultibodyJointHandle);
89
90/// An impulse-based joint attached to two entities.
91///
92/// The first end-point of the joint is the rigid-body attached to
93/// `ImpulseJoint::parent`. The second endpoint of the joint is the
94/// rigid-body attached to the entity (or the parent of the entity)
95/// containing this `ImpulseJoint` component.
96///
97/// To attach multiple impulse joints to the same rigid-body, multiple
98/// joints can be added in the children of the entity containing that
99/// rigid-body (this is similar to the technique used to attach multiple
100/// colliders to the same rigid-body).
101///
102/// Modifying `parent` re-attaches the joint to the rigid-body of the new parent entity. Add the
103/// [`ImpulseJointDisabled`] component to disable the joint, and read the impulses it applied
104/// during the last simulation step from the [`ImpulseJointImpulses`] component.
105///
106/// Note that the `user_data` of the underlying Rapier joint is reserved: it stores the bits of
107/// the entity containing this component.
108#[derive(Copy, Clone, Debug, PartialEq, Component)]
109#[require(ImpulseJointImpulses)]
110pub struct ImpulseJoint {
111    /// The entity containing the rigid-body used as the first endpoint of this joint.
112    pub parent: Entity,
113    /// The joint’s description.
114    pub data: TypedJoint,
115}
116
117impl ImpulseJoint {
118    /// Initializes an impulse-based joint from its first endpoint and the joint description.
119    pub fn new(parent: Entity, data: impl Into<TypedJoint>) -> Self {
120        Self {
121            parent,
122            data: data.into(),
123        }
124    }
125}
126
127/// Marker component disabling the [`ImpulseJoint`] of its entity.
128///
129/// A disabled joint stays attached to its rigid-bodies but is ignored by the constraints solver.
130/// Removing this component enables the joint again. Multibody joints cannot be disabled.
131#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Component, Reflect)]
132#[reflect(Component, Default, PartialEq)]
133pub struct ImpulseJointDisabled;
134
135/// The number of degrees of freedom of a joint (3 in 2D, 6 in 3D).
136#[cfg(feature = "dim2")]
137pub const JOINT_DOFS: usize = 3;
138/// The number of degrees of freedom of a joint (3 in 2D, 6 in 3D).
139#[cfg(feature = "dim3")]
140pub const JOINT_DOFS: usize = 6;
141
142/// The impulses applied by an [`ImpulseJoint`] during the last simulation step.
143///
144/// This component is automatically added alongside [`ImpulseJoint`] and updated after each
145/// simulation step. Comparing these impulses with a threshold can be used to implement
146/// breakable joints (by removing the [`ImpulseJoint`] once the threshold is exceeded).
147///
148/// All impulses are expressed along the axes of the joint frame attached to the first
149/// rigid-body. Divide them by the (sub)step length to get the corresponding forces.
150#[derive(Copy, Clone, Debug, Default, PartialEq, Component, Reflect)]
151#[reflect(Component, Default, PartialEq)]
152pub struct ImpulseJointImpulses {
153    /// The impulse applied along the locked translational degrees of freedom.
154    pub linear: Vect,
155    /// The angular impulse applied along the locked rotational degrees of freedom.
156    pub angular: AngVector,
157    /// The impulse applied by the limits along each degree of freedom (see [`JointAxis`]).
158    pub limits: [Real; JOINT_DOFS],
159    /// The impulse applied by the motors along each degree of freedom (see [`JointAxis`]).
160    pub motors: [Real; JOINT_DOFS],
161}
162
163impl ImpulseJointImpulses {
164    /// Reads the impulses applied by the given Rapier impulse joint.
165    pub fn from_rapier(joint: &RapierImpulseJoint) -> Self {
166        let impulses = joint.impulses;
167        #[cfg(feature = "dim2")]
168        let (linear, angular) = (Vect::new(impulses[0], impulses[1]), impulses[2]);
169        #[cfg(feature = "dim3")]
170        let (linear, angular) = (
171            Vect::new(impulses[0], impulses[1], impulses[2]),
172            AngVector::new(impulses[3], impulses[4], impulses[5]),
173        );
174        Self {
175            linear,
176            angular,
177            limits: std::array::from_fn(|i| joint.data.limits[i].impulse),
178            motors: std::array::from_fn(|i| joint.data.motors[i].impulse),
179        }
180    }
181}
182
183/// An joint based on generalized coordinates, attached to two entities.
184///
185/// The first end-point of the joint is the rigid-body attached to
186/// `MultibodyJoint::parent`. The second endpoint of the joint is the
187/// rigid-body attached to the entity containing this `MultibodyJoint` component.
188///
189/// Note that a set of multibody joints cannot form closed loops (for example a necklace).
190/// If a closed loop is detected, the last joint that closes the loop is ignored, and an
191/// error is printed to `stderr` (using `log::error!`).
192///
193/// Modifying `parent` re-attaches the joint to the rigid-body of the new parent entity. The
194/// joint can be further configured with the [`KinematicMultibodyJoint`],
195/// [`MultibodyJointDamping`], [`MultibodyJointFriction`], [`MultibodyJointArmature`],
196/// [`MultibodyJointSprings`] and [`MultibodyJointCouplings`] components, and its coordinates are
197/// read back into the [`MultibodyJointState`] component after each simulation step.
198///
199/// Note that the `user_data` of the underlying Rapier joint is reserved: it stores the bits of
200/// the entity containing this component.
201#[derive(Copy, Clone, Debug, PartialEq, Component)]
202#[require(MultibodyJointState)]
203pub struct MultibodyJoint {
204    /// The entity containing the rigid-body used as the first endpoint of this joint.
205    pub parent: Entity,
206    /// The joint’s description.
207    pub data: TypedJoint,
208}
209
210impl MultibodyJoint {
211    /// Initializes an joint based on reduced coordinates from its first endpoint and
212    /// the joint description.
213    pub fn new(parent: Entity, data: impl Into<TypedJoint>) -> Self {
214        Self {
215            parent,
216            data: data.into(),
217        }
218    }
219}