avian2d/dynamics/joints/
revolute.rs

1use crate::{
2    dynamics::joints::{EntityConstraint, JointSystems},
3    prelude::*,
4};
5use bevy::{
6    ecs::{
7        entity::{EntityMapper, MapEntities},
8        reflect::ReflectMapEntities,
9    },
10    prelude::*,
11};
12
13#[cfg_attr(
14    feature = "2d",
15    doc = "A revolute [joint](dynamics::joints) or hinge prevents any relative movement between two bodies, except for rotation about a pivot point defined by the joint anchor."
16)]
17#[cfg_attr(
18    feature = "3d",
19    doc = "A revolute [joint](dynamics::joints) or hinge prevents any relative movement between two bodies, except for rotation about the [`hinge_axis`](Self::hinge_axis) at a pivot point defined by the joint anchor."
20)]
21///
22/// This can be useful for things like wheels, fans, doors, and other rotating mechanisms.
23///
24#[cfg_attr(
25    feature = "2d",
26    doc = "Each revolute joint is defined by a [`JointFrame`] on each body,"
27)]
28#[cfg_attr(
29    feature = "3d",
30    doc = "Each revolute joint is defined by a [`JointFrame`] on each body, a [`hinge_axis`](Self::hinge_axis) about which the bodies can rotate,"
31)]
32/// and an optional [`AngleLimit`] that defines the extents of the allowed rotation. The joint aims to keep the anchor point of each frame aligned,
33#[cfg_attr(feature = "2d", doc = "while allowing rotation at the anchor point.")]
34#[cfg_attr(
35    feature = "3d",
36    doc = "while allowing rotation about the hinge axis at the anchor point."
37)]
38///
39#[doc = include_str!("./images/revolute_joint.svg")]
40#[derive(Component, Clone, Debug, PartialEq, Reflect)]
41#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
42#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
43#[reflect(Component, Debug, MapEntities, PartialEq)]
44#[doc(alias = "HingeJoint")]
45pub struct RevoluteJoint {
46    /// The first body constrained by the joint.
47    pub body1: Entity,
48    /// The second body constrained by the joint.
49    pub body2: Entity,
50    /// The reference frame of the first body, defining the joint anchor and basis
51    /// relative to the body transform.
52    pub frame1: JointFrame,
53    /// The reference frame of the second body, defining the joint anchor and basis
54    /// relative to the body transform.
55    pub frame2: JointFrame,
56    /// The local axis about which the bodies can rotate relative to each other.
57    ///
58    /// By default, this is the z-axis.
59    #[cfg(feature = "3d")]
60    pub hinge_axis: Vector,
61    /// The extents of the allowed relative rotation of the bodies.
62    pub angle_limit: Option<AngleLimit>,
63    /// The compliance of the point-to-point constraint (inverse of stiffness, m / N).
64    pub point_compliance: Scalar,
65    /// The compliance used for aligning the bodies along the [`hinge_axis`](Self::hinge_axis) (inverse of stiffness, N * m / rad).
66    #[cfg(feature = "3d")]
67    pub align_compliance: Scalar,
68    /// The compliance of the angle limit (inverse of stiffness, N * m / rad).
69    pub limit_compliance: Scalar,
70}
71
72impl EntityConstraint<2> for RevoluteJoint {
73    fn entities(&self) -> [Entity; 2] {
74        [self.body1, self.body2]
75    }
76}
77
78impl RevoluteJoint {
79    /// The default [`hinge_axis`](Self::hinge_axis) for a revolute joint.
80    #[cfg(feature = "3d")]
81    pub const DEFAULT_HINGE_AXIS: Vector = Vector::Z;
82
83    /// Creates a new [`RevoluteJoint`] between two entities.
84    #[inline]
85    pub const fn new(body1: Entity, body2: Entity) -> Self {
86        Self {
87            body1,
88            body2,
89            frame1: JointFrame::IDENTITY,
90            frame2: JointFrame::IDENTITY,
91            #[cfg(feature = "3d")]
92            hinge_axis: Self::DEFAULT_HINGE_AXIS,
93            angle_limit: None,
94            point_compliance: 0.0,
95            #[cfg(feature = "3d")]
96            align_compliance: 0.0,
97            limit_compliance: 0.0,
98        }
99    }
100
101    /// Sets the [`hinge_axis`](Self::hinge_axis) about which the bodies can rotate relative to each other.
102    ///
103    /// The axis should be a unit vector. By default, this is the z-axis.
104    #[inline]
105    #[cfg(feature = "3d")]
106    pub const fn with_hinge_axis(mut self, axis: Vector) -> Self {
107        self.hinge_axis = axis;
108        self
109    }
110
111    /// Sets the [`hinge_axis`](Self::hinge_axis) about which the bodies can rotate relative to each other.
112    ///
113    /// The axis should be a unit vector. By default, this is the x-axis.
114    ///
115    /// This method is deprecated in favor of [`with_hinge_axis`](Self::with_hinge_axis).
116    #[inline]
117    #[deprecated(since = "0.4.0", note = "Use `with_hinge_axis` instead.")]
118    #[cfg(feature = "3d")]
119    pub const fn with_aligned_axis(self, axis: Vector) -> Self {
120        self.with_hinge_axis(axis)
121    }
122
123    /// Sets the local [`JointFrame`] of the first body, configuring both the [`JointAnchor`] and [`JointBasis`].
124    #[inline]
125    pub fn with_local_frame1(mut self, frame: impl Into<Isometry>) -> Self {
126        self.frame1 = JointFrame::local(frame);
127        self
128    }
129
130    /// Sets the local [`JointFrame`] of the second body, configuring both the [`JointAnchor`] and [`JointBasis`].
131    #[inline]
132    pub fn with_local_frame2(mut self, frame: impl Into<Isometry>) -> Self {
133        self.frame2 = JointFrame::local(frame);
134        self
135    }
136
137    /// Sets the global anchor point on both bodies.
138    ///
139    /// This configures the [`JointAnchor`] of each [`JointFrame`].
140    #[inline]
141    pub const fn with_anchor(mut self, anchor: Vector) -> Self {
142        self.frame1.anchor = JointAnchor::FromGlobal(anchor);
143        self.frame2.anchor = JointAnchor::FromGlobal(anchor);
144        self
145    }
146
147    /// Sets the local anchor point on the first body.
148    ///
149    /// This configures the [`JointAnchor`] of the first [`JointFrame`].
150    #[inline]
151    pub const fn with_local_anchor1(mut self, anchor: Vector) -> Self {
152        self.frame1.anchor = JointAnchor::Local(anchor);
153        self
154    }
155
156    /// Sets the local anchor point on the second body.
157    ///
158    /// This configures the [`JointAnchor`] of the second [`JointFrame`].
159    #[inline]
160    pub const fn with_local_anchor2(mut self, anchor: Vector) -> Self {
161        self.frame2.anchor = JointAnchor::Local(anchor);
162        self
163    }
164
165    /// Sets the global basis for both bodies.
166    ///
167    /// This configures the [`JointBasis`] of each [`JointFrame`].
168    #[inline]
169    pub fn with_basis(mut self, basis: impl Into<Rot>) -> Self {
170        let basis = basis.into();
171        self.frame1.basis = JointBasis::FromGlobal(basis);
172        self.frame2.basis = JointBasis::FromGlobal(basis);
173        self
174    }
175
176    /// Sets the local basis for the first body.
177    ///
178    /// This configures the [`JointBasis`] of the first [`JointFrame`].
179    #[inline]
180    pub fn with_local_basis1(mut self, basis: impl Into<Rot>) -> Self {
181        self.frame1.basis = JointBasis::Local(basis.into());
182        self
183    }
184
185    /// Sets the local basis for the second body.
186    ///
187    /// This configures the [`JointBasis`] of the second [`JointFrame`].
188    #[inline]
189    pub fn with_local_basis2(mut self, basis: impl Into<Rot>) -> Self {
190        self.frame2.basis = JointBasis::Local(basis.into());
191        self
192    }
193
194    /// Returns the local [`JointFrame`] of the first body.
195    ///
196    /// If the [`JointAnchor`] is set to [`FromGlobal`](JointAnchor::FromGlobal),
197    /// and the local anchor has not yet been computed, or the [`JointBasis`] is set to
198    /// [`FromGlobal`](JointBasis::FromGlobal), and the local basis has not yet
199    /// been computed, this will return `None`.
200    #[inline]
201    pub fn local_frame1(&self) -> Option<Isometry> {
202        self.frame1.get_local_isometry()
203    }
204
205    /// Returns the local [`JointFrame`] of the second body.
206    ///
207    /// If the [`JointAnchor`] is set to [`FromGlobal`](JointAnchor::FromGlobal),
208    /// and the local anchor has not yet been computed, or the [`JointBasis`] is set to
209    /// [`FromGlobal`](JointBasis::FromGlobal), and the local basis has not yet
210    /// been computed, this will return `None`.
211    #[inline]
212    pub fn local_frame2(&self) -> Option<Isometry> {
213        self.frame2.get_local_isometry()
214    }
215
216    /// Returns the local anchor point on the first body.
217    ///
218    /// If the [`JointAnchor`] is set to [`FromGlobal`](JointAnchor::FromGlobal),
219    /// and the local anchor has not yet been computed, this will return `None`.
220    #[inline]
221    pub const fn local_anchor1(&self) -> Option<Vector> {
222        match self.frame1.anchor {
223            JointAnchor::Local(anchor) => Some(anchor),
224            _ => None,
225        }
226    }
227
228    /// Returns the local anchor point on the second body.
229    ///
230    /// If the [`JointAnchor`] is set to [`FromGlobal`](JointAnchor::FromGlobal),
231    /// and the local anchor has not yet been computed, this will return `None`.
232    #[inline]
233    pub const fn local_anchor2(&self) -> Option<Vector> {
234        match self.frame2.anchor {
235            JointAnchor::Local(anchor) => Some(anchor),
236            _ => None,
237        }
238    }
239
240    /// Returns the local basis of the first body.
241    ///
242    /// If the [`JointBasis`] is set to [`FromGlobal`](JointBasis::FromGlobal),
243    /// and the local basis has not yet been computed, this will return `None`.
244    #[inline]
245    pub fn local_basis1(&self) -> Option<Rot> {
246        match self.frame1.basis {
247            JointBasis::Local(basis) => Some(basis),
248            _ => None,
249        }
250    }
251
252    /// Returns the local basis of the second body.
253    ///
254    /// If the [`JointBasis`] is set to [`FromGlobal`](JointBasis::FromGlobal),
255    /// and the local basis has not yet been computed, this will return `None`.
256    #[inline]
257    pub fn local_basis2(&self) -> Option<Rot> {
258        match self.frame2.basis {
259            JointBasis::Local(basis) => Some(basis),
260            _ => None,
261        }
262    }
263
264    /// Returns the local hinge axis of the first body.
265    ///
266    /// This is equivalent to rotating the [`hinge_axis`](Self::hinge_axis)
267    /// by the local basis of [`frame1`](Self::frame1).
268    ///
269    /// If the [`JointBasis`] is set to [`FromGlobal`](JointBasis::FromGlobal),
270    /// and the local basis has not yet been computed, this will return `None`.
271    #[inline]
272    #[cfg(feature = "3d")]
273    pub fn local_hinge_axis1(&self) -> Option<Vector> {
274        match self.frame1.basis {
275            JointBasis::Local(basis) => Some(basis * self.hinge_axis),
276            _ => None,
277        }
278    }
279
280    /// Returns the local hinge axis of the second body.
281    ///
282    /// This is equivalent to rotating the [`hinge_axis`](Self::hinge_axis)
283    /// by the local basis of [`frame2`](Self::frame2).
284    ///
285    /// If the [`JointBasis`] is set to [`FromGlobal`](JointBasis::FromGlobal),
286    /// and the local basis has not yet been computed, this will return `None`.
287    #[inline]
288    #[cfg(feature = "3d")]
289    pub fn local_hinge_axis2(&self) -> Option<Vector> {
290        match self.frame2.basis {
291            JointBasis::Local(basis) => Some(basis * self.hinge_axis),
292            _ => None,
293        }
294    }
295
296    /// Sets the limits of the allowed relative rotation.
297    #[inline]
298    pub const fn with_angle_limits(mut self, min: Scalar, max: Scalar) -> Self {
299        self.angle_limit = Some(AngleLimit::new(min, max));
300        self
301    }
302
303    /// Sets the joint's compliance (inverse of stiffness, m / N).
304    #[inline]
305    #[deprecated(
306        since = "0.4.0",
307        note = "Use `with_point_compliance`, `with_align_compliance`, and `with_limit_compliance` instead."
308    )]
309    pub const fn with_compliance(mut self, compliance: Scalar) -> Self {
310        self.point_compliance = compliance;
311        #[cfg(feature = "3d")]
312        {
313            self.align_compliance = compliance;
314        }
315        self.limit_compliance = compliance;
316        self
317    }
318
319    /// Sets the compliance of the point-to-point constraint (inverse of stiffness, m / N).
320    #[inline]
321    pub const fn with_point_compliance(mut self, compliance: Scalar) -> Self {
322        self.point_compliance = compliance;
323        self
324    }
325
326    /// Sets the compliance of the axis alignment constraint (inverse of stiffness, N * m / rad).
327    #[inline]
328    #[cfg(feature = "3d")]
329    pub const fn with_align_compliance(mut self, compliance: Scalar) -> Self {
330        self.align_compliance = compliance;
331        self
332    }
333
334    /// Sets the compliance of the angle limit (inverse of stiffness, N * m / rad).
335    #[inline]
336    pub const fn with_limit_compliance(mut self, compliance: Scalar) -> Self {
337        self.limit_compliance = compliance;
338        self
339    }
340}
341
342impl MapEntities for RevoluteJoint {
343    fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
344        self.body1 = entity_mapper.get_mapped(self.body1);
345        self.body2 = entity_mapper.get_mapped(self.body2);
346    }
347}
348
349pub(super) fn plugin(app: &mut App) {
350    app.add_systems(
351        PhysicsSchedule,
352        update_local_frames.in_set(JointSystems::PrepareLocalFrames),
353    );
354}
355
356fn update_local_frames(
357    mut joints: Query<&mut RevoluteJoint, Changed<RevoluteJoint>>,
358    bodies: Query<(&Position, &Rotation)>,
359) {
360    for mut joint in &mut joints {
361        if matches!(joint.frame1.anchor, JointAnchor::Local(_))
362            && matches!(joint.frame2.anchor, JointAnchor::Local(_))
363            && matches!(joint.frame1.basis, JointBasis::Local(_))
364            && matches!(joint.frame2.basis, JointBasis::Local(_))
365        {
366            continue;
367        }
368
369        let Ok([(pos1, rot1), (pos2, rot2)]) = bodies.get_many(joint.entities()) else {
370            continue;
371        };
372
373        let [frame1, frame2] =
374            JointFrame::compute_local(joint.frame1, joint.frame2, pos1.0, pos2.0, rot1, rot2);
375        joint.frame1 = frame1;
376        joint.frame2 = frame2;
377    }
378}
379
380#[cfg(feature = "debug-plugin")]
381impl DebugRenderConstraint<2> for RevoluteJoint {
382    type Context = ();
383
384    fn debug_render(
385        &self,
386        positions: [Vector; 2],
387        rotations: [Rotation; 2],
388        _context: &mut Self::Context,
389        gizmos: &mut Gizmos<PhysicsGizmos>,
390        config: &PhysicsGizmos,
391    ) {
392        let [pos1, pos2] = positions;
393        let [rot1, rot2] = rotations;
394
395        let Some(local_anchor1) = self.local_anchor1() else {
396            return;
397        };
398        let Some(local_anchor2) = self.local_anchor2() else {
399            return;
400        };
401
402        let anchor1 = pos1 + rot1 * local_anchor1;
403        let anchor2 = pos2 + rot2 * local_anchor2;
404
405        if let Some(anchor_color) = config.joint_anchor_color {
406            gizmos.draw_line(pos1, anchor1, anchor_color);
407            gizmos.draw_line(pos2, anchor2, anchor_color);
408        }
409
410        if let Some(color) = config.joint_separation_color {
411            gizmos.draw_line(anchor1, anchor2, color);
412        }
413    }
414}