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}