// Copyright 2018 The Servo Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use crate::num::*; use crate::UnknownUnit; use crate::{point2, point3, vec2, vec3, Box2D, Box3D, Rect, Size2D}; use crate::{Point2D, Point3D, Transform2D, Transform3D, Vector2D, Vector3D}; use core::cmp::{Eq, PartialEq}; use core::fmt; use core::hash::Hash; use core::marker::PhantomData; use core::ops::{Add, AddAssign, Neg, Sub, SubAssign}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "bytemuck")] use bytemuck::{Zeroable, Pod}; /// A 2d transformation from a space to another that can only express translations. /// /// The main benefit of this type over a Vector2D is the ability to cast /// between a source and a destination spaces. /// /// Example: /// /// ``` /// use euclid::{Translation2D, Point2D, point2}; /// struct ParentSpace; /// struct ChildSpace; /// type ScrollOffset = Translation2D; /// type ParentPoint = Point2D; /// type ChildPoint = Point2D; /// /// let scrolling = ScrollOffset::new(0, 100); /// let p1: ParentPoint = point2(0, 0); /// let p2: ChildPoint = scrolling.transform_point(p1); /// ``` /// #[repr(C)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde", serde(bound( serialize = "T: serde::Serialize", deserialize = "T: serde::Deserialize<'de>" )) )] pub struct Translation2D { pub x: T, pub y: T, #[doc(hidden)] pub _unit: PhantomData<(Src, Dst)>, } #[cfg(feature = "arbitrary")] impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Translation2D where T: arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let (x, y) = arbitrary::Arbitrary::arbitrary(u)?; Ok(Translation2D { x, y, _unit: PhantomData, }) } } impl Copy for Translation2D {} impl Clone for Translation2D { fn clone(&self) -> Self { Translation2D { x: self.x.clone(), y: self.y.clone(), _unit: PhantomData, } } } impl Eq for Translation2D where T: Eq {} impl PartialEq for Translation2D where T: PartialEq, { fn eq(&self, other: &Self) -> bool { self.x == other.x && self.y == other.y } } impl Hash for Translation2D where T: Hash, { fn hash(&self, h: &mut H) { self.x.hash(h); self.y.hash(h); } } impl Translation2D { #[inline] pub const fn new(x: T, y: T) -> Self { Translation2D { x, y, _unit: PhantomData, } } #[inline] pub fn splat(v: T) -> Self where T: Clone, { Translation2D { x: v.clone(), y: v, _unit: PhantomData, } } /// Creates no-op translation (both `x` and `y` is `zero()`). #[inline] pub fn identity() -> Self where T: Zero, { Self::new(T::zero(), T::zero()) } /// Check if translation does nothing (both x and y is `zero()`). /// /// ```rust /// use euclid::default::Translation2D; /// /// assert_eq!(Translation2D::::identity().is_identity(), true); /// assert_eq!(Translation2D::new(0, 0).is_identity(), true); /// assert_eq!(Translation2D::new(1, 0).is_identity(), false); /// assert_eq!(Translation2D::new(0, 1).is_identity(), false); /// ``` #[inline] pub fn is_identity(&self) -> bool where T: Zero + PartialEq, { let _0 = T::zero(); self.x == _0 && self.y == _0 } /// No-op, just cast the unit. #[inline] pub fn transform_size(&self, s: Size2D) -> Size2D { Size2D::new(s.width, s.height) } } impl Translation2D { /// Cast into a 2D vector. #[inline] pub fn to_vector(&self) -> Vector2D { vec2(self.x, self.y) } /// Cast into an array with x and y. #[inline] pub fn to_array(&self) -> [T; 2] { [self.x, self.y] } /// Cast into a tuple with x and y. #[inline] pub fn to_tuple(&self) -> (T, T) { (self.x, self.y) } /// Drop the units, preserving only the numeric value. #[inline] pub fn to_untyped(&self) -> Translation2D { Translation2D { x: self.x, y: self.y, _unit: PhantomData, } } /// Tag a unitless value with units. #[inline] pub fn from_untyped(t: &Translation2D) -> Self { Translation2D { x: t.x, y: t.y, _unit: PhantomData, } } /// Returns the matrix representation of this translation. #[inline] pub fn to_transform(&self) -> Transform2D where T: Zero + One, { (*self).into() } /// Translate a point and cast its unit. #[inline] pub fn transform_point(&self, p: Point2D) -> Point2D where T: Add, { point2(p.x + self.x, p.y + self.y) } /// Translate a rectangle and cast its unit. #[inline] pub fn transform_rect(&self, r: &Rect) -> Rect where T: Add, { Rect { origin: self.transform_point(r.origin), size: self.transform_size(r.size), } } /// Translate a 2D box and cast its unit. #[inline] pub fn transform_box(&self, r: &Box2D) -> Box2D where T: Add, { Box2D { min: self.transform_point(r.min), max: self.transform_point(r.max), } } /// Return the inverse transformation. #[inline] pub fn inverse(&self) -> Translation2D where T: Neg, { Translation2D::new(-self.x, -self.y) } } #[cfg(feature = "bytemuck")] unsafe impl Zeroable for Translation2D {} #[cfg(feature = "bytemuck")] unsafe impl Pod for Translation2D {} impl Add> for Translation2D { type Output = Translation2D; fn add(self, other: Translation2D) -> Self::Output { Translation2D::new(self.x + other.x, self.y + other.y) } } impl AddAssign> for Translation2D { fn add_assign(&mut self, other: Translation2D) { self.x += other.x; self.y += other.y; } } impl Sub> for Translation2D { type Output = Translation2D; fn sub(self, other: Translation2D) -> Self::Output { Translation2D::new(self.x - other.x, self.y - other.y) } } impl SubAssign> for Translation2D { fn sub_assign(&mut self, other: Translation2D) { self.x -= other.x; self.y -= other.y; } } impl From> for Translation2D { fn from(v: Vector2D) -> Self { Translation2D::new(v.x, v.y) } } impl Into> for Translation2D { fn into(self) -> Vector2D { vec2(self.x, self.y) } } impl Into> for Translation2D where T: Zero + One, { fn into(self) -> Transform2D { Transform2D::translation(self.x, self.y) } } impl Default for Translation2D where T: Zero, { fn default() -> Self { Self::identity() } } impl fmt::Debug for Translation2D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Translation({:?},{:?})", self.x, self.y) } } /// A 3d transformation from a space to another that can only express translations. /// /// The main benefit of this type over a Vector3D is the ability to cast /// between a source and a destination spaces. #[repr(C)] pub struct Translation3D { pub x: T, pub y: T, pub z: T, #[doc(hidden)] pub _unit: PhantomData<(Src, Dst)>, } impl Copy for Translation3D {} impl Clone for Translation3D { fn clone(&self) -> Self { Translation3D { x: self.x.clone(), y: self.y.clone(), z: self.z.clone(), _unit: PhantomData, } } } #[cfg(feature = "serde")] impl<'de, T, Src, Dst> serde::Deserialize<'de> for Translation3D where T: serde::Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let (x, y, z) = serde::Deserialize::deserialize(deserializer)?; Ok(Translation3D { x, y, z, _unit: PhantomData, }) } } #[cfg(feature = "serde")] impl serde::Serialize for Translation3D where T: serde::Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { (&self.x, &self.y, &self.z).serialize(serializer) } } impl Eq for Translation3D where T: Eq {} impl PartialEq for Translation3D where T: PartialEq, { fn eq(&self, other: &Self) -> bool { self.x == other.x && self.y == other.y && self.z == other.z } } impl Hash for Translation3D where T: Hash, { fn hash(&self, h: &mut H) { self.x.hash(h); self.y.hash(h); self.z.hash(h); } } impl Translation3D { #[inline] pub const fn new(x: T, y: T, z: T) -> Self { Translation3D { x, y, z, _unit: PhantomData, } } #[inline] pub fn splat(v: T) -> Self where T: Clone, { Translation3D { x: v.clone(), y: v.clone(), z: v, _unit: PhantomData, } } /// Creates no-op translation (`x`, `y` and `z` is `zero()`). #[inline] pub fn identity() -> Self where T: Zero, { Translation3D::new(T::zero(), T::zero(), T::zero()) } /// Check if translation does nothing (`x`, `y` and `z` is `zero()`). /// /// ```rust /// use euclid::default::Translation3D; /// /// assert_eq!(Translation3D::::identity().is_identity(), true); /// assert_eq!(Translation3D::new(0, 0, 0).is_identity(), true); /// assert_eq!(Translation3D::new(1, 0, 0).is_identity(), false); /// assert_eq!(Translation3D::new(0, 1, 0).is_identity(), false); /// assert_eq!(Translation3D::new(0, 0, 1).is_identity(), false); /// ``` #[inline] pub fn is_identity(&self) -> bool where T: Zero + PartialEq, { let _0 = T::zero(); self.x == _0 && self.y == _0 && self.z == _0 } /// No-op, just cast the unit. #[inline] pub fn transform_size(self, s: Size2D) -> Size2D { Size2D::new(s.width, s.height) } } impl Translation3D { /// Cast into a 3D vector. #[inline] pub fn to_vector(&self) -> Vector3D { vec3(self.x, self.y, self.z) } /// Cast into an array with x, y and z. #[inline] pub fn to_array(&self) -> [T; 3] { [self.x, self.y, self.z] } /// Cast into a tuple with x, y and z. #[inline] pub fn to_tuple(&self) -> (T, T, T) { (self.x, self.y, self.z) } /// Drop the units, preserving only the numeric value. #[inline] pub fn to_untyped(&self) -> Translation3D { Translation3D { x: self.x, y: self.y, z: self.z, _unit: PhantomData, } } /// Tag a unitless value with units. #[inline] pub fn from_untyped(t: &Translation3D) -> Self { Translation3D { x: t.x, y: t.y, z: t.z, _unit: PhantomData, } } /// Returns the matrix representation of this translation. #[inline] pub fn to_transform(&self) -> Transform3D where T: Zero + One, { (*self).into() } /// Translate a point and cast its unit. #[inline] pub fn transform_point3d(&self, p: &Point3D) -> Point3D where T: Add, { point3(p.x + self.x, p.y + self.y, p.z + self.z) } /// Translate a point and cast its unit. #[inline] pub fn transform_point2d(&self, p: &Point2D) -> Point2D where T: Add, { point2(p.x + self.x, p.y + self.y) } /// Translate a 2D box and cast its unit. #[inline] pub fn transform_box2d(&self, b: &Box2D) -> Box2D where T: Add, { Box2D { min: self.transform_point2d(&b.min), max: self.transform_point2d(&b.max), } } /// Translate a 3D box and cast its unit. #[inline] pub fn transform_box3d(&self, b: &Box3D) -> Box3D where T: Add, { Box3D { min: self.transform_point3d(&b.min), max: self.transform_point3d(&b.max), } } /// Translate a rectangle and cast its unit. #[inline] pub fn transform_rect(&self, r: &Rect) -> Rect where T: Add, { Rect { origin: self.transform_point2d(&r.origin), size: self.transform_size(r.size), } } /// Return the inverse transformation. #[inline] pub fn inverse(&self) -> Translation3D where T: Neg, { Translation3D::new(-self.x, -self.y, -self.z) } } #[cfg(feature = "bytemuck")] unsafe impl Zeroable for Translation3D {} #[cfg(feature = "bytemuck")] unsafe impl Pod for Translation3D {} impl Add> for Translation3D { type Output = Translation3D; fn add(self, other: Translation3D) -> Self::Output { Translation3D::new(self.x + other.x, self.y + other.y, self.z + other.z) } } impl AddAssign> for Translation3D { fn add_assign(&mut self, other: Translation3D) { self.x += other.x; self.y += other.y; self.z += other.z; } } impl Sub> for Translation3D { type Output = Translation3D; fn sub(self, other: Translation3D) -> Self::Output { Translation3D::new(self.x - other.x, self.y - other.y, self.z - other.z) } } impl SubAssign> for Translation3D { fn sub_assign(&mut self, other: Translation3D) { self.x -= other.x; self.y -= other.y; self.z -= other.z; } } impl From> for Translation3D { fn from(v: Vector3D) -> Self { Translation3D::new(v.x, v.y, v.z) } } impl Into> for Translation3D { fn into(self) -> Vector3D { vec3(self.x, self.y, self.z) } } impl Into> for Translation3D where T: Zero + One, { fn into(self) -> Transform3D { Transform3D::translation(self.x, self.y, self.z) } } impl Default for Translation3D where T: Zero, { fn default() -> Self { Self::identity() } } impl fmt::Debug for Translation3D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Translation({:?},{:?},{:?})", self.x, self.y, self.z) } } #[cfg(test)] mod _2d { #[test] fn simple() { use crate::{rect, Rect, Translation2D}; struct A; struct B; type Translation = Translation2D; type SrcRect = Rect; type DstRect = Rect; let tx = Translation::new(10, -10); let r1: SrcRect = rect(10, 20, 30, 40); let r2: DstRect = tx.transform_rect(&r1); assert_eq!(r2, rect(20, 10, 30, 40)); let inv_tx = tx.inverse(); assert_eq!(inv_tx.transform_rect(&r2), r1); assert!((tx + inv_tx).is_identity()); } /// Operation tests mod ops { use crate::default::Translation2D; #[test] fn test_add() { let t1 = Translation2D::new(1.0, 2.0); let t2 = Translation2D::new(3.0, 4.0); assert_eq!(t1 + t2, Translation2D::new(4.0, 6.0)); let t1 = Translation2D::new(1.0, 2.0); let t2 = Translation2D::new(0.0, 0.0); assert_eq!(t1 + t2, Translation2D::new(1.0, 2.0)); let t1 = Translation2D::new(1.0, 2.0); let t2 = Translation2D::new(-3.0, -4.0); assert_eq!(t1 + t2, Translation2D::new(-2.0, -2.0)); let t1 = Translation2D::new(0.0, 0.0); let t2 = Translation2D::new(0.0, 0.0); assert_eq!(t1 + t2, Translation2D::new(0.0, 0.0)); } #[test] pub fn test_add_assign() { let mut t = Translation2D::new(1.0, 2.0); t += Translation2D::new(3.0, 4.0); assert_eq!(t, Translation2D::new(4.0, 6.0)); let mut t = Translation2D::new(1.0, 2.0); t += Translation2D::new(0.0, 0.0); assert_eq!(t, Translation2D::new(1.0, 2.0)); let mut t = Translation2D::new(1.0, 2.0); t += Translation2D::new(-3.0, -4.0); assert_eq!(t, Translation2D::new(-2.0, -2.0)); let mut t = Translation2D::new(0.0, 0.0); t += Translation2D::new(0.0, 0.0); assert_eq!(t, Translation2D::new(0.0, 0.0)); } #[test] pub fn test_sub() { let t1 = Translation2D::new(1.0, 2.0); let t2 = Translation2D::new(3.0, 4.0); assert_eq!(t1 - t2, Translation2D::new(-2.0, -2.0)); let t1 = Translation2D::new(1.0, 2.0); let t2 = Translation2D::new(0.0, 0.0); assert_eq!(t1 - t2, Translation2D::new(1.0, 2.0)); let t1 = Translation2D::new(1.0, 2.0); let t2 = Translation2D::new(-3.0, -4.0); assert_eq!(t1 - t2, Translation2D::new(4.0, 6.0)); let t1 = Translation2D::new(0.0, 0.0); let t2 = Translation2D::new(0.0, 0.0); assert_eq!(t1 - t2, Translation2D::new(0.0, 0.0)); } #[test] pub fn test_sub_assign() { let mut t = Translation2D::new(1.0, 2.0); t -= Translation2D::new(3.0, 4.0); assert_eq!(t, Translation2D::new(-2.0, -2.0)); let mut t = Translation2D::new(1.0, 2.0); t -= Translation2D::new(0.0, 0.0); assert_eq!(t, Translation2D::new(1.0, 2.0)); let mut t = Translation2D::new(1.0, 2.0); t -= Translation2D::new(-3.0, -4.0); assert_eq!(t, Translation2D::new(4.0, 6.0)); let mut t = Translation2D::new(0.0, 0.0); t -= Translation2D::new(0.0, 0.0); assert_eq!(t, Translation2D::new(0.0, 0.0)); } } } #[cfg(test)] mod _3d { #[test] fn simple() { use crate::{point3, Point3D, Translation3D}; struct A; struct B; type Translation = Translation3D; type SrcPoint = Point3D; type DstPoint = Point3D; let tx = Translation::new(10, -10, 100); let p1: SrcPoint = point3(10, 20, 30); let p2: DstPoint = tx.transform_point3d(&p1); assert_eq!(p2, point3(20, 10, 130)); let inv_tx = tx.inverse(); assert_eq!(inv_tx.transform_point3d(&p2), p1); assert!((tx + inv_tx).is_identity()); } /// Operation tests mod ops { use crate::default::Translation3D; #[test] pub fn test_add() { let t1 = Translation3D::new(1.0, 2.0, 3.0); let t2 = Translation3D::new(4.0, 5.0, 6.0); assert_eq!(t1 + t2, Translation3D::new(5.0, 7.0, 9.0)); let t1 = Translation3D::new(1.0, 2.0, 3.0); let t2 = Translation3D::new(0.0, 0.0, 0.0); assert_eq!(t1 + t2, Translation3D::new(1.0, 2.0, 3.0)); let t1 = Translation3D::new(1.0, 2.0, 3.0); let t2 = Translation3D::new(-4.0, -5.0, -6.0); assert_eq!(t1 + t2, Translation3D::new(-3.0, -3.0, -3.0)); let t1 = Translation3D::new(0.0, 0.0, 0.0); let t2 = Translation3D::new(0.0, 0.0, 0.0); assert_eq!(t1 + t2, Translation3D::new(0.0, 0.0, 0.0)); } #[test] pub fn test_add_assign() { let mut t = Translation3D::new(1.0, 2.0, 3.0); t += Translation3D::new(4.0, 5.0, 6.0); assert_eq!(t, Translation3D::new(5.0, 7.0, 9.0)); let mut t = Translation3D::new(1.0, 2.0, 3.0); t += Translation3D::new(0.0, 0.0, 0.0); assert_eq!(t, Translation3D::new(1.0, 2.0, 3.0)); let mut t = Translation3D::new(1.0, 2.0, 3.0); t += Translation3D::new(-4.0, -5.0, -6.0); assert_eq!(t, Translation3D::new(-3.0, -3.0, -3.0)); let mut t = Translation3D::new(0.0, 0.0, 0.0); t += Translation3D::new(0.0, 0.0, 0.0); assert_eq!(t, Translation3D::new(0.0, 0.0, 0.0)); } #[test] pub fn test_sub() { let t1 = Translation3D::new(1.0, 2.0, 3.0); let t2 = Translation3D::new(4.0, 5.0, 6.0); assert_eq!(t1 - t2, Translation3D::new(-3.0, -3.0, -3.0)); let t1 = Translation3D::new(1.0, 2.0, 3.0); let t2 = Translation3D::new(0.0, 0.0, 0.0); assert_eq!(t1 - t2, Translation3D::new(1.0, 2.0, 3.0)); let t1 = Translation3D::new(1.0, 2.0, 3.0); let t2 = Translation3D::new(-4.0, -5.0, -6.0); assert_eq!(t1 - t2, Translation3D::new(5.0, 7.0, 9.0)); let t1 = Translation3D::new(0.0, 0.0, 0.0); let t2 = Translation3D::new(0.0, 0.0, 0.0); assert_eq!(t1 - t2, Translation3D::new(0.0, 0.0, 0.0)); } #[test] pub fn test_sub_assign() { let mut t = Translation3D::new(1.0, 2.0, 3.0); t -= Translation3D::new(4.0, 5.0, 6.0); assert_eq!(t, Translation3D::new(-3.0, -3.0, -3.0)); let mut t = Translation3D::new(1.0, 2.0, 3.0); t -= Translation3D::new(0.0, 0.0, 0.0); assert_eq!(t, Translation3D::new(1.0, 2.0, 3.0)); let mut t = Translation3D::new(1.0, 2.0, 3.0); t -= Translation3D::new(-4.0, -5.0, -6.0); assert_eq!(t, Translation3D::new(5.0, 7.0, 9.0)); let mut t = Translation3D::new(0.0, 0.0, 0.0); t -= Translation3D::new(0.0, 0.0, 0.0); assert_eq!(t, Translation3D::new(0.0, 0.0, 0.0)); } } }