bevy_rapier3d/dynamics/multibody.rs
1use crate::math::{Real, Rot};
2use crate::reflect::JointAxisWrapper;
3use bevy::prelude::*;
4use rapier::dynamics::{JointAxesMask, JointAxis, MultibodyJoint as RapierMultibodyJoint};
5use rapier::math::SPATIAL_DIM;
6
7pub use rapier::dynamics::InverseKinematicsOption;
8
9use super::JOINT_DOFS;
10
11#[cfg(doc)]
12use super::MultibodyJoint;
13#[cfg(doc)]
14use crate::plugin::context::RapierContextJoints;
15
16/// Iterates through the free degrees of freedom of a joint with the given locked axes, as
17/// `(dof, axis)` pairs, where `dof` is the index of the DoF in the joint’s slice of the
18/// multibody’s generalized coordinates, and `axis` its [`JointAxis`] index.
19pub(crate) fn free_joint_dofs(locked_axes: JointAxesMask) -> impl Iterator<Item = (usize, usize)> {
20 let locked = locked_axes.bits();
21 (0..SPATIAL_DIM)
22 .filter(move |axis| locked & (1 << axis) == 0)
23 .enumerate()
24}
25
26/// Marker component making the [`MultibodyJoint`] of its entity kinematic.
27///
28/// The velocities of a kinematic multibody joint are never modified by the physics engine: its
29/// degrees of freedom only move according to the generalized velocities set by the user (see
30/// [`RapierContextJoints::multibody_joint_velocity_mut`]), ignoring gravity, contacts and the
31/// other joints of the multibody. This can be added or removed at any time.
32#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Component, Reflect)]
33#[reflect(Component, Default, PartialEq)]
34pub struct KinematicMultibodyJoint;
35
36/// The per-DoF viscous damping of a [`MultibodyJoint`], indexed by [`JointAxis`].
37///
38/// The damping applies a generalized force opposed to the joint velocity along each free
39/// degree of freedom. Values along locked axes are ignored. Removing this component restores the
40/// default damping (see [`MultibodyJointDamping::default`]).
41#[derive(Copy, Clone, Debug, PartialEq, Component, Reflect)]
42#[reflect(Component, Default, PartialEq)]
43pub struct MultibodyJointDamping(pub [Real; JOINT_DOFS]);
44
45impl Default for MultibodyJointDamping {
46 /// The damping Rapier assigns to new multibody joints: `0.1` on angular axes, zero on
47 /// linear axes.
48 fn default() -> Self {
49 let lin_dofs = JOINT_DOFS - rapier::math::ANG_DIM;
50 Self(std::array::from_fn(
51 |i| if i < lin_dofs { 0.0 } else { 0.1 },
52 ))
53 }
54}
55
56impl MultibodyJointDamping {
57 /// The same damping along every axis.
58 pub fn splat(damping: Real) -> Self {
59 Self([damping; JOINT_DOFS])
60 }
61
62 /// Sets the damping along the given axis.
63 pub fn with(mut self, axis: JointAxis, damping: Real) -> Self {
64 self.0[axis as usize] = damping;
65 self
66 }
67}
68
69/// The per-DoF dry friction of a [`MultibodyJoint`], indexed by [`JointAxis`].
70///
71/// Each value is the largest generalized force (a force for linear axes, a torque for angular
72/// axes) friction may apply to stop the motion of the corresponding degree of freedom. Values
73/// along locked axes are ignored. Removing this component removes the friction.
74#[derive(Copy, Clone, Default, Debug, PartialEq, Component, Reflect)]
75#[reflect(Component, Default, PartialEq)]
76pub struct MultibodyJointFriction(pub [Real; JOINT_DOFS]);
77
78impl MultibodyJointFriction {
79 /// The same friction along every axis.
80 pub fn splat(friction: Real) -> Self {
81 Self([friction; JOINT_DOFS])
82 }
83
84 /// Sets the friction along the given axis.
85 pub fn with(mut self, axis: JointAxis, friction: Real) -> Self {
86 self.0[axis as usize] = friction;
87 self
88 }
89}
90
91/// The per-DoF armature (reflected rotor inertia) of a [`MultibodyJoint`], indexed by
92/// [`JointAxis`].
93///
94/// This inertia is added to the multibody’s mass matrix along each free degree of freedom, which
95/// typically models the inertia of an actuator and improves stability. Values along locked axes
96/// are ignored. Removing this component removes the armature.
97#[derive(Copy, Clone, Default, Debug, PartialEq, Component, Reflect)]
98#[reflect(Component, Default, PartialEq)]
99pub struct MultibodyJointArmature(pub [Real; JOINT_DOFS]);
100
101impl MultibodyJointArmature {
102 /// The same armature along every axis.
103 pub fn splat(armature: Real) -> Self {
104 Self([armature; JOINT_DOFS])
105 }
106
107 /// Sets the armature along the given axis.
108 pub fn with(mut self, axis: JointAxis, armature: Real) -> Self {
109 self.0[axis as usize] = armature;
110 self
111 }
112}
113
114/// Passive springs acting on the degrees of freedom of a [`MultibodyJoint`], indexed by
115/// [`JointAxis`].
116///
117/// Along each free axis, the spring applies the generalized force
118/// `-stiffness * (coord - rest)`, integrated implicitly (so stiff springs remain stable), where
119/// `coord` is the joint coordinate reported by [`MultibodyJointState::coords`]. Axes with a zero
120/// stiffness have no spring. Removing this component removes all the springs.
121#[derive(Copy, Clone, Default, Debug, PartialEq, Component, Reflect)]
122#[reflect(Component, Default, PartialEq)]
123pub struct MultibodyJointSprings {
124 /// The spring stiffness along each axis.
125 pub stiffness: [Real; JOINT_DOFS],
126 /// The rest coordinate of the spring along each axis.
127 pub rest: [Real; JOINT_DOFS],
128}
129
130impl MultibodyJointSprings {
131 /// Sets the spring along the given axis.
132 pub fn with(mut self, axis: JointAxis, stiffness: Real, rest: Real) -> Self {
133 self.stiffness[axis as usize] = stiffness;
134 self.rest[axis as usize] = rest;
135 self
136 }
137}
138
139/// A linear coupling between a degree of freedom of this [`MultibodyJoint`] and a degree of
140/// freedom of another joint of the same multibody.
141///
142/// The coupling enforces `coord(axis) = coeff * source_coord(source_axis) + offset`, similar to
143/// a "mimic" joint. See [`MultibodyJointCouplings`].
144#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
145#[reflect(PartialEq)]
146pub struct MultibodyJointCoupling {
147 /// The coupled axis of the joint containing this coupling.
148 #[reflect(remote = JointAxisWrapper)]
149 pub axis: JointAxis,
150 /// The entity with the [`MultibodyJoint`] (or the root rigid-body) driving this coupling.
151 pub source: Entity,
152 /// The axis of `source` driving this coupling.
153 #[reflect(remote = JointAxisWrapper)]
154 pub source_axis: JointAxis,
155 /// The linear coupling coefficient.
156 pub coeff: Real,
157 /// The constant offset of the coupling.
158 pub offset: Real,
159}
160
161impl MultibodyJointCoupling {
162 /// Couples `axis` of this joint to `source_axis` of `source` with
163 /// `coord(axis) = coeff * source_coord(source_axis) + offset`.
164 pub fn new(
165 axis: JointAxis,
166 source: Entity,
167 source_axis: JointAxis,
168 coeff: Real,
169 offset: Real,
170 ) -> Self {
171 Self {
172 axis,
173 source,
174 source_axis,
175 coeff,
176 offset,
177 }
178 }
179}
180
181/// The couplings between the degrees of freedom of this [`MultibodyJoint`] and other joints of
182/// the same multibody.
183///
184/// A coupling is only active while both joints belong to the same multibody and both coupled
185/// axes are free; it is ignored otherwise. Removing a coupling (or this component) removes it
186/// from the multibody without affecting the joint state.
187#[derive(Clone, Default, Debug, PartialEq, Component, Reflect)]
188#[reflect(Component, Default, PartialEq)]
189pub struct MultibodyJointCouplings(pub Vec<MultibodyJointCoupling>);
190
191/// Marker component disabling the contacts between the links of the multibody containing the
192/// rigid-body of its entity.
193///
194/// This can be added to any rigid-body of the multibody (typically its root), and overrides the
195/// per-joint [`GenericJoint::contacts_enabled`](crate::dynamics::GenericJoint::contacts_enabled)
196/// flags. Removing it enables self-contacts again (unless another link of the multibody still has
197/// this component).
198#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Component, Reflect)]
199#[reflect(Component, Default, PartialEq)]
200pub struct MultibodySelfContactsDisabled;
201
202/// The state of a [`MultibodyJoint`], written back after each simulation step.
203///
204/// This component is automatically added alongside [`MultibodyJoint`]. Modifying it has no
205/// effect: use the helpers of [`RapierContextJoints`] to control the joint coordinates and
206/// velocities.
207#[derive(Copy, Clone, Debug, PartialEq, Component, Reflect)]
208#[reflect(Component, Default, PartialEq)]
209pub struct MultibodyJointState {
210 /// The generalized coordinates of the joint, indexed by [`JointAxis`], zero along locked
211 /// axes.
212 ///
213 /// These are translations along linear axes and angles along angular axes. With more than
214 /// one free angular axis (3D only), the angular coordinates are integrated angular
215 /// velocities: use [`Self::rotation`] instead.
216 pub coords: [Real; JOINT_DOFS],
217 /// The generalized velocities of the joint, indexed by [`JointAxis`], zero along locked axes.
218 pub velocities: [Real; JOINT_DOFS],
219 /// The rotation of the joint’s second frame relative to its first frame.
220 pub rotation: Rot,
221}
222
223impl Default for MultibodyJointState {
224 fn default() -> Self {
225 Self {
226 coords: [0.0; JOINT_DOFS],
227 velocities: [0.0; JOINT_DOFS],
228 #[cfg(feature = "dim2")]
229 rotation: 0.0,
230 #[cfg(feature = "dim3")]
231 rotation: Rot::IDENTITY,
232 }
233 }
234}
235
236impl MultibodyJointState {
237 /// Reads the state of a Rapier multibody joint, given its generalized velocities (the
238 /// joint’s slice of the multibody’s generalized velocities).
239 pub fn from_rapier(joint: &RapierMultibodyJoint, velocities: &[Real]) -> Self {
240 let coords = joint.coords();
241 let mut result = Self {
242 coords: std::array::from_fn(|i| coords[i]),
243 #[cfg(feature = "dim2")]
244 rotation: joint.joint_rot().angle(),
245 #[cfg(feature = "dim3")]
246 rotation: joint.joint_rot(),
247 ..Default::default()
248 };
249 for (dof, axis) in free_joint_dofs(joint.data.locked_axes) {
250 if let Some(vel) = velocities.get(dof) {
251 result.velocities[axis] = *vel;
252 }
253 }
254 result
255 }
256}