avian2d/dynamics/joints/
revolute.rs

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