Skip to main content

rapier3d/dynamics/joint/multibody_joint/
multibody_joint.rs

1use crate::dynamics::solver::GenericJointConstraint;
2use crate::dynamics::{
3    FixedJointBuilder, GenericJoint, IntegrationParameters, Multibody, MultibodyLink,
4    RigidBodyVelocity, joint,
5};
6use crate::math::{
7    ANG_DIM, DIM, DVector, JacobianViewMut, Pose, Real, Rotation, SPATIAL_DIM, SpatialVector,
8    Vector,
9};
10use parry::math::VectorExt;
11
12#[cfg(feature = "dim2")]
13use crate::math::rotation_from_angle;
14#[cfg(feature = "dim3")]
15use crate::utils::RotationOps;
16use crate::utils::vect_to_na;
17use na::DVectorViewMut;
18
19#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
20#[derive(Copy, Clone, Debug)]
21/// An joint attached to two bodies based on the reduced coordinates formalism.
22pub struct MultibodyJoint {
23    /// The joint’s description.
24    pub data: GenericJoint,
25    /// Is the joint a kinematic joint?
26    ///
27    /// Kinematic joint velocities are never changed by the physics engine. This gives the user
28    /// total control over the values of their degrees of freedoms.
29    pub kinematic: bool,
30    pub(crate) coords: SpatialVector,
31    pub(crate) joint_rot: Rotation,
32    /// Per-DoF spring stiffness (a passive joint spring integrated implicitly
33    /// in the generalized dynamics). Zero on axes with no spring.
34    pub(crate) spring_stiffness: SpatialVector,
35    /// Per-DoF spring rest position, in the joint's generalized coordinate
36    /// (same convention as [`Self::coords`]). Only meaningful where
37    /// `spring_stiffness` is non-zero.
38    pub(crate) spring_ref: SpatialVector,
39}
40
41impl MultibodyJoint {
42    /// Creates a new multibody joint from its description.
43    pub fn new(data: GenericJoint, kinematic: bool) -> Self {
44        Self {
45            data,
46            kinematic,
47            coords: Default::default(),
48            joint_rot: Rotation::IDENTITY,
49            spring_stiffness: Default::default(),
50            spring_ref: Default::default(),
51        }
52    }
53
54    /// Sets a passive joint spring on `axis` (an index into the 6-DoF spatial
55    /// layout: `0..DIM` linear, `DIM..SPATIAL_DIM` angular). The spring applies
56    /// a generalized force `-stiffness · (q − rest)` integrated implicitly.
57    pub fn set_spring(&mut self, axis: usize, stiffness: Real, rest: Real) {
58        self.spring_stiffness[axis] = stiffness;
59        self.spring_ref[axis] = rest;
60    }
61
62    /// The passive joint spring on `axis` as `(stiffness, rest)`, as set by
63    /// [`Self::set_spring`]. `(0, 0)` if no spring is set on that axis.
64    pub fn spring(&self, axis: usize) -> (Real, Real) {
65        (self.spring_stiffness[axis], self.spring_ref[axis])
66    }
67
68    pub(crate) fn free(pos: Pose) -> Self {
69        let mut result = Self::new(GenericJoint::default(), false);
70        result.set_free_pos(pos);
71        result
72    }
73
74    pub(crate) fn fixed(pos: Pose) -> Self {
75        Self::new(
76            FixedJointBuilder::new().local_frame1(pos).build().into(),
77            false,
78        )
79    }
80
81    pub(crate) fn set_free_pos(&mut self, pos: Pose) {
82        #[cfg(feature = "dim2")]
83        {
84            self.coords.x = pos.translation.x;
85            self.coords.y = pos.translation.y;
86        }
87        #[cfg(feature = "dim3")]
88        {
89            self.coords[0] = pos.translation.x;
90            self.coords[1] = pos.translation.y;
91            self.coords[2] = pos.translation.z;
92        }
93        self.joint_rot = pos.rotation;
94    }
95
96    /// The joint’s angular coordinates converted to a rotation.
97    pub fn joint_rot(&self) -> Rotation {
98        self.joint_rot
99    }
100
101    fn num_free_lin_dofs(&self) -> usize {
102        let locked_bits = self.data.locked_axes.bits();
103        DIM - (locked_bits & ((1 << DIM) - 1)).count_ones() as usize
104    }
105
106    /// Generalized coordinates for this joint.
107    pub fn coords(&self) -> SpatialVector {
108        self.coords
109    }
110
111    /// The number of degrees of freedom allowed by the multibody_joint.
112    pub fn ndofs(&self) -> usize {
113        SPATIAL_DIM - self.data.locked_axes.bits().count_ones() as usize
114    }
115
116    /// The position of the multibody link containing this multibody_joint relative to its parent.
117    pub fn body_to_parent(&self) -> Pose {
118        let locked_bits = self.data.locked_axes.bits();
119        let mut transform = Pose::from_rotation(self.joint_rot) * self.data.local_frame2.inverse();
120
121        for i in 0..DIM {
122            if (locked_bits & (1 << i)) == 0 {
123                // Create a translation along axis i with the coordinate value
124                let translation = Vector::ith(i, self.coords[i]);
125                transform = Pose::from_translation(translation) * transform;
126            }
127        }
128
129        self.data.local_frame1 * transform
130    }
131
132    /// Integrate the position of this multibody_joint.
133    #[profiling::function]
134    pub fn integrate(&mut self, dt: Real, vels: &[Real]) {
135        let locked_bits = self.data.locked_axes.bits();
136        let mut curr_free_dof = 0;
137
138        for i in 0..DIM {
139            if (locked_bits & (1 << i)) == 0 {
140                self.coords[i] += vels[curr_free_dof] * dt;
141                curr_free_dof += 1;
142            }
143        }
144
145        let locked_ang_bits = locked_bits >> DIM;
146        let num_free_ang_dofs = ANG_DIM - locked_ang_bits.count_ones() as usize;
147        match num_free_ang_dofs {
148            0 => { /* No free dofs. */ }
149            1 => {
150                let dof_id = (!locked_ang_bits).trailing_zeros() as usize;
151                self.coords[DIM + dof_id] += vels[curr_free_dof] * dt;
152                #[cfg(feature = "dim2")]
153                {
154                    self.joint_rot = rotation_from_angle(self.coords[DIM + dof_id]);
155                }
156                #[cfg(feature = "dim3")]
157                {
158                    self.joint_rot = Rotation::from_axis_angle(
159                        Vector::ith(dof_id, 1.0),
160                        self.coords[DIM + dof_id],
161                    );
162                }
163            }
164            2 => {
165                todo!()
166            }
167            #[cfg(feature = "dim3")]
168            3 => {
169                let angvel = Vector::from_slice(&vels[curr_free_dof..curr_free_dof + 3]);
170                let disp = Rotation::from_scaled_axis(angvel * dt);
171                self.joint_rot = disp * self.joint_rot;
172                self.coords[3] += angvel[0] * dt;
173                self.coords[4] += angvel[1] * dt;
174                self.coords[5] += angvel[2] * dt;
175            }
176            _ => unreachable!(),
177        }
178    }
179
180    /// Apply a displacement to the multibody_joint.
181    pub fn apply_displacement(&mut self, disp: &[Real]) {
182        self.integrate(1.0, disp);
183    }
184
185    /// Sets in `out` the non-zero entries of the multibody_joint jacobian transformed by `transform`.
186    pub fn jacobian(&self, transform: &Rotation, out: &mut JacobianViewMut<Real>) {
187        let locked_bits = self.data.locked_axes.bits();
188        let mut curr_free_dof = 0;
189
190        for i in 0..DIM {
191            if (locked_bits & (1 << i)) == 0 {
192                let transformed_axis = (*transform) * Vector::ith(i, 1.0);
193                out.fixed_view_mut::<DIM, 1>(0, curr_free_dof)
194                    .copy_from(&vect_to_na(transformed_axis));
195                curr_free_dof += 1;
196            }
197        }
198
199        let locked_ang_bits = locked_bits >> DIM;
200        let num_free_ang_dofs = ANG_DIM - locked_ang_bits.count_ones() as usize;
201        match num_free_ang_dofs {
202            0 => { /* No free dofs. */ }
203            1 => {
204                #[cfg(feature = "dim2")]
205                {
206                    out[(DIM, curr_free_dof)] = 1.0;
207                }
208
209                #[cfg(feature = "dim3")]
210                {
211                    let dof_id = (!locked_ang_bits).trailing_zeros() as usize;
212                    let rotmat = transform.to_mat();
213                    out.fixed_view_mut::<ANG_DIM, 1>(DIM, curr_free_dof)
214                        .copy_from_slice(rotmat.col(dof_id).as_ref());
215                }
216            }
217            2 => {
218                todo!()
219            }
220            #[cfg(feature = "dim3")]
221            3 => {
222                let rotmat = transform.to_mat();
223                out.fixed_view_mut::<3, 3>(3, curr_free_dof)
224                    .copy_from_slice(rotmat.as_ref());
225            }
226            _ => unreachable!(),
227        }
228    }
229
230    /// Multiply the multibody_joint jacobian by generalized velocities to obtain the
231    /// relative velocity of the multibody link containing this multibody_joint.
232    pub fn jacobian_mul_coordinates(&self, acc: &[Real]) -> RigidBodyVelocity<Real> {
233        let locked_bits = self.data.locked_axes.bits();
234        let mut result = RigidBodyVelocity::zero();
235        let mut curr_free_dof = 0;
236
237        for i in 0..DIM {
238            if (locked_bits & (1 << i)) == 0 {
239                result.linvel += Vector::ith(i, acc[curr_free_dof]);
240                curr_free_dof += 1;
241            }
242        }
243
244        let locked_ang_bits = locked_bits >> DIM;
245        let num_free_ang_dofs = ANG_DIM - locked_ang_bits.count_ones() as usize;
246        match num_free_ang_dofs {
247            0 => { /* No free dofs. */ }
248            1 => {
249                #[cfg(feature = "dim2")]
250                {
251                    result.angvel += acc[curr_free_dof];
252                }
253                #[cfg(feature = "dim3")]
254                {
255                    let dof_id = (!locked_ang_bits).trailing_zeros() as usize;
256                    result.angvel[dof_id] += acc[curr_free_dof];
257                }
258            }
259            2 => {
260                todo!()
261            }
262            #[cfg(feature = "dim3")]
263            3 => {
264                let angvel = Vector::from_slice(&acc[curr_free_dof..curr_free_dof + 3]);
265                result.angvel += angvel;
266            }
267            _ => unreachable!(),
268        }
269        result
270    }
271
272    /// Fill `out` with the non-zero entries of a damping that can be applied by default to ensure a good stability of the multibody_joint.
273    pub fn default_damping(&self, out: &mut DVectorViewMut<Real>) {
274        let locked_bits = self.data.locked_axes.bits();
275        let mut curr_free_dof = self.num_free_lin_dofs();
276
277        // A default damping only for the angular dofs
278        for i in DIM..SPATIAL_DIM {
279            if locked_bits & (1 << i) == 0 {
280                // This is a free angular DOF.
281                out[curr_free_dof] = 0.1;
282                curr_free_dof += 1;
283            }
284        }
285    }
286
287    /// Maximum number of velocity constrains that can be generated by this multibody_joint.
288    pub fn num_velocity_constraints(&self) -> usize {
289        let locked_bits = self.data.locked_axes.bits();
290        let limit_bits = self.data.limit_axes.bits();
291        let motor_bits = self.data.motor_axes.bits();
292        let mut num_constraints = 0;
293
294        for i in 0..SPATIAL_DIM {
295            if (locked_bits & (1 << i)) == 0 {
296                if (limit_bits & (1 << i)) != 0 {
297                    num_constraints += 1;
298                }
299                if (motor_bits & (1 << i)) != 0 {
300                    num_constraints += 1;
301                }
302            }
303        }
304
305        num_constraints
306    }
307
308    /// Initialize and generate velocity constraints to enforce, e.g., multibody_joint limits and motors.
309    pub fn velocity_constraints(
310        &self,
311        params: &IntegrationParameters,
312        multibody: &Multibody,
313        link: &MultibodyLink,
314        mut j_id: usize,
315        jacobians: &mut DVector,
316        constraints: &mut [GenericJointConstraint],
317    ) -> usize {
318        let j_id = &mut j_id;
319        let locked_bits = self.data.locked_axes.bits();
320        let limit_bits = self.data.limit_axes.bits();
321        let motor_bits = self.data.motor_axes.bits();
322        let mut num_constraints = 0;
323        let mut curr_free_dof = 0;
324
325        for i in 0..DIM {
326            if (locked_bits & (1 << i)) == 0 {
327                let limits = if (limit_bits & (1 << i)) != 0 {
328                    Some([self.data.limits[i].min, self.data.limits[i].max])
329                } else {
330                    None
331                };
332
333                if (motor_bits & (1 << i)) != 0 {
334                    joint::unit_joint_motor_constraint(
335                        params,
336                        multibody,
337                        link,
338                        &self.data.motors[i],
339                        self.coords[i],
340                        limits,
341                        curr_free_dof,
342                        j_id,
343                        jacobians,
344                        constraints,
345                        &mut num_constraints,
346                    );
347                }
348
349                if (limit_bits & (1 << i)) != 0 {
350                    joint::unit_joint_limit_constraint(
351                        params,
352                        multibody,
353                        link,
354                        [self.data.limits[i].min, self.data.limits[i].max],
355                        self.coords[i],
356                        curr_free_dof,
357                        j_id,
358                        jacobians,
359                        constraints,
360                        &mut num_constraints,
361                        self.data.softness,
362                    );
363                }
364                curr_free_dof += 1;
365            }
366        }
367
368        /*
369        let locked_ang_bits = locked_bits >> DIM;
370        let num_free_ang_dofs = ANG_DIM - locked_ang_bits.count_ones() as usize;
371        match num_free_ang_dofs {
372            0 => { /* No free dofs. */ }
373            1 => {}
374            2 => {
375                todo!()
376            }
377            3 => {}
378            _ => unreachable!(),
379        }
380         */
381        // TODO: we should make special cases for multi-angular-dofs limits/motors
382        for i in DIM..SPATIAL_DIM {
383            if (locked_bits & (1 << i)) == 0 {
384                let limits = if (limit_bits & (1 << i)) != 0 {
385                    let limits = [self.data.limits[i].min, self.data.limits[i].max];
386                    joint::unit_joint_limit_constraint(
387                        params,
388                        multibody,
389                        link,
390                        limits,
391                        self.coords[i],
392                        curr_free_dof,
393                        j_id,
394                        jacobians,
395                        constraints,
396                        &mut num_constraints,
397                        self.data.softness,
398                    );
399                    Some(limits)
400                } else {
401                    None
402                };
403
404                if (motor_bits & (1 << i)) != 0 {
405                    joint::unit_joint_motor_constraint(
406                        params,
407                        multibody,
408                        link,
409                        &self.data.motors[i],
410                        self.coords[i],
411                        limits,
412                        curr_free_dof,
413                        j_id,
414                        jacobians,
415                        constraints,
416                        &mut num_constraints,
417                    );
418                }
419                curr_free_dof += 1;
420            }
421        }
422
423        num_constraints
424    }
425}