bevy_xpbd_2d/constraints/joints/
revolute.rs

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