nalgebra/geometry/rotation.rs
1use approx::{AbsDiffEq, RelativeEq, UlpsEq};
2use num::{One, Zero};
3use std::fmt;
4use std::hash;
5#[cfg(feature = "abomonation-serialize")]
6use std::io::{Result as IOResult, Write};
7
8#[cfg(feature = "serde-serialize-no-std")]
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11#[cfg(feature = "serde-serialize-no-std")]
12use crate::base::storage::Owned;
13
14#[cfg(feature = "abomonation-serialize")]
15use abomonation::Abomonation;
16
17use simba::scalar::RealField;
18use simba::simd::SimdRealField;
19
20use crate::base::allocator::Allocator;
21use crate::base::dimension::{DimNameAdd, DimNameSum, U1};
22use crate::base::{Const, DefaultAllocator, OMatrix, SMatrix, SVector, Scalar, Unit};
23use crate::geometry::Point;
24
25/// A rotation matrix.
26///
27/// This is also known as an element of a Special Orthogonal (SO) group.
28/// The `Rotation` type can either represent a 2D or 3D rotation, represented as a matrix.
29/// For a rotation based on quaternions, see [`UnitQuaternion`](crate::UnitQuaternion) instead.
30///
31/// Note that instead of using the [`Rotation`](crate::Rotation) type in your code directly, you should use one
32/// of its aliases: [`Rotation2`](crate::Rotation2), or [`Rotation3`](crate::Rotation3). Though
33/// keep in mind that all the documentation of all the methods of these aliases will also appears on
34/// this page.
35///
36/// # Construction
37/// * [Identity <span style="float:right;">`identity`</span>](#identity)
38/// * [From a 2D rotation angle <span style="float:right;">`new`…</span>](#construction-from-a-2d-rotation-angle)
39/// * [From an existing 2D matrix or rotations <span style="float:right;">`from_matrix`, `rotation_between`, `powf`…</span>](#construction-from-an-existing-2d-matrix-or-rotations)
40/// * [From a 3D axis and/or angles <span style="float:right;">`new`, `from_euler_angles`, `from_axis_angle`…</span>](#construction-from-a-3d-axis-andor-angles)
41/// * [From a 3D eye position and target point <span style="float:right;">`look_at`, `look_at_lh`, `rotation_between`…</span>](#construction-from-a-3d-eye-position-and-target-point)
42/// * [From an existing 3D matrix or rotations <span style="float:right;">`from_matrix`, `rotation_between`, `powf`…</span>](#construction-from-an-existing-3d-matrix-or-rotations)
43///
44/// # Transformation and composition
45/// Note that transforming vectors and points can be done by multiplication, e.g., `rotation * point`.
46/// Composing an rotation with another transformation can also be done by multiplication or division.
47/// * [3D axis and angle extraction <span style="float:right;">`angle`, `euler_angles`, `scaled_axis`, `angle_to`…</span>](#3d-axis-and-angle-extraction)
48/// * [2D angle extraction <span style="float:right;">`angle`, `angle_to`…</span>](#2d-angle-extraction)
49/// * [Transformation of a vector or a point <span style="float:right;">`transform_vector`, `inverse_transform_point`…</span>](#transformation-of-a-vector-or-a-point)
50/// * [Transposition and inversion <span style="float:right;">`transpose`, `inverse`…</span>](#transposition-and-inversion)
51/// * [Interpolation <span style="float:right;">`slerp`…</span>](#interpolation)
52///
53/// # Conversion
54/// * [Conversion to a matrix <span style="float:right;">`matrix`, `to_homogeneous`…</span>](#conversion-to-a-matrix)
55///
56#[repr(C)]
57#[cfg_attr(
58 all(not(target_os = "cuda"), feature = "cuda"),
59 derive(cust::DeviceCopy)
60)]
61#[derive(Copy, Clone)]
62pub struct Rotation<T, const D: usize> {
63 matrix: SMatrix<T, D, D>,
64}
65
66impl<T: fmt::Debug, const D: usize> fmt::Debug for Rotation<T, D> {
67 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
68 self.matrix.fmt(formatter)
69 }
70}
71
72impl<T: Scalar + hash::Hash, const D: usize> hash::Hash for Rotation<T, D>
73where
74 <DefaultAllocator as Allocator<T, Const<D>, Const<D>>>::Buffer: hash::Hash,
75{
76 fn hash<H: hash::Hasher>(&self, state: &mut H) {
77 self.matrix.hash(state)
78 }
79}
80
81#[cfg(feature = "bytemuck")]
82unsafe impl<T, const D: usize> bytemuck::Zeroable for Rotation<T, D>
83where
84 T: Scalar + bytemuck::Zeroable,
85 SMatrix<T, D, D>: bytemuck::Zeroable,
86{
87}
88
89#[cfg(feature = "bytemuck")]
90unsafe impl<T, const D: usize> bytemuck::Pod for Rotation<T, D>
91where
92 T: Scalar + bytemuck::Pod,
93 SMatrix<T, D, D>: bytemuck::Pod,
94{
95}
96
97#[cfg(feature = "abomonation-serialize")]
98impl<T, const D: usize> Abomonation for Rotation<T, D>
99where
100 T: Scalar,
101 SMatrix<T, D, D>: Abomonation,
102{
103 unsafe fn entomb<W: Write>(&self, writer: &mut W) -> IOResult<()> {
104 self.matrix.entomb(writer)
105 }
106
107 fn extent(&self) -> usize {
108 self.matrix.extent()
109 }
110
111 unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> {
112 self.matrix.exhume(bytes)
113 }
114}
115
116#[cfg(feature = "serde-serialize-no-std")]
117impl<T: Scalar, const D: usize> Serialize for Rotation<T, D>
118where
119 Owned<T, Const<D>, Const<D>>: Serialize,
120{
121 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122 where
123 S: Serializer,
124 {
125 self.matrix.serialize(serializer)
126 }
127}
128
129#[cfg(feature = "serde-serialize-no-std")]
130impl<'a, T: Scalar, const D: usize> Deserialize<'a> for Rotation<T, D>
131where
132 Owned<T, Const<D>, Const<D>>: Deserialize<'a>,
133{
134 fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
135 where
136 Des: Deserializer<'a>,
137 {
138 let matrix = SMatrix::<T, D, D>::deserialize(deserializer)?;
139
140 Ok(Self::from_matrix_unchecked(matrix))
141 }
142}
143
144impl<T, const D: usize> Rotation<T, D> {
145 /// Creates a new rotation from the given square matrix.
146 ///
147 /// The matrix orthonormality is not checked.
148 ///
149 /// # Example
150 /// ```
151 /// # use nalgebra::{Rotation2, Rotation3, Matrix2, Matrix3};
152 /// # use std::f32;
153 /// let mat = Matrix3::new(0.8660254, -0.5, 0.0,
154 /// 0.5, 0.8660254, 0.0,
155 /// 0.0, 0.0, 1.0);
156 /// let rot = Rotation3::from_matrix_unchecked(mat);
157 ///
158 /// assert_eq!(*rot.matrix(), mat);
159 ///
160 ///
161 /// let mat = Matrix2::new(0.8660254, -0.5,
162 /// 0.5, 0.8660254);
163 /// let rot = Rotation2::from_matrix_unchecked(mat);
164 ///
165 /// assert_eq!(*rot.matrix(), mat);
166 /// ```
167 #[inline]
168 pub const fn from_matrix_unchecked(matrix: SMatrix<T, D, D>) -> Self {
169 Self { matrix }
170 }
171}
172
173/// # Conversion to a matrix
174impl<T: Scalar, const D: usize> Rotation<T, D> {
175 /// A reference to the underlying matrix representation of this rotation.
176 ///
177 /// # Example
178 /// ```
179 /// # use nalgebra::{Rotation2, Rotation3, Vector3, Matrix2, Matrix3};
180 /// # use std::f32;
181 /// let rot = Rotation3::from_axis_angle(&Vector3::z_axis(), f32::consts::FRAC_PI_6);
182 /// let expected = Matrix3::new(0.8660254, -0.5, 0.0,
183 /// 0.5, 0.8660254, 0.0,
184 /// 0.0, 0.0, 1.0);
185 /// assert_eq!(*rot.matrix(), expected);
186 ///
187 ///
188 /// let rot = Rotation2::new(f32::consts::FRAC_PI_6);
189 /// let expected = Matrix2::new(0.8660254, -0.5,
190 /// 0.5, 0.8660254);
191 /// assert_eq!(*rot.matrix(), expected);
192 /// ```
193 #[inline]
194 #[must_use]
195 pub fn matrix(&self) -> &SMatrix<T, D, D> {
196 &self.matrix
197 }
198
199 /// A mutable reference to the underlying matrix representation of this rotation.
200 #[inline]
201 #[deprecated(note = "Use `.matrix_mut_unchecked()` instead.")]
202 pub unsafe fn matrix_mut(&mut self) -> &mut SMatrix<T, D, D> {
203 &mut self.matrix
204 }
205
206 /// A mutable reference to the underlying matrix representation of this rotation.
207 ///
208 /// This is suffixed by "_unchecked" because this allows the user to replace the
209 /// matrix by another one that is non-inversible or non-orthonormal. If one of
210 /// those properties is broken, subsequent method calls may return bogus results.
211 #[inline]
212 pub fn matrix_mut_unchecked(&mut self) -> &mut SMatrix<T, D, D> {
213 &mut self.matrix
214 }
215
216 /// Unwraps the underlying matrix.
217 ///
218 /// # Example
219 /// ```
220 /// # use nalgebra::{Rotation2, Rotation3, Vector3, Matrix2, Matrix3};
221 /// # use std::f32;
222 /// let rot = Rotation3::from_axis_angle(&Vector3::z_axis(), f32::consts::FRAC_PI_6);
223 /// let mat = rot.into_inner();
224 /// let expected = Matrix3::new(0.8660254, -0.5, 0.0,
225 /// 0.5, 0.8660254, 0.0,
226 /// 0.0, 0.0, 1.0);
227 /// assert_eq!(mat, expected);
228 ///
229 ///
230 /// let rot = Rotation2::new(f32::consts::FRAC_PI_6);
231 /// let mat = rot.into_inner();
232 /// let expected = Matrix2::new(0.8660254, -0.5,
233 /// 0.5, 0.8660254);
234 /// assert_eq!(mat, expected);
235 /// ```
236 #[inline]
237 pub fn into_inner(self) -> SMatrix<T, D, D> {
238 self.matrix
239 }
240
241 /// Unwraps the underlying matrix.
242 /// Deprecated: Use [`Rotation::into_inner`] instead.
243 #[deprecated(note = "use `.into_inner()` instead")]
244 #[inline]
245 pub fn unwrap(self) -> SMatrix<T, D, D> {
246 self.matrix
247 }
248
249 /// Converts this rotation into its equivalent homogeneous transformation matrix.
250 ///
251 /// This is the same as `self.into()`.
252 ///
253 /// # Example
254 /// ```
255 /// # use nalgebra::{Rotation2, Rotation3, Vector3, Matrix3, Matrix4};
256 /// # use std::f32;
257 /// let rot = Rotation3::from_axis_angle(&Vector3::z_axis(), f32::consts::FRAC_PI_6);
258 /// let expected = Matrix4::new(0.8660254, -0.5, 0.0, 0.0,
259 /// 0.5, 0.8660254, 0.0, 0.0,
260 /// 0.0, 0.0, 1.0, 0.0,
261 /// 0.0, 0.0, 0.0, 1.0);
262 /// assert_eq!(rot.to_homogeneous(), expected);
263 ///
264 ///
265 /// let rot = Rotation2::new(f32::consts::FRAC_PI_6);
266 /// let expected = Matrix3::new(0.8660254, -0.5, 0.0,
267 /// 0.5, 0.8660254, 0.0,
268 /// 0.0, 0.0, 1.0);
269 /// assert_eq!(rot.to_homogeneous(), expected);
270 /// ```
271 #[inline]
272 #[must_use]
273 pub fn to_homogeneous(&self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
274 where
275 T: Zero + One,
276 Const<D>: DimNameAdd<U1>,
277 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
278 {
279 // We could use `SMatrix::to_homogeneous()` here, but that would imply
280 // adding the additional traits `DimAdd` and `IsNotStaticOne`. Maybe
281 // these things will get nicer once specialization lands in Rust.
282 let mut res = OMatrix::<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>::identity();
283 res.fixed_slice_mut::<D, D>(0, 0).copy_from(&self.matrix);
284
285 res
286 }
287}
288
289/// # Transposition and inversion
290impl<T: Scalar, const D: usize> Rotation<T, D> {
291 /// Transposes `self`.
292 ///
293 /// Same as `.inverse()` because the inverse of a rotation matrix is its transform.
294 ///
295 /// # Example
296 /// ```
297 /// # #[macro_use] extern crate approx;
298 /// # use nalgebra::{Rotation2, Rotation3, Vector3};
299 /// let rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
300 /// let tr_rot = rot.transpose();
301 /// assert_relative_eq!(rot * tr_rot, Rotation3::identity(), epsilon = 1.0e-6);
302 /// assert_relative_eq!(tr_rot * rot, Rotation3::identity(), epsilon = 1.0e-6);
303 ///
304 /// let rot = Rotation2::new(1.2);
305 /// let tr_rot = rot.transpose();
306 /// assert_relative_eq!(rot * tr_rot, Rotation2::identity(), epsilon = 1.0e-6);
307 /// assert_relative_eq!(tr_rot * rot, Rotation2::identity(), epsilon = 1.0e-6);
308 /// ```
309 #[inline]
310 #[must_use = "Did you mean to use transpose_mut()?"]
311 pub fn transpose(&self) -> Self {
312 Self::from_matrix_unchecked(self.matrix.transpose())
313 }
314
315 /// Inverts `self`.
316 ///
317 /// Same as `.transpose()` because the inverse of a rotation matrix is its transform.
318 ///
319 /// # Example
320 /// ```
321 /// # #[macro_use] extern crate approx;
322 /// # use nalgebra::{Rotation2, Rotation3, Vector3};
323 /// let rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
324 /// let inv = rot.inverse();
325 /// assert_relative_eq!(rot * inv, Rotation3::identity(), epsilon = 1.0e-6);
326 /// assert_relative_eq!(inv * rot, Rotation3::identity(), epsilon = 1.0e-6);
327 ///
328 /// let rot = Rotation2::new(1.2);
329 /// let inv = rot.inverse();
330 /// assert_relative_eq!(rot * inv, Rotation2::identity(), epsilon = 1.0e-6);
331 /// assert_relative_eq!(inv * rot, Rotation2::identity(), epsilon = 1.0e-6);
332 /// ```
333 #[inline]
334 #[must_use = "Did you mean to use inverse_mut()?"]
335 pub fn inverse(&self) -> Self {
336 self.transpose()
337 }
338
339 /// Transposes `self` in-place.
340 ///
341 /// Same as `.inverse_mut()` because the inverse of a rotation matrix is its transform.
342 ///
343 /// # Example
344 /// ```
345 /// # #[macro_use] extern crate approx;
346 /// # use nalgebra::{Rotation2, Rotation3, Vector3};
347 /// let rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
348 /// let mut tr_rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
349 /// tr_rot.transpose_mut();
350 ///
351 /// assert_relative_eq!(rot * tr_rot, Rotation3::identity(), epsilon = 1.0e-6);
352 /// assert_relative_eq!(tr_rot * rot, Rotation3::identity(), epsilon = 1.0e-6);
353 ///
354 /// let rot = Rotation2::new(1.2);
355 /// let mut tr_rot = Rotation2::new(1.2);
356 /// tr_rot.transpose_mut();
357 ///
358 /// assert_relative_eq!(rot * tr_rot, Rotation2::identity(), epsilon = 1.0e-6);
359 /// assert_relative_eq!(tr_rot * rot, Rotation2::identity(), epsilon = 1.0e-6);
360 /// ```
361 #[inline]
362 pub fn transpose_mut(&mut self) {
363 self.matrix.transpose_mut()
364 }
365
366 /// Inverts `self` in-place.
367 ///
368 /// Same as `.transpose_mut()` because the inverse of a rotation matrix is its transform.
369 ///
370 /// # Example
371 /// ```
372 /// # #[macro_use] extern crate approx;
373 /// # use nalgebra::{Rotation2, Rotation3, Vector3};
374 /// let rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
375 /// let mut inv = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
376 /// inv.inverse_mut();
377 ///
378 /// assert_relative_eq!(rot * inv, Rotation3::identity(), epsilon = 1.0e-6);
379 /// assert_relative_eq!(inv * rot, Rotation3::identity(), epsilon = 1.0e-6);
380 ///
381 /// let rot = Rotation2::new(1.2);
382 /// let mut inv = Rotation2::new(1.2);
383 /// inv.inverse_mut();
384 ///
385 /// assert_relative_eq!(rot * inv, Rotation2::identity(), epsilon = 1.0e-6);
386 /// assert_relative_eq!(inv * rot, Rotation2::identity(), epsilon = 1.0e-6);
387 /// ```
388 #[inline]
389 pub fn inverse_mut(&mut self) {
390 self.transpose_mut()
391 }
392}
393
394/// # Transformation of a vector or a point
395impl<T: SimdRealField, const D: usize> Rotation<T, D>
396where
397 T::Element: SimdRealField,
398{
399 /// Rotate the given point.
400 ///
401 /// This is the same as the multiplication `self * pt`.
402 ///
403 /// # Example
404 /// ```
405 /// # #[macro_use] extern crate approx;
406 /// # use std::f32;
407 /// # use nalgebra::{Point3, Rotation2, Rotation3, UnitQuaternion, Vector3};
408 /// let rot = Rotation3::new(Vector3::y() * f32::consts::FRAC_PI_2);
409 /// let transformed_point = rot.transform_point(&Point3::new(1.0, 2.0, 3.0));
410 ///
411 /// assert_relative_eq!(transformed_point, Point3::new(3.0, 2.0, -1.0), epsilon = 1.0e-6);
412 /// ```
413 #[inline]
414 #[must_use]
415 pub fn transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
416 self * pt
417 }
418
419 /// Rotate the given vector.
420 ///
421 /// This is the same as the multiplication `self * v`.
422 ///
423 /// # Example
424 /// ```
425 /// # #[macro_use] extern crate approx;
426 /// # use std::f32;
427 /// # use nalgebra::{Rotation2, Rotation3, UnitQuaternion, Vector3};
428 /// let rot = Rotation3::new(Vector3::y() * f32::consts::FRAC_PI_2);
429 /// let transformed_vector = rot.transform_vector(&Vector3::new(1.0, 2.0, 3.0));
430 ///
431 /// assert_relative_eq!(transformed_vector, Vector3::new(3.0, 2.0, -1.0), epsilon = 1.0e-6);
432 /// ```
433 #[inline]
434 #[must_use]
435 pub fn transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
436 self * v
437 }
438
439 /// Rotate the given point by the inverse of this rotation. This may be
440 /// cheaper than inverting the rotation and then transforming the given
441 /// point.
442 ///
443 /// # Example
444 /// ```
445 /// # #[macro_use] extern crate approx;
446 /// # use std::f32;
447 /// # use nalgebra::{Point3, Rotation2, Rotation3, UnitQuaternion, Vector3};
448 /// let rot = Rotation3::new(Vector3::y() * f32::consts::FRAC_PI_2);
449 /// let transformed_point = rot.inverse_transform_point(&Point3::new(1.0, 2.0, 3.0));
450 ///
451 /// assert_relative_eq!(transformed_point, Point3::new(-3.0, 2.0, 1.0), epsilon = 1.0e-6);
452 /// ```
453 #[inline]
454 #[must_use]
455 pub fn inverse_transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
456 Point::from(self.inverse_transform_vector(&pt.coords))
457 }
458
459 /// Rotate the given vector by the inverse of this rotation. This may be
460 /// cheaper than inverting the rotation and then transforming the given
461 /// vector.
462 ///
463 /// # Example
464 /// ```
465 /// # #[macro_use] extern crate approx;
466 /// # use std::f32;
467 /// # use nalgebra::{Rotation2, Rotation3, UnitQuaternion, Vector3};
468 /// let rot = Rotation3::new(Vector3::y() * f32::consts::FRAC_PI_2);
469 /// let transformed_vector = rot.inverse_transform_vector(&Vector3::new(1.0, 2.0, 3.0));
470 ///
471 /// assert_relative_eq!(transformed_vector, Vector3::new(-3.0, 2.0, 1.0), epsilon = 1.0e-6);
472 /// ```
473 #[inline]
474 #[must_use]
475 pub fn inverse_transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
476 self.matrix().tr_mul(v)
477 }
478
479 /// Rotate the given vector by the inverse of this rotation. This may be
480 /// cheaper than inverting the rotation and then transforming the given
481 /// vector.
482 ///
483 /// # Example
484 /// ```
485 /// # #[macro_use] extern crate approx;
486 /// # use std::f32;
487 /// # use nalgebra::{Rotation2, Rotation3, UnitQuaternion, Vector3};
488 /// let rot = Rotation3::new(Vector3::z() * f32::consts::FRAC_PI_2);
489 /// let transformed_vector = rot.inverse_transform_unit_vector(&Vector3::x_axis());
490 ///
491 /// assert_relative_eq!(transformed_vector, -Vector3::y_axis(), epsilon = 1.0e-6);
492 /// ```
493 #[inline]
494 #[must_use]
495 pub fn inverse_transform_unit_vector(&self, v: &Unit<SVector<T, D>>) -> Unit<SVector<T, D>> {
496 Unit::new_unchecked(self.inverse_transform_vector(&**v))
497 }
498}
499
500impl<T: Scalar + Eq, const D: usize> Eq for Rotation<T, D> {}
501
502impl<T: Scalar + PartialEq, const D: usize> PartialEq for Rotation<T, D> {
503 #[inline]
504 fn eq(&self, right: &Self) -> bool {
505 self.matrix == right.matrix
506 }
507}
508
509impl<T, const D: usize> AbsDiffEq for Rotation<T, D>
510where
511 T: Scalar + AbsDiffEq,
512 T::Epsilon: Clone,
513{
514 type Epsilon = T::Epsilon;
515
516 #[inline]
517 fn default_epsilon() -> Self::Epsilon {
518 T::default_epsilon()
519 }
520
521 #[inline]
522 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
523 self.matrix.abs_diff_eq(&other.matrix, epsilon)
524 }
525}
526
527impl<T, const D: usize> RelativeEq for Rotation<T, D>
528where
529 T: Scalar + RelativeEq,
530 T::Epsilon: Clone,
531{
532 #[inline]
533 fn default_max_relative() -> Self::Epsilon {
534 T::default_max_relative()
535 }
536
537 #[inline]
538 fn relative_eq(
539 &self,
540 other: &Self,
541 epsilon: Self::Epsilon,
542 max_relative: Self::Epsilon,
543 ) -> bool {
544 self.matrix
545 .relative_eq(&other.matrix, epsilon, max_relative)
546 }
547}
548
549impl<T, const D: usize> UlpsEq for Rotation<T, D>
550where
551 T: Scalar + UlpsEq,
552 T::Epsilon: Clone,
553{
554 #[inline]
555 fn default_max_ulps() -> u32 {
556 T::default_max_ulps()
557 }
558
559 #[inline]
560 fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
561 self.matrix.ulps_eq(&other.matrix, epsilon, max_ulps)
562 }
563}
564
565/*
566 *
567 * Display
568 *
569 */
570impl<T, const D: usize> fmt::Display for Rotation<T, D>
571where
572 T: RealField + fmt::Display,
573{
574 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575 let precision = f.precision().unwrap_or(3);
576
577 writeln!(f, "Rotation matrix {{")?;
578 write!(f, "{:.*}", precision, self.matrix)?;
579 writeln!(f, "}}")
580 }
581}
582
583// // /*
584// // *
585// // * Absolute
586// // *
587// // */
588// // impl<T: Absolute> Absolute for $t<T> {
589// // type AbsoluteValue = $submatrix<T::AbsoluteValue>;
590// //
591// // #[inline]
592// // fn abs(m: &$t<T>) -> $submatrix<T::AbsoluteValue> {
593// // Absolute::abs(&m.submatrix)
594// // }
595// // }