From 67603be6ed435947f9bc24ead3cdc9df9be9f0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Fri, 18 Dec 2020 11:38:33 +0100 Subject: [PATCH 1/8] Fix the PartialEq impl for quaternions. The double-covering property should only be taken into account for `UnitQuaternion` instead of `Quaternion` itself. --- src/base/unit.rs | 27 +++++++++++++- src/geometry/quaternion.rs | 54 ++++++++++----------------- src/geometry/quaternion_conversion.rs | 10 ++--- src/geometry/unit_complex.rs | 11 ++++++ tests/geometry/quaternion.rs | 1 - 5 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/base/unit.rs b/src/base/unit.rs index 20ca4603..2483307a 100644 --- a/src/base/unit.rs +++ b/src/base/unit.rs @@ -11,7 +11,8 @@ use abomonation::Abomonation; use crate::allocator::Allocator; use crate::base::DefaultAllocator; -use crate::{Dim, MatrixMN, RealField, Scalar, SimdComplexField, SimdRealField}; +use crate::storage::Storage; +use crate::{Dim, Matrix, MatrixMN, RealField, Scalar, SimdComplexField, SimdRealField}; /// A wrapper that ensures the underlying algebraic entity has a unit norm. /// @@ -24,7 +25,7 @@ use crate::{Dim, MatrixMN, RealField, Scalar, SimdComplexField, SimdRealField}; /// and [`UnitQuaternion`](crate::UnitQuaternion); both built on top of `Unit`. If you are interested /// in their documentation, read their dedicated pages directly. #[repr(transparent)] -#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)] +#[derive(Clone, Hash, Debug, Copy)] pub struct Unit { pub(crate) value: T, } @@ -64,6 +65,28 @@ impl Abomonation for Unit { } } +impl PartialEq for Unit> +where + N: Scalar + PartialEq, + R: Dim, + C: Dim, + S: Storage, +{ + #[inline] + fn eq(&self, rhs: &Self) -> bool { + self.value.eq(&rhs.value) + } +} + +impl Eq for Unit> +where + N: Scalar + Eq, + R: Dim, + C: Dim, + S: Storage, +{ +} + /// Trait implemented by entities scan be be normalized and put in an `Unit` struct. pub trait Normed { /// The type of the norm. diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index e860a8b8..c8fd80be 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "abomonation-serialize")] use abomonation::Abomonation; -use simba::scalar::RealField; +use simba::scalar::{ClosedNeg, RealField}; use simba::simd::{SimdBool, SimdOption, SimdRealField, SimdValue}; use crate::base::dimension::{U1, U3, U4}; @@ -23,17 +23,18 @@ use crate::base::{ }; use crate::geometry::{Point3, Rotation}; +use std::ops::Neg; /// A quaternion. See the type alias `UnitQuaternion = Unit` for a quaternion /// that may be used as a rotation. #[repr(C)] -#[derive(Debug)] -pub struct Quaternion { +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct Quaternion { /// This quaternion as a 4D vector of coordinates in the `[ x, y, z, w ]` storage order. pub coords: Vector4, } -impl Default for Quaternion { +impl Default for Quaternion { fn default() -> Self { Quaternion { coords: Vector4::zeros(), @@ -42,7 +43,7 @@ impl Default for Quaternion { } #[cfg(feature = "abomonation-serialize")] -impl Abomonation for Quaternion +impl Abomonation for Quaternion where Vector4: Abomonation, { @@ -59,36 +60,8 @@ where } } -impl Eq for Quaternion where N::Element: SimdRealField {} - -impl PartialEq for Quaternion -where - N::Element: SimdRealField, -{ - fn eq(&self, rhs: &Self) -> bool { - self.coords == rhs.coords || - // Account for the double-covering of S², i.e. q = -q - self.as_vector().iter().zip(rhs.as_vector().iter()).all(|(a, b)| *a == -*b) - } -} - -impl hash::Hash for Quaternion { - fn hash(&self, state: &mut H) { - self.coords.hash(state) - } -} - -impl Copy for Quaternion {} - -impl Clone for Quaternion { - #[inline] - fn clone(&self) -> Self { - Self::from(self.coords.clone()) - } -} - #[cfg(feature = "serde-serialize")] -impl Serialize for Quaternion +impl Serialize for Quaternion where Owned: Serialize, { @@ -101,7 +74,7 @@ where } #[cfg(feature = "serde-serialize")] -impl<'a, N: SimdRealField> Deserialize<'a> for Quaternion +impl<'a, N: Scalar> Deserialize<'a> for Quaternion where Owned: Deserialize<'a>, { @@ -980,6 +953,17 @@ impl fmt::Display for Quaternion { /// A unit quaternions. May be used to represent a rotation. pub type UnitQuaternion = Unit>; +impl PartialEq for UnitQuaternion { + #[inline] + fn eq(&self, rhs: &Self) -> bool { + self.coords == rhs.coords || + // Account for the double-covering of S², i.e. q = -q + self.coords.iter().zip(rhs.coords.iter()).all(|(a, b)| *a == -b.inlined_clone()) + } +} + +impl Eq for UnitQuaternion {} + impl Normed for Quaternion { type Norm = N::SimdRealField; diff --git a/src/geometry/quaternion_conversion.rs b/src/geometry/quaternion_conversion.rs index 330c97e4..7f276e99 100644 --- a/src/geometry/quaternion_conversion.rs +++ b/src/geometry/quaternion_conversion.rs @@ -184,14 +184,14 @@ impl> SubsetOf> for Un } #[cfg(feature = "mint")] -impl From> for Quaternion { +impl From> for Quaternion { fn from(q: mint::Quaternion) -> Self { Self::new(q.s, q.v.x, q.v.y, q.v.z) } } #[cfg(feature = "mint")] -impl Into> for Quaternion { +impl Into> for Quaternion { fn into(self) -> mint::Quaternion { mint::Quaternion { v: mint::Vector3 { @@ -205,7 +205,7 @@ impl Into> for Quaternion { } #[cfg(feature = "mint")] -impl Into> for UnitQuaternion { +impl Into> for UnitQuaternion { fn into(self) -> mint::Quaternion { mint::Quaternion { v: mint::Vector3 { @@ -258,14 +258,14 @@ where } } -impl From> for Quaternion { +impl From> for Quaternion { #[inline] fn from(coords: Vector4) -> Self { Self { coords } } } -impl From<[N; 4]> for Quaternion { +impl From<[N; 4]> for Quaternion { #[inline] fn from(coords: [N; 4]) -> Self { Self { diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 40148454..43ef7324 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -4,8 +4,10 @@ use std::fmt; use crate::base::{Matrix2, Matrix3, Normed, Unit, Vector1, Vector2}; use crate::geometry::{Point2, Rotation2}; +use crate::Scalar; use simba::scalar::RealField; use simba::simd::SimdRealField; +use std::cmp::{Eq, PartialEq}; /// A 2D rotation represented as a complex number with magnitude 1. /// @@ -29,6 +31,15 @@ use simba::simd::SimdRealField; /// * [Conversion to a matrix `to_rotation_matrix`, `to_homogeneous`…](#conversion-to-a-matrix) pub type UnitComplex = Unit>; +impl PartialEq for UnitComplex { + #[inline] + fn eq(&self, rhs: &Self) -> bool { + (**self).eq(&**rhs) + } +} + +impl Eq for UnitComplex {} + impl Normed for Complex { type Norm = N::SimdRealField; diff --git a/tests/geometry/quaternion.rs b/tests/geometry/quaternion.rs index 77456ca0..5ff20a0e 100644 --- a/tests/geometry/quaternion.rs +++ b/tests/geometry/quaternion.rs @@ -114,7 +114,6 @@ quickcheck!( */ fn unit_quaternion_double_covering(q: UnitQuaternion) -> bool { let mq = UnitQuaternion::new_unchecked(-q.into_inner()); - mq == q && mq.angle() == q.angle() && mq.axis() == q.axis() } From ed74ff7c99a4b96ff2f76e98a506b7fdc489cb20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Fri, 18 Dec 2020 12:06:27 +0100 Subject: [PATCH 2/8] Simplify trait bounds for quaternion indexing. --- src/geometry/quaternion_construction.rs | 2 +- src/geometry/quaternion_conversion.rs | 18 +++++++++--------- src/geometry/quaternion_ops.rs | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index 63f00636..7d0cca8c 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -19,7 +19,7 @@ use crate::{Scalar, SimdRealField}; use crate::geometry::{Quaternion, Rotation3, UnitQuaternion}; -impl Quaternion { +impl Quaternion { /// Creates a quaternion from a 4D vector. The quaternion scalar part corresponds to the `w` /// vector component. #[inline] diff --git a/src/geometry/quaternion_conversion.rs b/src/geometry/quaternion_conversion.rs index 7f276e99..b57cc52b 100644 --- a/src/geometry/quaternion_conversion.rs +++ b/src/geometry/quaternion_conversion.rs @@ -195,25 +195,25 @@ impl Into> for Quaternion { fn into(self) -> mint::Quaternion { mint::Quaternion { v: mint::Vector3 { - x: self[0], - y: self[1], - z: self[2], + x: self[0].inlined_clone(), + y: self[1].inlined_clone(), + z: self[2].inlined_clone(), }, - s: self[3], + s: self[3].inlined_clone(), } } } #[cfg(feature = "mint")] -impl Into> for UnitQuaternion { +impl Into> for UnitQuaternion { fn into(self) -> mint::Quaternion { mint::Quaternion { v: mint::Vector3 { - x: self[0], - y: self[1], - z: self[2], + x: self[0].inlined_clone(), + y: self[1].inlined_clone(), + z: self[2].inlined_clone(), }, - s: self[3], + s: self[3].inlined_clone(), } } } diff --git a/src/geometry/quaternion_ops.rs b/src/geometry/quaternion_ops.rs index 2961362e..f9205874 100644 --- a/src/geometry/quaternion_ops.rs +++ b/src/geometry/quaternion_ops.rs @@ -57,12 +57,12 @@ use std::ops::{ use crate::base::allocator::Allocator; use crate::base::dimension::{U1, U3, U4}; use crate::base::storage::Storage; -use crate::base::{DefaultAllocator, Unit, Vector, Vector3}; +use crate::base::{DefaultAllocator, Scalar, Unit, Vector, Vector3}; use crate::SimdRealField; use crate::geometry::{Point3, Quaternion, Rotation, UnitQuaternion}; -impl Index for Quaternion { +impl Index for Quaternion { type Output = N; #[inline] @@ -71,7 +71,7 @@ impl Index for Quaternion { } } -impl IndexMut for Quaternion { +impl IndexMut for Quaternion { #[inline] fn index_mut(&mut self, i: usize) -> &mut N { &mut self.coords[i] From 44be05d3e6f14dcf1d8ef535d68895a78f7bbba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Fri, 18 Dec 2020 12:27:38 +0100 Subject: [PATCH 3/8] Update the changelog. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82fc953a..c8a36ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). In this release, we improved the documentation of some of the geometric types by applying changes similar to what we did in the version 0.23.1 for matrices. +### Added +* The `Isometry::inv_mul` method which is a more efficient way of doing + `isometry1.inverse() * isometry2`. + ## [0.23.1] In this release we improved the documentation of the matrix and vector types by: - Grouping `impl` bocks logically, adding a title comment to these impl blocks. From 8036c56fdafcbfc2e2e4e64ba7a23f2944cdb0a2 Mon Sep 17 00:00:00 2001 From: Chinedu Francis Nwafili Date: Wed, 16 Dec 2020 09:02:02 -0500 Subject: [PATCH 4/8] Introduce DualQuaternion type This commit introduces the `DualQuaternion` type, in line with the plan laid out in [#487]. [#487]: https://github.com/dimforge/nalgebra/issues/487 --- src/geometry/dual_quaternion.rs | 116 +++++++++++++++++++ src/geometry/dual_quaternion_construction.rs | 49 ++++++++ src/geometry/dual_quaternion_ops.rs | 95 +++++++++++++++ src/geometry/mod.rs | 6 + src/geometry/quaternion.rs | 4 +- src/geometry/quaternion_construction.rs | 2 +- 6 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 src/geometry/dual_quaternion.rs create mode 100644 src/geometry/dual_quaternion_construction.rs create mode 100644 src/geometry/dual_quaternion_ops.rs diff --git a/src/geometry/dual_quaternion.rs b/src/geometry/dual_quaternion.rs new file mode 100644 index 00000000..9c5ad27c --- /dev/null +++ b/src/geometry/dual_quaternion.rs @@ -0,0 +1,116 @@ +use crate::{Quaternion, SimdRealField}; + +/// A dual quaternion. +/// +/// # Indexing +/// +/// DualQuaternions are stored as \[..real, ..dual\]. +/// Both of the quaternion components are laid out in `w, i, j, k` order. +/// +/// ``` +/// # use nalgebra::{DualQuaternion, Quaternion}; +/// +/// let real = Quaternion::new(1.0, 2.0, 3.0, 4.0); +/// let dual = Quaternion::new(5.0, 6.0, 7.0, 8.0); +/// +/// let dq = DualQuaternion::from_real_and_dual(real, dual); +/// assert_eq!(dq[0], 1.0); +/// assert_eq!(dq[4], 5.0); +/// assert_eq!(dq[6], 7.0); +/// ``` +/// +/// NOTE: +/// As of December 2020, dual quaternion support is a work in progress. +/// If a feature that you need is missing, feel free to open an issue or a PR. +/// See https://github.com/dimforge/nalgebra/issues/487 +#[repr(C)] +#[derive(Debug, Default, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +pub struct DualQuaternion { + // [real(w, i, j, k), dual(w, i, j, k)] + pub(crate) dq: [N; 8], +} + +impl DualQuaternion { + /// Get the first quaternion component. + /// + /// # Example + /// ``` + /// # #[macro_use] extern crate approx; + /// # use nalgebra::{DualQuaternion, Quaternion}; + /// + /// let real = Quaternion::new(1.0, 2.0, 3.0, 4.0); + /// let dual = Quaternion::new(5.0, 6.0, 7.0, 8.0); + /// + /// let dq = DualQuaternion::from_real_and_dual(real, dual); + /// relative_eq!(dq.real(), real); + /// ``` + #[inline] + pub fn real(&self) -> Quaternion { + Quaternion::new(self[0], self[1], self[2], self[3]) + } + + /// Get the second quaternion component. + /// + /// # Example + /// ``` + /// # #[macro_use] extern crate approx; + /// # use nalgebra::{DualQuaternion, Quaternion}; + /// + /// let real = Quaternion::new(1.0, 2.0, 3.0, 4.0); + /// let dual = Quaternion::new(5.0, 6.0, 7.0, 8.0); + /// + /// let dq = DualQuaternion::from_real_and_dual(real, dual); + /// relative_eq!(dq.dual(), dual); + /// ``` + #[inline] + pub fn dual(&self) -> Quaternion { + Quaternion::new(self[4], self[5], self[6], self[7]) + } +} + +impl DualQuaternion +where + N::Element: SimdRealField, +{ + /// Normalizes this quaternion. + /// + /// # Example + /// ``` + /// # #[macro_use] extern crate approx; + /// # use nalgebra::{DualQuaternion, Quaternion}; + /// let real = Quaternion::new(1.0, 2.0, 3.0, 4.0); + /// let dual = Quaternion::new(5.0, 6.0, 7.0, 8.0); + /// let dq = DualQuaternion::from_real_and_dual(real, dual); + /// + /// let dq_normalized = dq.normalize(); + /// + /// relative_eq!(dq_normalized.real().norm(), 1.0); + /// ``` + #[inline] + #[must_use = "Did you mean to use normalize_mut()?"] + pub fn normalize(&self) -> Self { + let real_norm = self.real().norm(); + + Self::from_real_and_dual(self.real() / real_norm, self.dual() / real_norm) + } + + /// Normalizes this quaternion. + /// + /// # Example + /// ``` + /// # #[macro_use] extern crate approx; + /// # use nalgebra::{DualQuaternion, Quaternion}; + /// let real = Quaternion::new(1.0, 2.0, 3.0, 4.0); + /// let dual = Quaternion::new(5.0, 6.0, 7.0, 8.0); + /// let mut dq = DualQuaternion::from_real_and_dual(real, dual); + /// + /// dq.normalize_mut(); + /// + /// relative_eq!(dq.real().norm(), 1.0); + /// ``` + #[inline] + pub fn normalize_mut(&mut self) { + *self = self.normalize(); + } +} diff --git a/src/geometry/dual_quaternion_construction.rs b/src/geometry/dual_quaternion_construction.rs new file mode 100644 index 00000000..beb398ad --- /dev/null +++ b/src/geometry/dual_quaternion_construction.rs @@ -0,0 +1,49 @@ +use crate::{DualQuaternion, Quaternion, SimdRealField}; + +impl DualQuaternion { + /// Creates a dual quaternion from its rotation and translation components. + /// + /// # Example + /// ``` + /// # use nalgebra::{DualQuaternion, Quaternion}; + /// let rot = Quaternion::new(1.0, 2.0, 3.0, 4.0); + /// let trans = Quaternion::new(5.0, 6.0, 7.0, 8.0); + /// + /// let dq = DualQuaternion::from_real_and_dual(rot, trans); + /// assert_eq!(dq.real().w, 1.0); + /// ``` + #[inline] + pub fn from_real_and_dual(real: Quaternion, dual: Quaternion) -> Self { + Self { + dq: [ + real.w, real.i, real.j, real.k, dual.w, dual.i, dual.j, dual.k, + ], + } + } +} + +impl DualQuaternion { + /// The dual quaternion multiplicative identity + /// + /// # Example + /// + /// ``` + /// # use nalgebra::{DualQuaternion, Quaternion}; + /// + /// let dq1 = DualQuaternion::identity(); + /// let dq2 = DualQuaternion::from_real_and_dual( + /// Quaternion::new(1.,2.,3.,4.), + /// Quaternion::new(5.,6.,7.,8.) + /// ); + /// + /// assert_eq!(dq1 * dq2, dq2); + /// assert_eq!(dq2 * dq1, dq2); + /// ``` + #[inline] + pub fn identity() -> Self { + Self::from_real_and_dual( + Quaternion::from_real(N::one()), + Quaternion::from_real(N::zero()), + ) + } +} diff --git a/src/geometry/dual_quaternion_ops.rs b/src/geometry/dual_quaternion_ops.rs new file mode 100644 index 00000000..dc46faf2 --- /dev/null +++ b/src/geometry/dual_quaternion_ops.rs @@ -0,0 +1,95 @@ +/* + * This file provides: + * + * NOTE: Work in progress https://github.com/dimforge/nalgebra/issues/487 + * + * (Dual Quaternion) + * + * Index + * IndexMut + * + * (Assignment Operators) + * + * DualQuaternion × Scalar + * DualQuaternion × DualQuaternion + * DualQuaternion + DualQuaternion + * DualQuaternion - DualQuaternion + * + * --- + * + * References: + * Multiplication: + * - https://cs.gmu.edu/~jmlien/teaching/cs451/uploads/Main/dual-quaternion.pdf + */ + +use crate::base::allocator::Allocator; +use crate::{DefaultAllocator, DualQuaternion, SimdRealField, U1, U4}; +use simba::simd::SimdValue; +use std::ops::{Add, Index, IndexMut, Mul, Sub}; + +impl Index for DualQuaternion { + type Output = N; + + #[inline] + fn index(&self, i: usize) -> &Self::Output { + &self.dq[i] + } +} + +impl IndexMut for DualQuaternion { + #[inline] + fn index_mut(&mut self, i: usize) -> &mut N { + &mut self.dq[i] + } +} + +impl Mul> for DualQuaternion +where + N::Element: SimdRealField, + DefaultAllocator: Allocator + Allocator, +{ + type Output = DualQuaternion; + + fn mul(self, rhs: Self) -> Self::Output { + Self::from_real_and_dual( + self.real() * rhs.real(), + self.real() * rhs.dual() + self.dual() * rhs.real(), + ) + } +} + +impl Mul for DualQuaternion +where + N::Element: SimdRealField + SimdValue, + DefaultAllocator: Allocator + Allocator, +{ + type Output = DualQuaternion; + + fn mul(self, rhs: N) -> Self::Output { + Self::from_real_and_dual(self.real() * rhs, self.dual() * rhs) + } +} + +impl Add> for DualQuaternion +where + N::Element: SimdRealField, + DefaultAllocator: Allocator + Allocator, +{ + type Output = DualQuaternion; + + fn add(self, rhs: DualQuaternion) -> Self::Output { + Self::from_real_and_dual(self.real() + rhs.real(), self.dual() + rhs.dual()) + } +} + +impl Sub> for DualQuaternion +where + N::Element: SimdRealField, + DefaultAllocator: Allocator + Allocator, +{ + type Output = DualQuaternion; + + fn sub(self, rhs: DualQuaternion) -> Self::Output { + Self::from_real_and_dual(self.real() - rhs.real(), self.dual() - rhs.dual()) + } +} diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index d3e2236a..19313b65 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -35,6 +35,10 @@ mod quaternion_coordinates; mod quaternion_ops; mod quaternion_simba; +mod dual_quaternion; +mod dual_quaternion_construction; +mod dual_quaternion_ops; + mod unit_complex; #[cfg(feature = "alga")] mod unit_complex_alga; @@ -98,6 +102,8 @@ pub use self::rotation_alias::*; pub use self::quaternion::*; +pub use self::dual_quaternion::*; + pub use self::unit_complex::*; pub use self::translation::*; diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index c8fd80be..a5db1c69 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -1,7 +1,6 @@ use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use num::Zero; use std::fmt; -use std::hash; #[cfg(feature = "abomonation-serialize")] use std::io::{Result as IOResult, Write}; @@ -14,7 +13,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use abomonation::Abomonation; use simba::scalar::{ClosedNeg, RealField}; -use simba::simd::{SimdBool, SimdOption, SimdRealField, SimdValue}; +use simba::simd::{SimdBool, SimdOption, SimdRealField}; use crate::base::dimension::{U1, U3, U4}; use crate::base::storage::{CStride, RStride}; @@ -23,7 +22,6 @@ use crate::base::{ }; use crate::geometry::{Point3, Rotation}; -use std::ops::Neg; /// A quaternion. See the type alias `UnitQuaternion = Unit` for a quaternion /// that may be used as a rotation. diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index 7d0cca8c..03d87d39 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -10,7 +10,7 @@ use rand::distributions::{Distribution, OpenClosed01, Standard}; use rand::Rng; use simba::scalar::RealField; -use simba::simd::{SimdBool, SimdValue}; +use simba::simd::SimdBool; use crate::base::dimension::U3; use crate::base::storage::Storage; From 89134efc3b7de5cda534205f734ca382ac89c65f Mon Sep 17 00:00:00 2001 From: Chinedu Francis Nwafili Date: Fri, 18 Dec 2020 10:09:56 -0500 Subject: [PATCH 5/8] Store DQ as real and dual Quat --- src/geometry/dual_quaternion.rs | 62 +++++--------------- src/geometry/dual_quaternion_construction.rs | 8 +-- src/geometry/dual_quaternion_ops.rs | 29 ++++++--- 3 files changed, 38 insertions(+), 61 deletions(-) diff --git a/src/geometry/dual_quaternion.rs b/src/geometry/dual_quaternion.rs index 9c5ad27c..ce49244f 100644 --- a/src/geometry/dual_quaternion.rs +++ b/src/geometry/dual_quaternion.rs @@ -5,7 +5,7 @@ use crate::{Quaternion, SimdRealField}; /// # Indexing /// /// DualQuaternions are stored as \[..real, ..dual\]. -/// Both of the quaternion components are laid out in `w, i, j, k` order. +/// Both of the quaternion components are laid out in `i, j, k, w` order. /// /// ``` /// # use nalgebra::{DualQuaternion, Quaternion}; @@ -14,9 +14,11 @@ use crate::{Quaternion, SimdRealField}; /// let dual = Quaternion::new(5.0, 6.0, 7.0, 8.0); /// /// let dq = DualQuaternion::from_real_and_dual(real, dual); -/// assert_eq!(dq[0], 1.0); -/// assert_eq!(dq[4], 5.0); -/// assert_eq!(dq[6], 7.0); +/// assert_eq!(dq[0], 2.0); +/// assert_eq!(dq[1], 3.0); +/// +/// assert_eq!(dq[4], 6.0); +/// assert_eq!(dq[7], 5.0); /// ``` /// /// NOTE: @@ -27,46 +29,10 @@ use crate::{Quaternion, SimdRealField}; #[derive(Debug, Default, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct DualQuaternion { - // [real(w, i, j, k), dual(w, i, j, k)] - pub(crate) dq: [N; 8], -} - -impl DualQuaternion { - /// Get the first quaternion component. - /// - /// # Example - /// ``` - /// # #[macro_use] extern crate approx; - /// # use nalgebra::{DualQuaternion, Quaternion}; - /// - /// let real = Quaternion::new(1.0, 2.0, 3.0, 4.0); - /// let dual = Quaternion::new(5.0, 6.0, 7.0, 8.0); - /// - /// let dq = DualQuaternion::from_real_and_dual(real, dual); - /// relative_eq!(dq.real(), real); - /// ``` - #[inline] - pub fn real(&self) -> Quaternion { - Quaternion::new(self[0], self[1], self[2], self[3]) - } - - /// Get the second quaternion component. - /// - /// # Example - /// ``` - /// # #[macro_use] extern crate approx; - /// # use nalgebra::{DualQuaternion, Quaternion}; - /// - /// let real = Quaternion::new(1.0, 2.0, 3.0, 4.0); - /// let dual = Quaternion::new(5.0, 6.0, 7.0, 8.0); - /// - /// let dq = DualQuaternion::from_real_and_dual(real, dual); - /// relative_eq!(dq.dual(), dual); - /// ``` - #[inline] - pub fn dual(&self) -> Quaternion { - Quaternion::new(self[4], self[5], self[6], self[7]) - } + /// The real component of the quaternion + pub real: Quaternion, + /// The dual component of the quaternion + pub dual: Quaternion, } impl DualQuaternion @@ -85,14 +51,14 @@ where /// /// let dq_normalized = dq.normalize(); /// - /// relative_eq!(dq_normalized.real().norm(), 1.0); + /// relative_eq!(dq_normalized.real.norm(), 1.0); /// ``` #[inline] #[must_use = "Did you mean to use normalize_mut()?"] pub fn normalize(&self) -> Self { - let real_norm = self.real().norm(); + let real_norm = self.real.norm(); - Self::from_real_and_dual(self.real() / real_norm, self.dual() / real_norm) + Self::from_real_and_dual(self.real / real_norm, self.dual / real_norm) } /// Normalizes this quaternion. @@ -107,7 +73,7 @@ where /// /// dq.normalize_mut(); /// - /// relative_eq!(dq.real().norm(), 1.0); + /// relative_eq!(dq.real.norm(), 1.0); /// ``` #[inline] pub fn normalize_mut(&mut self) { diff --git a/src/geometry/dual_quaternion_construction.rs b/src/geometry/dual_quaternion_construction.rs index beb398ad..aece0a87 100644 --- a/src/geometry/dual_quaternion_construction.rs +++ b/src/geometry/dual_quaternion_construction.rs @@ -10,15 +10,11 @@ impl DualQuaternion { /// let trans = Quaternion::new(5.0, 6.0, 7.0, 8.0); /// /// let dq = DualQuaternion::from_real_and_dual(rot, trans); - /// assert_eq!(dq.real().w, 1.0); + /// assert_eq!(dq.real.w, 1.0); /// ``` #[inline] pub fn from_real_and_dual(real: Quaternion, dual: Quaternion) -> Self { - Self { - dq: [ - real.w, real.i, real.j, real.k, dual.w, dual.i, dual.j, dual.k, - ], - } + Self { real, dual } } } diff --git a/src/geometry/dual_quaternion_ops.rs b/src/geometry/dual_quaternion_ops.rs index dc46faf2..869ee7bf 100644 --- a/src/geometry/dual_quaternion_ops.rs +++ b/src/geometry/dual_quaternion_ops.rs @@ -25,21 +25,36 @@ use crate::base::allocator::Allocator; use crate::{DefaultAllocator, DualQuaternion, SimdRealField, U1, U4}; use simba::simd::SimdValue; +use std::mem; use std::ops::{Add, Index, IndexMut, Mul, Sub}; +impl AsRef<[N; 8]> for DualQuaternion { + #[inline] + fn as_ref(&self) -> &[N; 8] { + unsafe { mem::transmute(self) } + } +} + +impl AsMut<[N; 8]> for DualQuaternion { + #[inline] + fn as_mut(&mut self) -> &mut [N; 8] { + unsafe { mem::transmute(self) } + } +} + impl Index for DualQuaternion { type Output = N; #[inline] fn index(&self, i: usize) -> &Self::Output { - &self.dq[i] + &self.as_ref()[i] } } impl IndexMut for DualQuaternion { #[inline] fn index_mut(&mut self, i: usize) -> &mut N { - &mut self.dq[i] + &mut self.as_mut()[i] } } @@ -52,8 +67,8 @@ where fn mul(self, rhs: Self) -> Self::Output { Self::from_real_and_dual( - self.real() * rhs.real(), - self.real() * rhs.dual() + self.dual() * rhs.real(), + self.real * rhs.real, + self.real * rhs.dual + self.dual * rhs.real, ) } } @@ -66,7 +81,7 @@ where type Output = DualQuaternion; fn mul(self, rhs: N) -> Self::Output { - Self::from_real_and_dual(self.real() * rhs, self.dual() * rhs) + Self::from_real_and_dual(self.real * rhs, self.dual * rhs) } } @@ -78,7 +93,7 @@ where type Output = DualQuaternion; fn add(self, rhs: DualQuaternion) -> Self::Output { - Self::from_real_and_dual(self.real() + rhs.real(), self.dual() + rhs.dual()) + Self::from_real_and_dual(self.real + rhs.real, self.dual + rhs.dual) } } @@ -90,6 +105,6 @@ where type Output = DualQuaternion; fn sub(self, rhs: DualQuaternion) -> Self::Output { - Self::from_real_and_dual(self.real() - rhs.real(), self.dual() - rhs.dual()) + Self::from_real_and_dual(self.real - rhs.real, self.dual - rhs.dual) } } From 7ec5d00c3db7851d9c6893eb012a29b3563bd4d8 Mon Sep 17 00:00:00 2001 From: Chinedu Francis Nwafili Date: Fri, 18 Dec 2020 10:38:19 -0500 Subject: [PATCH 6/8] Clean up DualQuat bounds --- src/geometry/dual_quaternion_construction.rs | 3 --- src/geometry/dual_quaternion_ops.rs | 10 ++-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/geometry/dual_quaternion_construction.rs b/src/geometry/dual_quaternion_construction.rs index aece0a87..25a979f7 100644 --- a/src/geometry/dual_quaternion_construction.rs +++ b/src/geometry/dual_quaternion_construction.rs @@ -16,9 +16,6 @@ impl DualQuaternion { pub fn from_real_and_dual(real: Quaternion, dual: Quaternion) -> Self { Self { real, dual } } -} - -impl DualQuaternion { /// The dual quaternion multiplicative identity /// /// # Example diff --git a/src/geometry/dual_quaternion_ops.rs b/src/geometry/dual_quaternion_ops.rs index 869ee7bf..0c9f78f4 100644 --- a/src/geometry/dual_quaternion_ops.rs +++ b/src/geometry/dual_quaternion_ops.rs @@ -22,9 +22,7 @@ * - https://cs.gmu.edu/~jmlien/teaching/cs451/uploads/Main/dual-quaternion.pdf */ -use crate::base::allocator::Allocator; -use crate::{DefaultAllocator, DualQuaternion, SimdRealField, U1, U4}; -use simba::simd::SimdValue; +use crate::{DualQuaternion, SimdRealField}; use std::mem; use std::ops::{Add, Index, IndexMut, Mul, Sub}; @@ -61,7 +59,6 @@ impl IndexMut for DualQuaternion { impl Mul> for DualQuaternion where N::Element: SimdRealField, - DefaultAllocator: Allocator + Allocator, { type Output = DualQuaternion; @@ -75,8 +72,7 @@ where impl Mul for DualQuaternion where - N::Element: SimdRealField + SimdValue, - DefaultAllocator: Allocator + Allocator, + N::Element: SimdRealField, { type Output = DualQuaternion; @@ -88,7 +84,6 @@ where impl Add> for DualQuaternion where N::Element: SimdRealField, - DefaultAllocator: Allocator + Allocator, { type Output = DualQuaternion; @@ -100,7 +95,6 @@ where impl Sub> for DualQuaternion where N::Element: SimdRealField, - DefaultAllocator: Allocator + Allocator, { type Output = DualQuaternion; From b9513257b88dfe4a0243af5d6088fb6486dd5384 Mon Sep 17 00:00:00 2001 From: Chinedu Francis Nwafili Date: Fri, 18 Dec 2020 12:19:38 -0500 Subject: [PATCH 7/8] Serialize DQ to [N;8] Closes #816 --- src/geometry/dual_quaternion.rs | 36 ++++++++++++++++++++++++++++++++- tests/core/matrixcompare.rs | 10 +++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/geometry/dual_quaternion.rs b/src/geometry/dual_quaternion.rs index ce49244f..ce9f7284 100644 --- a/src/geometry/dual_quaternion.rs +++ b/src/geometry/dual_quaternion.rs @@ -1,4 +1,6 @@ use crate::{Quaternion, SimdRealField}; +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// A dual quaternion. /// @@ -27,7 +29,6 @@ use crate::{Quaternion, SimdRealField}; /// See https://github.com/dimforge/nalgebra/issues/487 #[repr(C)] #[derive(Debug, Default, Eq, PartialEq, Copy, Clone)] -#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct DualQuaternion { /// The real component of the quaternion pub real: Quaternion, @@ -80,3 +81,36 @@ where *self = self.normalize(); } } + +#[cfg(feature = "serde-serialize")] +impl Serialize for DualQuaternion +where + N: Serialize, +{ + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + self.as_ref().serialize(serializer) + } +} + +#[cfg(feature = "serde-serialize")] +impl<'a, N: SimdRealField> Deserialize<'a> for DualQuaternion +where + N: Deserialize<'a>, +{ + fn deserialize(deserializer: Des) -> Result + where + Des: Deserializer<'a>, + { + type Dq = [N; 8]; + + let dq: Dq = Dq::::deserialize(deserializer)?; + + Ok(Self { + real: Quaternion::new(dq[3], dq[0], dq[1], dq[2]), + dual: Quaternion::new(dq[7], dq[4], dq[5], dq[6]), + }) + } +} diff --git a/tests/core/matrixcompare.rs b/tests/core/matrixcompare.rs index df112173..cdd93ea3 100644 --- a/tests/core/matrixcompare.rs +++ b/tests/core/matrixcompare.rs @@ -4,9 +4,15 @@ //! The tests here only check that the necessary trait implementations are correctly implemented, //! in addition to some sanity checks with example input. -use nalgebra::{DMatrix, MatrixMN, U4, U5}; +use nalgebra::{MatrixMN, U4, U5}; -use matrixcompare::{assert_matrix_eq, DenseAccess}; +#[cfg(feature = "arbitrary")] +use nalgebra::DMatrix; + +use matrixcompare::assert_matrix_eq; + +#[cfg(feature = "arbitrary")] +use matrixcompare::DenseAccess; #[cfg(feature = "arbitrary")] quickcheck! { From b695aaa807c102f91c98a11669dbf9f236268846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Wed, 30 Dec 2020 15:07:13 +0100 Subject: [PATCH 8/8] Release v0.24.0 --- CHANGELOG.md | 11 +++++++++++ Cargo.toml | 2 +- nalgebra-glm/Cargo.toml | 4 ++-- nalgebra-lapack/Cargo.toml | 6 +++--- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8a36ade..cfc0e080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ documented here. This project adheres to [Semantic Versioning](https://semver.org/). +## [0.24.0] + +### Added +* The `DualQuaternion` type. It is still work-in-progress but the basics are here: + creation from its real and dual part, multiplication of two dual quaternions, + and normalization. + +### Removed +* There is no blanket `impl PartialEq for Unit` any more. Instead, it is + implemented specifically for `UnitComplex`, `UnitQuaternion` and `Unit`. + ## [0.23.2] In this release, we improved the documentation of some of the geometric types by applying changes similar to what we did in the version 0.23.1 for matrices. diff --git a/Cargo.toml b/Cargo.toml index ae3b3378..615942a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra" -version = "0.23.2" +version = "0.24.0" authors = [ "Sébastien Crozet " ] description = "Linear algebra library with transformations and statically-sized or dynamically-sized matrices." diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml index f4e12f8e..6cbf7ab7 100644 --- a/nalgebra-glm/Cargo.toml +++ b/nalgebra-glm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-glm" -version = "0.9.0" +version = "0.10.0" authors = ["sebcrozet "] description = "A computer-graphics oriented API for nalgebra, inspired by the C++ GLM library." @@ -25,4 +25,4 @@ abomonation-serialize = [ "nalgebra/abomonation-serialize" ] num-traits = { version = "0.2", default-features = false } approx = { version = "0.4", default-features = false } simba = { version = "0.3", default-features = false } -nalgebra = { path = "..", version = "0.23", default-features = false } +nalgebra = { path = "..", version = "0.24", default-features = false } diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml index e70359cc..1ea68287 100644 --- a/nalgebra-lapack/Cargo.toml +++ b/nalgebra-lapack/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-lapack" -version = "0.14.0" +version = "0.15.0" authors = [ "Sébastien Crozet ", "Andrew Straw " ] description = "Linear algebra library with transformations and satically-sized or dynamically-sized matrices." @@ -23,7 +23,7 @@ accelerate = ["lapack-src/accelerate"] intel-mkl = ["lapack-src/intel-mkl"] [dependencies] -nalgebra = { version = "0.22" } # , path = ".." } +nalgebra = { version = "0.24", path = ".." } num-traits = "0.2" num-complex = { version = "0.2", default-features = false } simba = "0.2" @@ -34,7 +34,7 @@ lapack-src = { version = "0.5", default-features = false } # clippy = "*" [dev-dependencies] -nalgebra = { version = "0.22", features = [ "arbitrary" ] } # path = ".." } +nalgebra = { version = "0.24", features = [ "arbitrary" ], path = ".." } quickcheck = "0.9" approx = "0.3" rand = "0.7"