1use crate::{
2 dynamics::joints::{EntityConstraint, JointSystems, motor::AngularMotor},
3 prelude::*,
4};
5use bevy::{
6 ecs::{
7 entity::{EntityMapper, MapEntities},
8 reflect::ReflectMapEntities,
9 },
10 prelude::*,
11};
12
13#[cfg_attr(
14 feature = "2d",
15 doc = "A revolute [joint](dynamics::joints) or hinge prevents any relative movement between two bodies, except for rotation about a pivot point defined by the joint anchor."
16)]
17#[cfg_attr(
18 feature = "3d",
19 doc = "A revolute [joint](dynamics::joints) or hinge prevents any relative movement between two bodies, except for rotation about the [`hinge_axis`](Self::hinge_axis) at a pivot point defined by the joint anchor."
20)]
21#[cfg_attr(
25 feature = "2d",
26 doc = "Each revolute joint is defined by a [`JointFrame`] on each body,"
27)]
28#[cfg_attr(
29 feature = "3d",
30 doc = "Each revolute joint is defined by a [`JointFrame`] on each body, a [`hinge_axis`](Self::hinge_axis) about which the bodies can rotate,"
31)]
32#[cfg_attr(feature = "2d", doc = "while allowing rotation at the anchor point.")]
34#[cfg_attr(
35 feature = "3d",
36 doc = "while allowing rotation about the hinge axis at the anchor point."
37)]
38#[doc = include_str!("./images/revolute_joint.svg")]
40#[derive(Component, Clone, Debug, PartialEq, Reflect)]
44#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
45#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
46#[reflect(Component, Debug, MapEntities, PartialEq)]
47#[doc(alias = "HingeJoint")]
48pub struct RevoluteJoint {
49 pub body1: Entity,
51 pub body2: Entity,
53 pub frame1: JointFrame,
56 pub frame2: JointFrame,
59 #[cfg(feature = "3d")]
63 pub hinge_axis: Vector,
64 pub angle_limit: Option<AngleLimit>,
66 pub point_compliance: Scalar,
68 #[cfg(feature = "3d")]
70 pub align_compliance: Scalar,
71 pub limit_compliance: Scalar,
73 pub motor: AngularMotor,
75}
76
77impl EntityConstraint<2> for RevoluteJoint {
78 fn entities(&self) -> [Entity; 2] {
79 [self.body1, self.body2]
80 }
81}
82
83impl RevoluteJoint {
84 #[cfg(feature = "3d")]
86 pub const DEFAULT_HINGE_AXIS: Vector = Vector::Z;
87
88 #[inline]
90 pub const fn new(body1: Entity, body2: Entity) -> Self {
91 Self {
92 body1,
93 body2,
94 frame1: JointFrame::IDENTITY,
95 frame2: JointFrame::IDENTITY,
96 #[cfg(feature = "3d")]
97 hinge_axis: Self::DEFAULT_HINGE_AXIS,
98 angle_limit: None,
99 point_compliance: 0.0,
100 #[cfg(feature = "3d")]
101 align_compliance: 0.0,
102 limit_compliance: 0.0,
103 motor: AngularMotor::new_disabled(MotorModel::DEFAULT),
104 }
105 }
106
107 #[inline]
111 #[cfg(feature = "3d")]
112 pub const fn with_hinge_axis(mut self, axis: Vector) -> Self {
113 self.hinge_axis = axis;
114 self
115 }
116
117 #[inline]
123 #[deprecated(since = "0.4.0", note = "Use `with_hinge_axis` instead.")]
124 #[cfg(feature = "3d")]
125 pub const fn with_aligned_axis(self, axis: Vector) -> Self {
126 self.with_hinge_axis(axis)
127 }
128
129 #[inline]
131 pub fn with_local_frame1(mut self, frame: impl Into<Isometry>) -> Self {
132 self.frame1 = JointFrame::local(frame);
133 self
134 }
135
136 #[inline]
138 pub fn with_local_frame2(mut self, frame: impl Into<Isometry>) -> Self {
139 self.frame2 = JointFrame::local(frame);
140 self
141 }
142
143 #[inline]
147 pub const fn with_anchor(mut self, anchor: Vector) -> Self {
148 self.frame1.anchor = JointAnchor::FromGlobal(anchor);
149 self.frame2.anchor = JointAnchor::FromGlobal(anchor);
150 self
151 }
152
153 #[inline]
157 pub const fn with_local_anchor1(mut self, anchor: Vector) -> Self {
158 self.frame1.anchor = JointAnchor::Local(anchor);
159 self
160 }
161
162 #[inline]
166 pub const fn with_local_anchor2(mut self, anchor: Vector) -> Self {
167 self.frame2.anchor = JointAnchor::Local(anchor);
168 self
169 }
170
171 #[inline]
175 pub fn with_basis(mut self, basis: impl Into<Rot>) -> Self {
176 let basis = basis.into();
177 self.frame1.basis = JointBasis::FromGlobal(basis);
178 self.frame2.basis = JointBasis::FromGlobal(basis);
179 self
180 }
181
182 #[inline]
186 pub fn with_local_basis1(mut self, basis: impl Into<Rot>) -> Self {
187 self.frame1.basis = JointBasis::Local(basis.into());
188 self
189 }
190
191 #[inline]
195 pub fn with_local_basis2(mut self, basis: impl Into<Rot>) -> Self {
196 self.frame2.basis = JointBasis::Local(basis.into());
197 self
198 }
199
200 #[inline]
207 pub fn local_frame1(&self) -> Option<Isometry> {
208 self.frame1.get_local_isometry()
209 }
210
211 #[inline]
218 pub fn local_frame2(&self) -> Option<Isometry> {
219 self.frame2.get_local_isometry()
220 }
221
222 #[inline]
227 pub const fn local_anchor1(&self) -> Option<Vector> {
228 match self.frame1.anchor {
229 JointAnchor::Local(anchor) => Some(anchor),
230 _ => None,
231 }
232 }
233
234 #[inline]
239 pub const fn local_anchor2(&self) -> Option<Vector> {
240 match self.frame2.anchor {
241 JointAnchor::Local(anchor) => Some(anchor),
242 _ => None,
243 }
244 }
245
246 #[inline]
251 pub fn local_basis1(&self) -> Option<Rot> {
252 match self.frame1.basis {
253 JointBasis::Local(basis) => Some(basis),
254 _ => None,
255 }
256 }
257
258 #[inline]
263 pub fn local_basis2(&self) -> Option<Rot> {
264 match self.frame2.basis {
265 JointBasis::Local(basis) => Some(basis),
266 _ => None,
267 }
268 }
269
270 #[inline]
278 #[cfg(feature = "3d")]
279 pub fn local_hinge_axis1(&self) -> Option<Vector> {
280 match self.frame1.basis {
281 JointBasis::Local(basis) => Some(basis * self.hinge_axis),
282 _ => None,
283 }
284 }
285
286 #[inline]
294 #[cfg(feature = "3d")]
295 pub fn local_hinge_axis2(&self) -> Option<Vector> {
296 match self.frame2.basis {
297 JointBasis::Local(basis) => Some(basis * self.hinge_axis),
298 _ => None,
299 }
300 }
301
302 #[inline]
304 pub const fn with_angle_limits(mut self, min: Scalar, max: Scalar) -> Self {
305 self.angle_limit = Some(AngleLimit::new(min, max));
306 self
307 }
308
309 #[inline]
311 #[deprecated(
312 since = "0.4.0",
313 note = "Use `with_point_compliance`, `with_align_compliance`, and `with_limit_compliance` instead."
314 )]
315 pub const fn with_compliance(mut self, compliance: Scalar) -> Self {
316 self.point_compliance = compliance;
317 #[cfg(feature = "3d")]
318 {
319 self.align_compliance = compliance;
320 }
321 self.limit_compliance = compliance;
322 self
323 }
324
325 #[inline]
327 pub const fn with_point_compliance(mut self, compliance: Scalar) -> Self {
328 self.point_compliance = compliance;
329 self
330 }
331
332 #[inline]
334 #[cfg(feature = "3d")]
335 pub const fn with_align_compliance(mut self, compliance: Scalar) -> Self {
336 self.align_compliance = compliance;
337 self
338 }
339
340 #[inline]
342 pub const fn with_limit_compliance(mut self, compliance: Scalar) -> Self {
343 self.limit_compliance = compliance;
344 self
345 }
346
347 #[inline]
349 pub const fn with_motor(mut self, motor: AngularMotor) -> Self {
350 self.motor = motor;
351 self
352 }
353}
354
355impl MapEntities for RevoluteJoint {
356 fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
357 self.body1 = entity_mapper.get_mapped(self.body1);
358 self.body2 = entity_mapper.get_mapped(self.body2);
359 }
360}
361
362pub(super) fn plugin(app: &mut App) {
363 app.add_systems(
364 PhysicsSchedule,
365 update_local_frames.in_set(JointSystems::PrepareLocalFrames),
366 );
367}
368
369fn update_local_frames(
370 mut joints: Query<&mut RevoluteJoint, Changed<RevoluteJoint>>,
371 bodies: Query<(&Position, &Rotation)>,
372) {
373 for mut joint in &mut joints {
374 if matches!(joint.frame1.anchor, JointAnchor::Local(_))
375 && matches!(joint.frame2.anchor, JointAnchor::Local(_))
376 && matches!(joint.frame1.basis, JointBasis::Local(_))
377 && matches!(joint.frame2.basis, JointBasis::Local(_))
378 {
379 continue;
380 }
381
382 let Ok([(pos1, rot1), (pos2, rot2)]) = bodies.get_many(joint.entities()) else {
383 continue;
384 };
385
386 let [frame1, frame2] =
387 JointFrame::compute_local(joint.frame1, joint.frame2, pos1.0, pos2.0, rot1, rot2);
388 joint.frame1 = frame1;
389 joint.frame2 = frame2;
390 }
391}
392
393#[cfg(feature = "debug-plugin")]
394impl DebugRenderConstraint<2> for RevoluteJoint {
395 type Context = ();
396
397 fn debug_render(
398 &self,
399 positions: [Vector; 2],
400 rotations: [Rotation; 2],
401 _context: &mut Self::Context,
402 gizmos: &mut Gizmos<PhysicsGizmos>,
403 config: &PhysicsGizmos,
404 ) {
405 let [pos1, pos2] = positions;
406 let [rot1, rot2] = rotations;
407
408 let Some(local_anchor1) = self.local_anchor1() else {
409 return;
410 };
411 let Some(local_anchor2) = self.local_anchor2() else {
412 return;
413 };
414
415 let anchor1 = pos1 + rot1 * local_anchor1;
416 let anchor2 = pos2 + rot2 * local_anchor2;
417
418 if let Some(anchor_color) = config.joint_anchor_color {
419 gizmos.draw_line(pos1, anchor1, anchor_color);
420 gizmos.draw_line(pos2, anchor2, anchor_color);
421 }
422
423 if let Some(color) = config.joint_separation_color {
424 gizmos.draw_line(anchor1, anchor2, color);
425 }
426 }
427}