1use 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)]
17pub struct SpatialMotion(pub Vector6<f64>);
21impl SpatialMotion {
24 #[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 #[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 #[must_use]
42 pub fn rotation(&self) -> Vector3D {
43 Vector3D(self.0.fixed_rows::<3>(3).into())
44 }
45
46 #[must_use]
48 pub fn translation(&self) -> Vector3D {
49 Vector3D(self.0.fixed_rows::<3>(0).into())
50 }
51
52 #[must_use]
54 pub fn zero() -> Self {
55 Self(Vector6::zeros())
56 }
57
58 #[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 #[must_use]
76 pub fn from_vector6d(v: Vector6D) -> Self {
77 Self(v.0)
78 }
79
80 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 #[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 #[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 #[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 #[must_use]
166 pub fn inner(&self, other: &SpatialMotion) -> f64 {
167 self.0.dot(&other.0)
168 }
169
170 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)]
270pub struct SpatialRotation(pub(crate) Rotation3<f64>);
272
273impl SpatialRotation {
274 #[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 #[must_use]
283 pub fn to_se3(&self, translation: &Vector3D) -> SE3 {
284 SE3::from_parts(*translation, *self)
285 }
286
287 #[must_use]
289 pub fn identity() -> Self {
290 Self(Rotation3::identity())
291 }
292
293 #[must_use]
295 pub fn angle(&self) -> f64 {
296 self.0.angle()
297 }
298
299 #[must_use]
300 pub fn from_euler_angles(roll: f64, pitch: f64, yaw: f64) -> Self {
302 Self(Rotation3::from_euler_angles(roll, pitch, yaw))
303 }
304
305 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}