avian2d/dynamics/joints/
prismatic.rs

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