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