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}