Skip to main content

dynamics_spatial/
motion.rs

1//! Defines spatial **motion** and related operations.
2
3use nalgebra::{Matrix6, Rotation3, Vector6};
4
5use crate::{
6    force::SpatialForce,
7    se3::{ActSE3, SE3},
8    vector3d::Vector3D,
9    vector6d::Vector6D,
10};
11use std::{
12    fmt::Display,
13    ops::{Add, AddAssign, Mul},
14};
15
16#[derive(Clone, Debug, PartialEq, Default)]
17/// Spatial motion vector, combining linear and angular velocity components.
18///
19/// The first three elements represent linear velocity, and the last three represent angular velocity.
20pub struct SpatialMotion(pub Vector6<f64>);
21// TODO: rewrite this with Vector6D to avoid double implementation
22
23impl SpatialMotion {
24    /// Creates a new `SpatialMotion` from a 3D rotational axis (for revolute and continuous joints).
25    #[must_use]
26    pub fn from_rotational_axis(axis: &Vector3D) -> Self {
27        let mut v = Vector6::zeros();
28        v.fixed_rows_mut::<3>(3).copy_from(&axis.0);
29        Self(v)
30    }
31
32    /// Creates a new `SpatialMotion` from a 3D translational axis (for prismatic joints).
33    #[must_use]
34    pub fn from_translational_axis(axis: &Vector3D) -> Self {
35        let mut v = Vector6::zeros();
36        v.fixed_rows_mut::<3>(0).copy_from(&axis.0);
37        Self(v)
38    }
39
40    /// Extracts the rotation (angular velocity) component of the spatial motion.
41    #[must_use]
42    pub fn rotation(&self) -> Vector3D {
43        Vector3D(self.0.fixed_rows::<3>(3).into())
44    }
45
46    /// Extracts the translation (linear velocity) component of the spatial motion.
47    #[must_use]
48    pub fn translation(&self) -> Vector3D {
49        Vector3D(self.0.fixed_rows::<3>(0).into())
50    }
51
52    /// Zero spatial motion (no motion).
53    #[must_use]
54    pub fn zero() -> Self {
55        Self(Vector6::zeros())
56    }
57
58    /// Creates a `SpatialMotion` from linear and angular components.
59    ///
60    /// # Arguments
61    /// * `linear` - The linear velocity component (3D vector).
62    /// * `angular` - The angular velocity component (3D vector).
63    #[must_use]
64    pub fn from_parts(linear: Vector3D, angular: Vector3D) -> Self {
65        let mut v = Vector6::zeros();
66        v.fixed_rows_mut::<3>(0).copy_from(&linear.0);
67        v.fixed_rows_mut::<3>(3).copy_from(&angular.0);
68        Self(v)
69    }
70
71    /// Creates a `SpatialMotion` from a 6D vector.
72    ///
73    /// # Arguments
74    /// * `v` - The 6D vector representing spatial motion; the first three elements are linear, the last three are angular.
75    #[must_use]
76    pub fn from_vector6d(v: Vector6D) -> Self {
77        Self(v.0)
78    }
79
80    /// Constructs the cross product matrix for spatial motion vectors.
81    ///
82    /// # Arguments
83    /// * `angular` - The angular velocity component (3D vector).
84    /// * `linear` - The linear velocity component (3D vector).
85    ///
86    /// # Returns
87    /// A 6x6 matrix representing the cross product operation.
88    fn cross_matrix(angular: Vector3D, linear: Vector3D) -> Matrix6<f64> {
89        let mut cross_matrix = Matrix6::zeros();
90        let angular_so3 = crate::so3::SO3::from_vector3d(&angular);
91        let linear_so3 = crate::so3::SO3::from_vector3d(&linear);
92        cross_matrix
93            .view_mut((0, 0), (3, 3))
94            .copy_from(&angular_so3.0);
95        cross_matrix
96            .view_mut((0, 3), (3, 3))
97            .copy_from(&linear_so3.0);
98        cross_matrix
99            .view_mut((3, 3), (3, 3))
100            .copy_from(&angular_so3.0);
101        cross_matrix
102    }
103
104    /// Computes the cross product of two spatial motion vectors.
105    ///
106    /// # Arguments
107    /// * `other` - The other spatial motion vector to compute the cross product with.
108    ///
109    /// # Returns
110    /// A new `SpatialMotion` representing the cross product.
111    #[must_use]
112    pub fn cross(&self, other: &SpatialMotion) -> SpatialMotion {
113        let angular_1 = self.rotation();
114        let linear_1 = self.translation();
115
116        let angular_2 = other.rotation();
117        let linear_2 = other.translation();
118
119        let cross_linear = angular_1.cross(&linear_2) + linear_1.cross(&angular_2);
120        let cross_angular = angular_1.cross(&angular_2);
121
122        SpatialMotion::from_parts(cross_linear, cross_angular)
123    }
124
125    /// Computes the cross product of two spatial motion vectors.
126    ///
127    /// # Arguments
128    /// * `other` - The other spatial motion vector to compute the cross product with.
129    ///
130    /// # Returns
131    /// A new `SpatialMotion` representing the cross product.
132    #[must_use]
133    pub fn cross_force(&self, other: &SpatialForce) -> SpatialForce {
134        let m_linear = self.translation();
135        let m_angular = self.rotation();
136
137        let f_linear = other.translation();
138        let f_angular = other.rotation();
139
140        let cross_linear = m_angular.cross(&f_linear);
141        let cross_angular = m_angular.cross(&f_angular) + m_linear.cross(&f_linear);
142
143        SpatialForce::from_parts(cross_linear, cross_angular)
144    }
145
146    /// Computes the dual cross product of two spatial motion vectors.
147    ///
148    /// # Arguments
149    /// * `other` - The other spatial motion vector to compute the dual cross product with.
150    ///
151    /// # Returns
152    /// A new `SpatialMotion` representing the dual cross product.
153    #[must_use]
154    pub fn cross_star(&self, other: &SpatialMotion) -> SpatialMotion {
155        let angular = self.rotation();
156        let linear = self.translation();
157
158        let cross_matrix = SpatialMotion::cross_matrix(angular, linear);
159        let dual_cross_matrix = -cross_matrix.transpose();
160
161        SpatialMotion(dual_cross_matrix * other.0)
162    }
163
164    /// Computes the inner product of two spatial motion vectors.
165    #[must_use]
166    pub fn inner(&self, other: &SpatialMotion) -> f64 {
167        self.0.dot(&other.0)
168    }
169
170    /// Returns the spatial motion as a slice of 6 elements (linear followed by angular).
171    pub fn as_slice(&self) -> &[f64; 6] {
172        self.0
173            .as_slice()
174            .try_into()
175            .expect("Vector6 should have exactly 6 elements")
176    }
177}
178
179impl Add for SpatialMotion {
180    type Output = SpatialMotion;
181
182    fn add(self, rhs: Self) -> Self::Output {
183        SpatialMotion(self.0 + rhs.0)
184    }
185}
186
187impl Add<&SpatialMotion> for SpatialMotion {
188    type Output = SpatialMotion;
189
190    fn add(self, rhs: &Self) -> Self::Output {
191        SpatialMotion(self.0 + rhs.0)
192    }
193}
194
195impl Add<SpatialMotion> for &SpatialMotion {
196    type Output = SpatialMotion;
197
198    fn add(self, rhs: SpatialMotion) -> Self::Output {
199        SpatialMotion(self.0 + rhs.0)
200    }
201}
202
203impl AddAssign for SpatialMotion {
204    fn add_assign(&mut self, rhs: Self) {
205        self.0 += rhs.0;
206    }
207}
208
209impl AddAssign<&SpatialMotion> for SpatialMotion {
210    fn add_assign(&mut self, rhs: &Self) {
211        self.0 += rhs.0;
212    }
213}
214
215impl Mul<f64> for SpatialMotion {
216    type Output = SpatialMotion;
217
218    fn mul(self, rhs: f64) -> Self::Output {
219        SpatialMotion(self.0 * rhs)
220    }
221}
222
223impl Mul<f64> for &SpatialMotion {
224    type Output = SpatialMotion;
225
226    fn mul(self, rhs: f64) -> Self::Output {
227        SpatialMotion(self.0 * rhs)
228    }
229}
230
231impl Mul<SpatialMotion> for f64 {
232    type Output = SpatialMotion;
233
234    fn mul(self, rhs: SpatialMotion) -> Self::Output {
235        SpatialMotion(rhs.0 * self)
236    }
237}
238
239impl ActSE3 for SpatialMotion {
240    fn act(&self, se3: &SE3) -> Self {
241        let angular = se3.rotation() * &self.rotation();
242        let linear = se3.rotation() * &self.translation() + se3.translation().cross(&angular);
243        SpatialMotion::from_parts(linear, angular)
244    }
245
246    fn act_inv(&self, se3: &SE3) -> Self {
247        let angular = se3.rotation().transpose() * &self.rotation();
248        let linear = se3.rotation().transpose()
249            * &(self.translation() - se3.translation().cross(&self.rotation()));
250        SpatialMotion::from_parts(linear, angular)
251    }
252}
253
254impl Display for SpatialMotion {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        write!(
257            f,
258            "SpatialMotion(linear: [{:.4}, {:.4}, {:.4}], angular: [{:.4}, {:.4}, {:.4}])",
259            self.translation().0[0],
260            self.translation().0[1],
261            self.translation().0[2],
262            self.rotation().0[0],
263            self.rotation().0[1],
264            self.rotation().0[2],
265        )
266    }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq)]
270/// Spatial rotation, represented as a 3x3 rotation matrix.
271pub struct SpatialRotation(pub(crate) Rotation3<f64>);
272
273impl SpatialRotation {
274    /// Creates a new `SpatialRotation` from a 3D axis and an angle (for revolute joints).
275    #[must_use]
276    pub fn from_axis_angle(axis: &Vector3D, angle: f64) -> Self {
277        let rot = Rotation3::from_axis_angle(&nalgebra::Unit::new_normalize(axis.0), angle);
278        Self(rot)
279    }
280
281    /// Converts the rotation to an [`SE3`] with the given translation.
282    #[must_use]
283    pub fn to_se3(&self, translation: &Vector3D) -> SE3 {
284        SE3::from_parts(*translation, *self)
285    }
286
287    /// Returns the identity rotation.
288    #[must_use]
289    pub fn identity() -> Self {
290        Self(Rotation3::identity())
291    }
292
293    /// Returns the angle of rotation in radians.
294    #[must_use]
295    pub fn angle(&self) -> f64 {
296        self.0.angle()
297    }
298
299    #[must_use]
300    /// Construct a [`SpatialRotation`] from the given Euler angles.
301    pub fn from_euler_angles(roll: f64, pitch: f64, yaw: f64) -> Self {
302        Self(Rotation3::from_euler_angles(roll, pitch, yaw))
303    }
304
305    /// Returns the transpose of the rotation matrix.
306    pub fn transpose(&self) -> Self {
307        Self(self.0.transpose())
308    }
309}
310
311impl Mul<&Vector3D> for SpatialRotation {
312    type Output = Vector3D;
313
314    fn mul(self, rhs: &Vector3D) -> Self::Output {
315        Vector3D(self.0 * rhs.0)
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use crate::so3::SO3;
322    use approx::assert_relative_eq;
323    use nalgebra::{Matrix3, Matrix6};
324
325    use super::*;
326
327    #[test]
328    fn test_spatial_motion_zero() {
329        let zero = SpatialMotion::zero();
330        assert_eq!(zero.0, Vector6::zeros());
331    }
332
333    #[test]
334    fn test_spatial_rotation_identity() {
335        let identity = SpatialRotation::identity();
336        assert_eq!(identity.0, Rotation3::identity());
337    }
338
339    #[test]
340    fn test_spatial_rotation_pi_2() {
341        let z = Vector3D::new(0.0, 0.0, 1.0);
342        let rotation = SpatialRotation::from_axis_angle(&z, std::f64::consts::PI / 2.0);
343        let expected = Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
344        assert_relative_eq!(rotation.0.matrix(), &expected);
345    }
346
347    #[test]
348    fn test_cross() {
349        let angular1 = Vector3D::new(1.0, 2.0, 3.0);
350        let linear1 = Vector3D::new(4.0, 5.0, 6.0);
351        let motion1 = SpatialMotion::from_parts(linear1, angular1);
352
353        let angular2 = Vector3D::new(7.0, 8.0, 9.0);
354        let linear2 = Vector3D::new(10.0, 11.0, 12.0);
355        let motion2 = SpatialMotion::from_parts(angular2, linear2);
356
357        let mut matrix = Matrix6::zeros();
358        let angular_so3 = SO3::from_vector3d(&angular1);
359        let linear_so3 = SO3::from_vector3d(&linear1);
360        matrix.view_mut((0, 0), (3, 3)).copy_from(&angular_so3.0);
361        matrix.view_mut((0, 3), (3, 3)).copy_from(&linear_so3.0);
362        matrix.view_mut((3, 3), (3, 3)).copy_from(&angular_so3.0);
363
364        let expected_cross = SpatialMotion(matrix * motion2.0);
365        let result_cross = motion1.cross(&motion2);
366        let expected_cross_star = SpatialMotion(-matrix.transpose() * motion2.0);
367        let result_cross_star = motion1.cross_star(&motion2);
368
369        assert_relative_eq!(result_cross.0, expected_cross.0);
370        assert_relative_eq!(result_cross_star.0, expected_cross_star.0);
371    }
372}