avian3d/dynamics/solver/joints/mod.rs
1//! **Joints** are a way to connect entities in a way that restricts their movement relative to each other.
2//! They act as [constraints](dynamics::solver::xpbd#constraints) that restrict different *Degrees Of Freedom*
3//! depending on the joint type.
4//!
5//! # Degrees of Freedom (DOF)
6//!
7//! In 3D, entities can normally translate and rotate along the `X`, `Y` and `Z` axes.
8//! Therefore, they have 3 translational DOF and 3 rotational DOF, which is a total of 6 DOF.
9//!
10//! Joints reduce the number of DOF that entities have. For example, [revolute joints](RevoluteJoint)
11//! only allow rotation around one axis.
12//!
13//! Below is a table containing the joints that are currently implemented.
14//!
15//! | Joint | Allowed 2D DOF | Allowed 3D DOF |
16//! | ------------------ | ------------------------- | --------------------------- |
17//! | [`FixedJoint`] | None | None |
18//! | [`DistanceJoint`] | 1 Translation, 1 Rotation | 2 Translations, 3 Rotations |
19//! | [`PrismaticJoint`] | 1 Translation | 1 Translation |
20//! | [`RevoluteJoint`] | 1 Rotation | 1 Rotation |
21#![cfg_attr(
22 feature = "3d",
23 doc = "| [`SphericalJoint`] | 1 Rotation | 3 Rotations |"
24)]
25//!
26//! # Using Joints
27//!
28//! In Avian, joints are modeled as components. You can create a joint by simply spawning
29//! an entity and adding the joint component you want, giving the connected entities as arguments
30//! to the `new` method.
31//!
32//! ```
33#![cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
34#![cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
35//! use bevy::prelude::*;
36
37//! fn setup(mut commands: Commands) {
38//! let entity1 = commands.spawn(RigidBody::Dynamic).id();
39//! let entity2 = commands.spawn(RigidBody::Dynamic).id();
40//!
41//! // Connect the bodies with a fixed joint
42//! commands.spawn(FixedJoint::new(entity1, entity2));
43//! }
44//! ```
45//!
46//! ## Stiffness
47//!
48//! You can control the stiffness of a joint with the `with_compliance` method.
49//! *Compliance* refers to the inverse of stiffness, so using a compliance of 0 corresponds to
50//! infinite stiffness.
51//!
52//! ## Attachment Positions
53//!
54//! By default, joints are connected to the centers of entities, but attachment positions can be used to change this.
55//!
56//! You can use `with_local_anchor_1` and `with_local_anchor_2` to set the attachment positions on the first
57//! and second entity respectively.
58//!
59//! ## Damping
60//!
61//! You can configure the linear and angular damping caused by joints using the `with_linear_velocity_damping` and
62//! `with_angular_velocity_damping` methods. Increasing the damping values will cause the velocities
63//! of the connected entities to decrease faster.
64//!
65//! ## Other Configuration
66//!
67//! Different joints may have different configuration options. Many joints allow you to change the axis of allowed
68//! translation or rotation, and they may have distance or angle limits along these axes.
69//!
70//! Take a look at the documentation and methods of each joint to see all of the configuration options.
71//!
72//! # Custom Joints
73//!
74//! Joints are [constraints](dynamics::solver::xpbd#constraints) that implement [`Joint`] and [`XpbdConstraint`].
75//!
76//! The process of creating a joint is essentially the same as [creating a constraint](dynamics::solver::xpbd#custom-constraints),
77//! except you should also implement the [`Joint`] trait's methods. The trait has some useful helper methods
78//! like `align_position` and `align_orientation` to reduce some common boilerplate.
79//!
80//! Many joints also have joint limits. You can use [`DistanceLimit`] and [`AngleLimit`] to help store these limits
81//! and to compute the current distance from the specified limits.
82//!
83//! [See the code implementations](https://github.com/Jondolf/avian/tree/main/src/constraints/joints)
84//! of the implemented joints to get a better idea of how to create joints.
85
86mod distance;
87mod fixed;
88mod prismatic;
89mod revolute;
90#[cfg(feature = "3d")]
91mod spherical;
92
93pub use distance::*;
94pub use fixed::*;
95pub use prismatic::*;
96pub use revolute::*;
97#[cfg(feature = "3d")]
98pub use spherical::*;
99
100use crate::{dynamics::solver::xpbd::*, prelude::*};
101use bevy::prelude::*;
102
103/// A trait for [joints](self).
104pub trait Joint: Component + PositionConstraint + AngularConstraint {
105 /// Creates a new joint between two entities.
106 fn new(entity1: Entity, entity2: Entity) -> Self;
107
108 /// Sets the joint's compliance (inverse of stiffness, meters / Newton).
109 fn with_compliance(self, compliance: Scalar) -> Self;
110
111 /// Sets the attachment point on the first body.
112 fn with_local_anchor_1(self, anchor: Vector) -> Self;
113
114 /// Sets the attachment point on the second body.
115 fn with_local_anchor_2(self, anchor: Vector) -> Self;
116
117 /// Sets the linear velocity damping caused by the joint.
118 fn with_linear_velocity_damping(self, damping: Scalar) -> Self;
119
120 /// Sets the angular velocity damping caused by the joint.
121 fn with_angular_velocity_damping(self, damping: Scalar) -> Self;
122
123 /// Returns the local attachment point on the first body.
124 fn local_anchor_1(&self) -> Vector;
125
126 /// Returns the local attachment point on the second body.
127 fn local_anchor_2(&self) -> Vector;
128
129 /// Returns the linear velocity damping of the joint.
130 fn damping_linear(&self) -> Scalar;
131
132 /// Returns the angular velocity damping of the joint.
133 fn damping_angular(&self) -> Scalar;
134
135 /// Applies a positional correction that aligns the positions of the local attachment points `r1` and `r2`.
136 ///
137 /// Returns the force exerted by the alignment.
138 #[allow(clippy::too_many_arguments)]
139 fn align_position(
140 &self,
141 body1: &mut RigidBodyQueryItem,
142 body2: &mut RigidBodyQueryItem,
143 r1: Vector,
144 r2: Vector,
145 lagrange: &mut Scalar,
146 compliance: Scalar,
147 dt: Scalar,
148 ) -> Vector {
149 let world_r1 = *body1.rotation * r1;
150 let world_r2 = *body2.rotation * r2;
151
152 let (dir, magnitude) = DistanceLimit::new(0.0, 0.0).compute_correction(
153 body1.current_position() + world_r1,
154 body2.current_position() + world_r2,
155 );
156
157 if magnitude <= Scalar::EPSILON {
158 return Vector::ZERO;
159 }
160
161 // Compute generalized inverse masses
162 let w1 = PositionConstraint::compute_generalized_inverse_mass(self, body1, world_r1, dir);
163 let w2 = PositionConstraint::compute_generalized_inverse_mass(self, body2, world_r2, dir);
164
165 // Compute Lagrange multiplier update
166 let delta_lagrange =
167 self.compute_lagrange_update(*lagrange, magnitude, &[w1, w2], compliance, dt);
168 *lagrange += delta_lagrange;
169
170 // Apply positional correction to align the positions of the bodies
171 self.apply_positional_lagrange_update(
172 body1,
173 body2,
174 delta_lagrange,
175 dir,
176 world_r1,
177 world_r2,
178 );
179
180 // Return constraint force
181 self.compute_force(*lagrange, dir, dt)
182 }
183}
184
185/// A limit that indicates that the distance between two points should be between `min` and `max`.
186#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
187#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
188#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
189#[reflect(Debug, PartialEq)]
190pub struct DistanceLimit {
191 /// The minimum distance between two points.
192 pub min: Scalar,
193 /// The maximum distance between two points.
194 pub max: Scalar,
195}
196
197impl DistanceLimit {
198 /// A `DistanceLimit` with `min` and `max` set to zero.
199 pub const ZERO: Self = Self { min: 0.0, max: 0.0 };
200
201 /// Creates a new `DistanceLimit`.
202 pub const fn new(min: Scalar, max: Scalar) -> Self {
203 Self { min, max }
204 }
205
206 /// Returns the direction and magnitude of the positional correction required
207 /// to limit the distance between `p1` and `p2` to be within the distance limit.
208 pub fn compute_correction(&self, p1: Vector, p2: Vector) -> (Vector, Scalar) {
209 let pos_offset = p2 - p1;
210 let distance = pos_offset.length();
211
212 if distance <= Scalar::EPSILON {
213 return (Vector::ZERO, 0.0);
214 }
215
216 // Equation 25
217 if distance < self.min {
218 // Separation distance lower limit
219 (-pos_offset / distance, (distance - self.min))
220 } else if distance > self.max {
221 // Separation distance upper limit
222 (-pos_offset / distance, (distance - self.max))
223 } else {
224 (Vector::ZERO, 0.0)
225 }
226 }
227
228 /// Returns the positional correction required to limit the distance between `p1` and `p2`
229 /// to be within the distance limit along a given `axis`.
230 pub fn compute_correction_along_axis(&self, p1: Vector, p2: Vector, axis: Vector) -> Vector {
231 let pos_offset = p2 - p1;
232 let a = pos_offset.dot(axis);
233
234 // Equation 25
235 if a < self.min {
236 // Separation distance lower limit
237 axis * (self.min - a)
238 } else if a > self.max {
239 // Separation distance upper limit
240 -axis * (a - self.max)
241 } else {
242 Vector::ZERO
243 }
244 }
245}
246
247/// A limit that indicates that angles should be between `alpha` and `beta`.
248#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
249#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
250#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
251#[reflect(Debug, PartialEq)]
252pub struct AngleLimit {
253 /// The minimum angle.
254 pub min: Scalar,
255 /// The maximum angle.
256 pub max: Scalar,
257}
258
259impl AngleLimit {
260 /// An `AngleLimit` with `alpha` and `beta` set to zero.
261 pub const ZERO: Self = Self { min: 0.0, max: 0.0 };
262
263 /// Creates a new `AngleLimit`.
264 pub const fn new(min: Scalar, max: Scalar) -> Self {
265 Self { min, max }
266 }
267
268 /// Returns the angular correction required to limit the angle between two rotations
269 /// to be within the angle limits.
270 #[cfg(feature = "2d")]
271 pub fn compute_correction(
272 &self,
273 rotation1: Rotation,
274 rotation2: Rotation,
275 max_correction: Scalar,
276 ) -> Option<Scalar> {
277 let angle = rotation1.angle_between(rotation2);
278
279 let correction = if angle < self.min {
280 angle - self.min
281 } else if angle > self.max {
282 angle - self.max
283 } else {
284 return None;
285 };
286
287 Some(correction.min(max_correction))
288 }
289
290 /// Returns the angular correction required to limit the angle between `axis1` and `axis2`
291 /// to be within the angle limits with respect to the `limit_axis`.
292 #[cfg(feature = "3d")]
293 pub fn compute_correction(
294 &self,
295 limit_axis: Vector,
296 axis1: Vector,
297 axis2: Vector,
298 max_correction: Scalar,
299 ) -> Option<Vector> {
300 // [limit_axis, axis1, axis2] = [n, n1, n2] in XPBD rigid body paper.
301
302 // Angle between axis1 and axis2 with respect to limit_axis.
303 let mut phi = axis1.cross(axis2).dot(limit_axis).asin();
304
305 // `asin` returns the angle in the [-pi/2, pi/2] range.
306 // This is correct if the angle between n1 and n2 is acute,
307 // but obtuse angles must be accounted for.
308 if axis1.dot(axis2) < 0.0 {
309 phi = PI - phi;
310 }
311
312 // Map the angle to the [-pi, pi] range.
313 if phi > PI {
314 phi -= TAU;
315 }
316
317 // The XPBD rigid body paper has this, but the angle
318 // should already be in the correct range.
319 //
320 // if phi < -PI {
321 // phi += TAU;
322 // }
323
324 // Only apply a correction if the limit is violated.
325 if phi < self.min || phi > self.max {
326 // phi now represents the angle between axis1 and axis2.
327
328 // Clamp phi to get the target angle.
329 phi = phi.clamp(self.min, self.max);
330
331 // Create a quaternion that represents the rotation.
332 let rot = Quaternion::from_axis_angle(limit_axis, phi);
333
334 // Rotate axis1 by the target angle and compute the correction.
335 return Some((rot * axis1).cross(axis2).clamp_length_max(max_correction));
336 }
337
338 None
339 }
340}
341
342/// A marker component that indicates that a [joint](self) is disabled
343/// and should not constrain the bodies it is attached to.
344/// Must be on the same entity as the joint.
345///
346/// This is useful for temporarily disabling a joint without removing it from the world.
347/// To re-enable the joint, simply remove this component.
348///
349/// Note that when re-enabling the joint, the bodies may snap back violently
350/// if they have moved significantly from the constrained positions while the joint was disabled.
351///
352/// # Related Components
353///
354/// - [`RigidBodyDisabled`]: Disables a rigid body.
355/// - [`ColliderDisabled`]: Disables a collider.
356#[derive(Reflect, Clone, Copy, Component, Debug, Default)]
357#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
358#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
359#[reflect(Debug, Component, Default)]
360pub struct JointDisabled;