franka/robot/target_control/
rotation.rs1use nalgebra::{Matrix3, Matrix4, Quaternion, Rotation3, UnitQuaternion, Vector3};
5
6use crate::error::{FrankaError, FrankaResult};
7use crate::math_utils::{linear_of, orthonormalized_rotation, pose_to_array};
8use crate::rate_limiting::scaled_axis;
9
10pub const ORTHONORMAL_TOLERANCE: f64 = 1e-3;
14
15pub const UNIT_QUATERNION_TOLERANCE: f64 = 1e-3;
18
19pub(super) fn rotation_of(pose: &[f64; 16]) -> Matrix3<f64> {
21 linear_of(&Matrix4::from_column_slice(pose))
22}
23
24pub(super) fn translation_of(pose: &[f64; 16]) -> [f64; 3] {
26 [pose[12], pose[13], pose[14]]
27}
28
29pub(super) fn pose_from(rotation: &Matrix3<f64>, translation: &[f64; 3]) -> [f64; 16] {
31 pose_to_array(rotation, &Vector3::from(*translation))
32}
33
34pub(super) fn orthonormality_error(m: &Matrix3<f64>) -> f64 {
37 let gram = m.transpose() * m - Matrix3::identity();
38 let worst = gram.iter().fold(0.0f64, |worst, x| worst.max(x.abs()));
39 worst.max((m.determinant() - 1.0).abs())
40}
41
42pub(super) fn to_quaternion(rotation: &Matrix3<f64>) -> [f64; 4] {
44 let q = UnitQuaternion::from_rotation_matrix(&Rotation3::from_matrix_unchecked(*rotation));
45 let sign = if q.w < 0.0 { -1.0 } else { 1.0 };
46 [sign * q.i, sign * q.j, sign * q.k, sign * q.w]
47}
48
49pub(super) fn from_quaternion(q: &[f64; 4]) -> Matrix3<f64> {
51 UnitQuaternion::from_quaternion(Quaternion::new(q[3], q[0], q[1], q[2]))
52 .to_rotation_matrix()
53 .into_inner()
54}
55
56pub(super) fn log(rotation: &Matrix3<f64>) -> [f64; 3] {
58 let v = scaled_axis(rotation);
59 [v[0], v[1], v[2]]
60}
61
62pub(super) fn exp(v: &[f64; 3]) -> Matrix3<f64> {
64 Rotation3::from_scaled_axis(Vector3::from(*v)).into_inner()
65}
66
67pub(super) fn angle_between(a: &Matrix3<f64>, b: &Matrix3<f64>) -> f64 {
69 norm(&log(&(b * a.transpose())))
70}
71
72pub(super) fn norm(v: &[f64; 3]) -> f64 {
73 (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
74}
75
76pub(super) fn distance(a: &[f64; 3], b: &[f64; 3]) -> f64 {
77 norm(&[a[0] - b[0], a[1] - b[1], a[2] - b[2]])
78}
79
80pub(super) fn unit_quaternion(q: [f64; 4]) -> FrankaResult<[f64; 4]> {
86 if q.iter().any(|x| !x.is_finite()) {
87 return Err(FrankaError::InvalidArgument(format!(
88 "target control: the orientation must be finite, got {q:?}"
89 )));
90 }
91 let norm = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
92 if (norm - 1.0).abs() > UNIT_QUATERNION_TOLERANCE {
93 return Err(FrankaError::InvalidArgument(format!(
94 "target control: the orientation {q:?} is not a unit quaternion (norm {norm}); \
95 the order is [x, y, z, w]"
96 )));
97 }
98 Ok(q.map(|x| x / norm))
99}
100
101pub(super) fn checked_pose(pose: &[f64; 16]) -> FrankaResult<([f64; 3], Matrix3<f64>)> {
107 if pose.iter().any(|x| !x.is_finite()) {
108 return Err(FrankaError::InvalidArgument(format!(
109 "target control: the pose must be finite, got {pose:?}"
110 )));
111 }
112 let last_row = [pose[3], pose[7], pose[11], pose[15]];
113 if last_row
114 .iter()
115 .zip(&[0.0, 0.0, 0.0, 1.0])
116 .any(|(a, b)| (a - b).abs() > 1e-6)
117 {
118 return Err(FrankaError::InvalidArgument(format!(
119 "target control: the pose must be a column-major homogeneous transform with the \
120 last row [0, 0, 0, 1], got {last_row:?}"
121 )));
122 }
123 let rotation = rotation_of(pose);
124 let error = orthonormality_error(&rotation);
125 if error > ORTHONORMAL_TOLERANCE {
126 return Err(FrankaError::InvalidArgument(format!(
127 "target control: the rotation block of the pose is not orthonormal (error {error:.3e} \
128 against a tolerance of {ORTHONORMAL_TOLERANCE:.0e}); a rotation matrix's columns \
129 must be unit length and mutually perpendicular, and the pose column-major"
130 )));
131 }
132 Ok((translation_of(pose), orthonormalized_rotation(&rotation)))
133}