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}