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