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;