spatial_math/exts.rs
1// Copyright (C) 2020-2025 spatial-math authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Extension traits for spatial algebra operations.
16//!
17//! This module provides utility traits and methods that extend the functionality
18//! of basic vector and matrix types for use in spatial algebra computations.
19//! These extensions bridge the gap between general linear algebra and the
20//! specific requirements of Featherstone's spatial vector algebra.
21
22use na::Dim;
23
24use crate::{Mat3, MatrixView, MatrixViewMut, Real, Vec3, Vec4};
25
26/// Extension trait for 3D vectors to support cross-product matrix operations.
27///
28/// The cross-product matrix (also known as skew-symmetric matrix) is a fundamental
29/// tool in spatial algebra for converting cross products into matrix multiplications.
30/// This enables efficient computation of spatial transformations and dynamics.
31pub trait Vec3Ext {
32 /// Convert a 3D vector to its cross-product matrix.
33 ///
34 /// The cross-product matrix \[v\]× satisfies: \[v\] × u = v × u for any vector u
35 ///
36 /// # Mathematical Definition
37 ///
38 /// For vector v = [x, y, z]ᵀ, the cross-product matrix is:
39 /// ```text
40 /// [v]× = | 0 -z y |
41 /// | z 0 -x |
42 /// | -y x 0 |
43 /// ```
44 fn into_cross_matrix(self) -> Mat3;
45}
46
47impl Vec3Ext for Vec3 {
48 #[inline]
49 fn into_cross_matrix(self) -> Mat3 {
50 // Construct the skew-symmetric cross-product matrix
51 // | 0 -z y |
52 // | z 0 -x |
53 // | -y x 0 |
54 Mat3::from_columns(&[
55 na::vector![0.0, self.z, -self.y],
56 na::vector![-self.z, 0.0, self.x],
57 na::vector![self.y, -self.x, 0.0],
58 ])
59 }
60}
61
62/// Extension trait for unit quaternions with spatial algebra utilities.
63///
64/// This trait provides additional methods for creating and validating unit quaternions
65/// specifically tailored for use in spatial transformations and dynamics calculations.
66pub trait UnitQuatExt {
67 /// Create a unit quaternion from a 4-element array without validation.
68 ///
69 /// # Safety
70 ///
71 /// This function assumes the input represents a valid unit quaternion.
72 /// Use with caution and only when you're certain the input is normalized.
73 ///
74 /// # Arguments
75 ///
76 /// * `array` - [x, y, z, w] quaternion components (w is scalar part)
77 fn from_array_unchecked(array: [Real; 4]) -> Self;
78
79 /// Create a unit quaternion from a 4D vector without validation.
80 ///
81 /// # Safety
82 ///
83 /// This function assumes the input vector represents a valid unit quaternion.
84 /// Use with caution and only when you're certain the input is normalized.
85 /// # Arguments
86 ///
87 /// * `v` - 4D vector containing [x, y, z, w] quaternion components
88 fn from_vec4_unchecked(v: Vec4) -> Self;
89
90 /// Check if the array represents a valid unit quaternion.
91 ///
92 /// Validates that the quaternion has unit length within the specified tolerance.
93 /// This is useful for input validation and debugging quaternion-related issues.
94 ///
95 /// # Arguments
96 ///
97 /// * `array` - The [x, y, z, w] array to check
98 /// * `eps` - The tolerance for checking the unit length (typically 1e-6 or smaller)
99 ///
100 /// # Returns
101 ///
102 /// `true` if the quaternion is normalized within tolerance, `false` otherwise
103 fn is_valid(array: [Real; 4], eps: Real) -> bool;
104}
105
106impl UnitQuatExt for na::UnitQuaternion<Real> {
107 #[inline]
108 fn from_array_unchecked(array: [Real; 4]) -> Self {
109 na::UnitQuaternion::new_unchecked(na::Quaternion::from(array))
110 }
111
112 #[inline]
113 fn from_vec4_unchecked(v: Vec4) -> Self {
114 na::UnitQuaternion::new_unchecked(na::Quaternion::from(v))
115 }
116
117 #[inline]
118 fn is_valid(array: [Real; 4], eps: Real) -> bool {
119 let length = Vec4::from(array).norm();
120 (length - 1.0).abs() < eps
121 }
122}
123
124pub trait ViewMatrix {
125 fn view_matrix<const R: usize, const C: usize>(
126 &self,
127 element_offset: usize,
128 ) -> MatrixView<R, C>;
129}
130
131pub trait ViewMatrixMut {
132 fn view_matrix_mut<const R: usize, const C: usize>(
133 &mut self,
134 element_offset: usize,
135 ) -> MatrixViewMut<R, C>;
136}
137
138macro_rules! impl_view_matrix {
139 ($type:ty) => {
140 impl ViewMatrix for $type {
141 #[inline]
142 fn view_matrix<const R: usize, const C: usize>(
143 &self,
144 element_offset: usize,
145 ) -> MatrixView<R, C> {
146 MatrixView::<R, C>::from_slice(&self[element_offset..])
147 }
148 }
149 };
150}
151
152macro_rules! impl_view_matrix_mut {
153 ($type:ty) => {
154 impl ViewMatrixMut for $type {
155 #[inline]
156 fn view_matrix_mut<const R: usize, const C: usize>(
157 &mut self,
158 element_offset: usize,
159 ) -> MatrixViewMut<R, C> {
160 MatrixViewMut::<R, C>::from_slice(&mut self[element_offset..])
161 }
162 }
163 };
164}
165
166impl_view_matrix! {[Real]}
167impl_view_matrix! {Vec<Real>}
168
169impl_view_matrix_mut! {[Real]}
170impl_view_matrix_mut! {Vec<Real>}
171
172pub trait MatrixExt {
173 /// Returns true if any element in the matrix is NaN.
174 fn any_nan(&self) -> bool;
175}
176
177impl<R, C, S> MatrixExt for na::Matrix<Real, R, C, S>
178where
179 R: na::Dim,
180 C: na::Dim,
181 S: na::RawStorage<Real, R, C>,
182{
183 #[inline]
184 fn any_nan(&self) -> bool {
185 self.iter().any(
186 #[inline]
187 |x| x.is_nan(),
188 )
189 }
190}
191
192pub trait SquareMatrixExt {
193 fn is_positive_definite(&self) -> bool;
194}
195
196impl<R, S> SquareMatrixExt for na::Matrix<Real, R, R, S>
197where
198 R: Dim,
199 S: na::RawStorage<Real, R, R>,
200{
201 #[inline]
202 fn is_positive_definite(&self) -> bool {
203 for size in 1..=self.ncols() {
204 let d = self.view((0, 0), (size, size)).determinant();
205 if d <= 0.0 {
206 return false;
207 }
208 }
209 true
210 }
211}
212
213pub trait RealSliceExt {
214 /// Returns a array of the given size starting at the given offset.
215 ///
216 /// # Panics
217 ///
218 /// Panics if there are not enough elements.
219 fn array<const N: usize>(&self, offset: usize) -> [Real; N];
220}
221
222impl RealSliceExt for [Real] {
223 #[inline]
224 fn array<const N: usize>(&self, offset: usize) -> [Real; N] {
225 self[offset..offset + N]
226 .try_into()
227 .expect("no enough elements")
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::Vec3Ext;
234 use crate::exts::SquareMatrixExt;
235 use crate::{SMatrix, vec3};
236
237 #[test]
238 fn test_cross_matrix() {
239 let v = vec3(1.0, 2.0, 3.0);
240 let m = v.into_cross_matrix();
241 assert_eq!(m.column(0), vec3(0.0, 3.0, -2.0));
242 assert_eq!(m.column(1), vec3(-3.0, 0.0, 1.0));
243 assert_eq!(m.column(2), vec3(2.0, -1.0, 0.0));
244
245 assert_eq!(m, v.cross_matrix());
246 }
247
248 #[test]
249 fn test_positive_define() {
250 let m = SMatrix::<1, 1>::new(-1.0);
251 assert!(!m.is_positive_definite());
252
253 let m = SMatrix::<2, 2>::new(1.0, 0.0, 0.0, 1.0);
254 assert!(m.is_positive_definite());
255 }
256}