avian3d/dynamics/solver/joints/
revolute.rs

1//! [`RevoluteJoint`] component.
2
3use crate::{dynamics::solver::xpbd::*, prelude::*};
4use bevy::{
5    ecs::{
6        entity::{EntityMapper, MapEntities},
7        reflect::ReflectMapEntities,
8    },
9    prelude::*,
10};
11
12/// A revolute joint prevents relative movement of the attached bodies, except for rotation around one `aligned_axis`.
13///
14/// Revolute joints can be useful for things like wheels, fans, revolving doors etc.
15#[derive(Component, Clone, Copy, Debug, PartialEq, Reflect)]
16#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
18#[reflect(Debug, Component, MapEntities, PartialEq)]
19pub struct RevoluteJoint {
20    /// First entity constrained by the joint.
21    pub entity1: Entity,
22    /// Second entity constrained by the joint.
23    pub entity2: Entity,
24    /// Attachment point on the first body.
25    pub local_anchor1: Vector,
26    /// Attachment point on the second body.
27    pub local_anchor2: Vector,
28    /// A unit vector that controls which axis should be aligned for both entities.
29    ///
30    /// In 2D this should always be the Z axis.
31    #[cfg(feature = "2d")]
32    pub(crate) aligned_axis: Vector3,
33    /// A unit vector that controls which axis should be aligned for both bodies.
34    #[cfg(feature = "3d")]
35    pub aligned_axis: Vector,
36    /// The extents of the allowed relative rotation of the bodies around the `aligned_axis`.
37    pub angle_limit: Option<AngleLimit>,
38    /// Linear damping applied by the joint.
39    pub damping_linear: Scalar,
40    /// Angular damping applied by the joint.
41    pub damping_angular: Scalar,
42    /// Lagrange multiplier for the positional correction.
43    pub position_lagrange: Scalar,
44    /// Lagrange multiplier for the angular correction caused by the alignment of the bodies.
45    pub align_lagrange: Scalar,
46    /// Lagrange multiplier for the angular correction caused by the angle limits.
47    pub angle_limit_lagrange: Scalar,
48    /// The joint's compliance, the inverse of stiffness, has the unit meters / Newton.
49    pub compliance: Scalar,
50    /// The force exerted by the joint.
51    pub force: Vector,
52    /// The torque exerted by the joint when aligning the bodies.
53    pub align_torque: Torque,
54    /// The torque exerted by the joint when limiting the relative rotation of the bodies around the `aligned_axis`.
55    pub angle_limit_torque: Torque,
56}
57
58impl XpbdConstraint<2> for RevoluteJoint {
59    fn entities(&self) -> [Entity; 2] {
60        [self.entity1, self.entity2]
61    }
62
63    fn clear_lagrange_multipliers(&mut self) {
64        self.position_lagrange = 0.0;
65        self.align_lagrange = 0.0;
66        self.angle_limit_lagrange = 0.0;
67    }
68
69    fn solve(&mut self, bodies: [&mut RigidBodyQueryItem; 2], dt: Scalar) {
70        let [body1, body2] = bodies;
71        let compliance = self.compliance;
72
73        #[cfg(feature = "3d")]
74        {
75            // Constrain the relative rotation of the bodies, only allowing rotation around one free axis
76            let difference = self.get_rotation_difference(&body1.rotation, &body2.rotation);
77            let mut lagrange = self.align_lagrange;
78            self.align_torque =
79                self.align_orientation(body1, body2, difference, &mut lagrange, compliance, dt);
80            self.align_lagrange = lagrange;
81        }
82
83        // Apply angle limits when rotating around the free axis
84        self.angle_limit_torque = self.apply_angle_limits(body1, body2, dt);
85
86        // Align positions
87        let mut lagrange = self.position_lagrange;
88        self.force = self.align_position(
89            body1,
90            body2,
91            self.local_anchor1,
92            self.local_anchor2,
93            &mut lagrange,
94            compliance,
95            dt,
96        );
97        self.position_lagrange = lagrange;
98    }
99}
100
101impl Joint for RevoluteJoint {
102    fn new(entity1: Entity, entity2: Entity) -> Self {
103        Self {
104            entity1,
105            entity2,
106            local_anchor1: Vector::ZERO,
107            local_anchor2: Vector::ZERO,
108            aligned_axis: Vector3::Z,
109            angle_limit: None,
110            damping_linear: 1.0,
111            damping_angular: 1.0,
112            position_lagrange: 0.0,
113            align_lagrange: 0.0,
114            angle_limit_lagrange: 0.0,
115            compliance: 0.0,
116            force: Vector::ZERO,
117            #[cfg(feature = "2d")]
118            align_torque: 0.0,
119            #[cfg(feature = "3d")]
120            align_torque: Vector::ZERO,
121            #[cfg(feature = "2d")]
122            angle_limit_torque: 0.0,
123            #[cfg(feature = "3d")]
124            angle_limit_torque: Vector::ZERO,
125        }
126    }
127
128    fn with_compliance(self, compliance: Scalar) -> Self {
129        Self { compliance, ..self }
130    }
131
132    fn with_local_anchor_1(self, anchor: Vector) -> Self {
133        Self {
134            local_anchor1: anchor,
135            ..self
136        }
137    }
138
139    fn with_local_anchor_2(self, anchor: Vector) -> Self {
140        Self {
141            local_anchor2: anchor,
142            ..self
143        }
144    }
145
146    fn with_linear_velocity_damping(self, damping: Scalar) -> Self {
147        Self {
148            damping_linear: damping,
149            ..self
150        }
151    }
152
153    fn with_angular_velocity_damping(self, damping: Scalar) -> Self {
154        Self {
155            damping_angular: damping,
156            ..self
157        }
158    }
159
160    fn local_anchor_1(&self) -> Vector {
161        self.local_anchor1
162    }
163
164    fn local_anchor_2(&self) -> Vector {
165        self.local_anchor2
166    }
167
168    fn damping_linear(&self) -> Scalar {
169        self.damping_linear
170    }
171
172    fn damping_angular(&self) -> Scalar {
173        self.damping_angular
174    }
175}
176
177impl RevoluteJoint {
178    /// Sets the axis that the bodies should be aligned on.
179    #[cfg(feature = "3d")]
180    pub fn with_aligned_axis(self, axis: Vector) -> Self {
181        Self {
182            aligned_axis: axis,
183            ..self
184        }
185    }
186
187    /// Sets the limits of the allowed relative rotation around the `aligned_axis`.
188    pub fn with_angle_limits(self, min: Scalar, max: Scalar) -> Self {
189        Self {
190            angle_limit: Some(AngleLimit::new(min, max)),
191            ..self
192        }
193    }
194
195    #[cfg(feature = "3d")]
196    fn get_rotation_difference(&self, rot1: &Rotation, rot2: &Rotation) -> Vector3 {
197        let a1 = rot1 * self.aligned_axis;
198        let a2 = rot2 * self.aligned_axis;
199        a1.cross(a2)
200    }
201
202    /// Applies angle limits to limit the relative rotation of the bodies around the `aligned_axis`.
203    #[allow(clippy::too_many_arguments)]
204    fn apply_angle_limits(
205        &mut self,
206        body1: &mut RigidBodyQueryItem,
207        body2: &mut RigidBodyQueryItem,
208        dt: Scalar,
209    ) -> Torque {
210        let Some(Some(correction)) = self.angle_limit.map(|angle_limit| {
211            #[cfg(feature = "2d")]
212            {
213                angle_limit.compute_correction(*body1.rotation, *body2.rotation, PI)
214            }
215            #[cfg(feature = "3d")]
216            {
217                // [n, n1, n2] = [a1, b1, b2], where [a, b, c] are perpendicular unit axes on the bodies.
218                let a1 = *body1.rotation * self.aligned_axis;
219                let b1 = *body1.rotation * self.aligned_axis.any_orthonormal_vector();
220                let b2 = *body2.rotation * self.aligned_axis.any_orthonormal_vector();
221                angle_limit.compute_correction(a1, b1, b2, PI)
222            }
223        }) else {
224            return Torque::ZERO;
225        };
226
227        let mut lagrange = self.angle_limit_lagrange;
228        let torque =
229            self.align_orientation(body1, body2, correction, &mut lagrange, self.compliance, dt);
230        self.angle_limit_lagrange = lagrange;
231        torque
232    }
233}
234
235impl PositionConstraint for RevoluteJoint {}
236
237impl AngularConstraint for RevoluteJoint {}
238
239impl MapEntities for RevoluteJoint {
240    fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
241        self.entity1 = entity_mapper.get_mapped(self.entity1);
242        self.entity2 = entity_mapper.get_mapped(self.entity2);
243    }
244}