summaryrefslogtreecommitdiffstats
path: root/third_party/rust/euclid/src/rigid.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/euclid/src/rigid.rs')
-rw-r--r--third_party/rust/euclid/src/rigid.rs286
1 files changed, 286 insertions, 0 deletions
diff --git a/third_party/rust/euclid/src/rigid.rs b/third_party/rust/euclid/src/rigid.rs
new file mode 100644
index 0000000000..16e1b86e37
--- /dev/null
+++ b/third_party/rust/euclid/src/rigid.rs
@@ -0,0 +1,286 @@
+//! All matrix multiplication in this module is in row-vector notation,
+//! i.e. a vector `v` is transformed with `v * T`, and if you want to apply `T1`
+//! before `T2` you use `T1 * T2`
+
+use crate::approxeq::ApproxEq;
+use crate::trig::Trig;
+use crate::{Rotation3D, Transform3D, UnknownUnit, Vector3D};
+use num_traits::real::Real;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A rigid transformation. All lengths are preserved under such a transformation.
+///
+///
+/// Internally, this is a rotation and a translation, with the rotation
+/// applied first (i.e. `Rotation * Translation`, in row-vector notation)
+///
+/// This can be more efficient to use over full matrices, especially if you
+/// have to deal with the decomposed quantities often.
+#[derive(Debug, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[repr(C)]
+pub struct RigidTransform3D<T, Src, Dst> {
+ pub rotation: Rotation3D<T, Src, Dst>,
+ pub translation: Vector3D<T, Dst>,
+}
+
+impl<T, Src, Dst> RigidTransform3D<T, Src, Dst> {
+ /// Construct a new rigid transformation, where the `rotation` applies first
+ #[inline]
+ pub const fn new(rotation: Rotation3D<T, Src, Dst>, translation: Vector3D<T, Dst>) -> Self {
+ Self {
+ rotation,
+ translation,
+ }
+ }
+}
+
+impl<T: Copy, Src, Dst> RigidTransform3D<T, Src, Dst> {
+ pub fn cast_unit<Src2, Dst2>(&self) -> RigidTransform3D<T, Src2, Dst2> {
+ RigidTransform3D {
+ rotation: self.rotation.cast_unit(),
+ translation: self.translation.cast_unit(),
+ }
+ }
+}
+
+impl<T: Real + ApproxEq<T>, Src, Dst> RigidTransform3D<T, Src, Dst> {
+ /// Construct an identity transform
+ #[inline]
+ pub fn identity() -> Self {
+ Self {
+ rotation: Rotation3D::identity(),
+ translation: Vector3D::zero(),
+ }
+ }
+
+ /// Construct a new rigid transformation, where the `translation` applies first
+ #[inline]
+ pub fn new_from_reversed(
+ translation: Vector3D<T, Src>,
+ rotation: Rotation3D<T, Src, Dst>,
+ ) -> Self {
+ // T * R
+ // = (R * R^-1) * T * R
+ // = R * (R^-1 * T * R)
+ // = R * T'
+ //
+ // T' = (R^-1 * T * R) is also a translation matrix
+ // It is equivalent to the translation matrix obtained by rotating the
+ // translation by R
+
+ let translation = rotation.transform_vector3d(translation);
+ Self {
+ rotation,
+ translation,
+ }
+ }
+
+ #[inline]
+ pub fn from_rotation(rotation: Rotation3D<T, Src, Dst>) -> Self {
+ Self {
+ rotation,
+ translation: Vector3D::zero(),
+ }
+ }
+
+ #[inline]
+ pub fn from_translation(translation: Vector3D<T, Dst>) -> Self {
+ Self {
+ translation,
+ rotation: Rotation3D::identity(),
+ }
+ }
+
+ /// Decompose this into a translation and an rotation to be applied in the opposite order
+ ///
+ /// i.e., the translation is applied _first_
+ #[inline]
+ pub fn decompose_reversed(&self) -> (Vector3D<T, Src>, Rotation3D<T, Src, Dst>) {
+ // self = R * T
+ // = R * T * (R^-1 * R)
+ // = (R * T * R^-1) * R)
+ // = T' * R
+ //
+ // T' = (R^ * T * R^-1) is T rotated by R^-1
+
+ let translation = self.rotation.inverse().transform_vector3d(self.translation);
+ (translation, self.rotation)
+ }
+
+ /// Returns the multiplication of the two transforms such that
+ /// other's transformation applies after self's transformation.
+ ///
+ /// i.e., this produces `self * other` in row-vector notation
+ #[inline]
+ pub fn then<Dst2>(
+ &self,
+ other: &RigidTransform3D<T, Dst, Dst2>,
+ ) -> RigidTransform3D<T, Src, Dst2> {
+ // self = R1 * T1
+ // other = R2 * T2
+ // result = R1 * T1 * R2 * T2
+ // = R1 * (R2 * R2^-1) * T1 * R2 * T2
+ // = (R1 * R2) * (R2^-1 * T1 * R2) * T2
+ // = R' * T' * T2
+ // = R' * T''
+ //
+ // (R2^-1 * T2 * R2^) = T' = T2 rotated by R2
+ // R1 * R2 = R'
+ // T' * T2 = T'' = vector addition of translations T2 and T'
+
+ let t_prime = other.rotation.transform_vector3d(self.translation);
+ let r_prime = self.rotation.then(&other.rotation);
+ let t_prime2 = t_prime + other.translation;
+ RigidTransform3D {
+ rotation: r_prime,
+ translation: t_prime2,
+ }
+ }
+
+ /// Inverts the transformation
+ #[inline]
+ pub fn inverse(&self) -> RigidTransform3D<T, Dst, Src> {
+ // result = (self)^-1
+ // = (R * T)^-1
+ // = T^-1 * R^-1
+ // = (R^-1 * R) * T^-1 * R^-1
+ // = R^-1 * (R * T^-1 * R^-1)
+ // = R' * T'
+ //
+ // T' = (R * T^-1 * R^-1) = (-T) rotated by R^-1
+ // R' = R^-1
+ //
+ // An easier way of writing this is to use new_from_reversed() with R^-1 and T^-1
+
+ RigidTransform3D::new_from_reversed(-self.translation, self.rotation.inverse())
+ }
+
+ pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
+ where
+ T: Trig,
+ {
+ self.rotation.to_transform().then(&self.translation.to_transform())
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> RigidTransform3D<T, UnknownUnit, UnknownUnit> {
+ RigidTransform3D {
+ rotation: self.rotation.to_untyped(),
+ translation: self.translation.to_untyped(),
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(transform: &RigidTransform3D<T, UnknownUnit, UnknownUnit>) -> Self {
+ RigidTransform3D {
+ rotation: Rotation3D::from_untyped(&transform.rotation),
+ translation: Vector3D::from_untyped(transform.translation),
+ }
+ }
+}
+
+impl<T: Copy, Src, Dst> Copy for RigidTransform3D<T, Src, Dst> {}
+
+impl<T: Clone, Src, Dst> Clone for RigidTransform3D<T, Src, Dst> {
+ fn clone(&self) -> Self {
+ RigidTransform3D {
+ rotation: self.rotation.clone(),
+ translation: self.translation.clone(),
+ }
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for RigidTransform3D<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for RigidTransform3D<T, Src, Dst> {}
+
+impl<T: Real + ApproxEq<T>, Src, Dst> From<Rotation3D<T, Src, Dst>>
+ for RigidTransform3D<T, Src, Dst>
+{
+ fn from(rot: Rotation3D<T, Src, Dst>) -> Self {
+ Self::from_rotation(rot)
+ }
+}
+
+impl<T: Real + ApproxEq<T>, Src, Dst> From<Vector3D<T, Dst>> for RigidTransform3D<T, Src, Dst> {
+ fn from(t: Vector3D<T, Dst>) -> Self {
+ Self::from_translation(t)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::RigidTransform3D;
+ use crate::default::{Rotation3D, Transform3D, Vector3D};
+
+ #[test]
+ fn test_rigid_construction() {
+ let translation = Vector3D::new(12.1, 17.8, -5.5);
+ let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
+
+ let rigid = RigidTransform3D::new(rotation, translation);
+ assert!(rigid.to_transform().approx_eq(
+ &rotation.to_transform().then(&translation.to_transform())
+ ));
+
+ let rigid = RigidTransform3D::new_from_reversed(translation, rotation);
+ assert!(rigid.to_transform().approx_eq(
+ &translation.to_transform().then(&rotation.to_transform())
+ ));
+ }
+
+ #[test]
+ fn test_rigid_decomposition() {
+ let translation = Vector3D::new(12.1, 17.8, -5.5);
+ let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
+
+ let rigid = RigidTransform3D::new(rotation, translation);
+ let (t2, r2) = rigid.decompose_reversed();
+ assert!(rigid
+ .to_transform()
+ .approx_eq(&t2.to_transform().then(&r2.to_transform())));
+ }
+
+ #[test]
+ fn test_rigid_inverse() {
+ let translation = Vector3D::new(12.1, 17.8, -5.5);
+ let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
+
+ let rigid = RigidTransform3D::new(rotation, translation);
+ let inverse = rigid.inverse();
+ assert!(rigid
+ .then(&inverse)
+ .to_transform()
+ .approx_eq(&Transform3D::identity()));
+ assert!(inverse
+ .to_transform()
+ .approx_eq(&rigid.to_transform().inverse().unwrap()));
+ }
+
+ #[test]
+ fn test_rigid_multiply() {
+ let translation = Vector3D::new(12.1, 17.8, -5.5);
+ let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
+ let translation2 = Vector3D::new(9.3, -3.9, 1.1);
+ let rotation2 = Rotation3D::unit_quaternion(0.1, 0.2, 0.3, -0.4);
+ let rigid = RigidTransform3D::new(rotation, translation);
+ let rigid2 = RigidTransform3D::new(rotation2, translation2);
+
+ assert!(rigid
+ .then(&rigid2)
+ .to_transform()
+ .approx_eq(&rigid.to_transform().then(&rigid2.to_transform())));
+ assert!(rigid2
+ .then(&rigid)
+ .to_transform()
+ .approx_eq(&rigid2.to_transform().then(&rigid.to_transform())));
+ }
+}