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}