diff options
Diffstat (limited to '')
22 files changed, 16252 insertions, 0 deletions
diff --git a/third_party/rust/euclid/src/angle.rs b/third_party/rust/euclid/src/angle.rs new file mode 100644 index 0000000000..a503df6efc --- /dev/null +++ b/third_party/rust/euclid/src/angle.rs @@ -0,0 +1,365 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::approxeq::ApproxEq; +use crate::trig::Trig; +use core::cmp::{Eq, PartialEq}; +use core::hash::Hash; +use core::iter::Sum; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; +use num_traits::real::Real; +use num_traits::{Float, FloatConst, NumCast, One, Zero}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// An angle in radians +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct Angle<T> { + pub radians: T, +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable> Zeroable for Angle<T> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod> Pod for Angle<T> {} + +impl<T> Angle<T> { + #[inline] + pub fn radians(radians: T) -> Self { + Angle { radians } + } + + #[inline] + pub fn get(self) -> T { + self.radians + } +} + +impl<T> Angle<T> +where + T: Trig, +{ + #[inline] + pub fn degrees(deg: T) -> Self { + Angle { + radians: T::degrees_to_radians(deg), + } + } + + #[inline] + pub fn to_degrees(self) -> T { + T::radians_to_degrees(self.radians) + } +} + +impl<T> Angle<T> +where + T: Rem<Output = T> + Sub<Output = T> + Add<Output = T> + Zero + FloatConst + PartialOrd + Copy, +{ + /// Returns this angle in the [0..2*PI[ range. + pub fn positive(&self) -> Self { + let two_pi = T::PI() + T::PI(); + let mut a = self.radians % two_pi; + if a < T::zero() { + a = a + two_pi; + } + Angle::radians(a) + } + + /// Returns this angle in the ]-PI..PI] range. + pub fn signed(&self) -> Self { + Angle::pi() - (Angle::pi() - *self).positive() + } +} + +impl<T> Angle<T> +where + T: Rem<Output = T> + + Mul<Output = T> + + Sub<Output = T> + + Add<Output = T> + + One + + FloatConst + + Copy, +{ + /// Returns the shortest signed angle between two angles. + /// + /// Takes wrapping and signs into account. + pub fn angle_to(&self, to: Self) -> Self { + let two = T::one() + T::one(); + let max = T::PI() * two; + let d = (to.radians - self.radians) % max; + + Angle::radians(two * d % max - d) + } + + /// Linear interpolation between two angles, using the shortest path. + pub fn lerp(&self, other: Self, t: T) -> Self { + *self + self.angle_to(other) * t + } +} + +impl<T> Angle<T> +where + T: Float, +{ + /// Returns true if the angle is a finite number. + #[inline] + pub fn is_finite(self) -> bool { + self.radians.is_finite() + } +} + +impl<T> Angle<T> +where + T: Real, +{ + /// Returns (sin(self), cos(self)). + pub fn sin_cos(self) -> (T, T) { + self.radians.sin_cos() + } +} + +impl<T> Angle<T> +where + T: Zero, +{ + pub fn zero() -> Self { + Angle::radians(T::zero()) + } +} + +impl<T> Angle<T> +where + T: FloatConst + Add<Output = T>, +{ + pub fn pi() -> Self { + Angle::radians(T::PI()) + } + + pub fn two_pi() -> Self { + Angle::radians(T::PI() + T::PI()) + } + + pub fn frac_pi_2() -> Self { + Angle::radians(T::FRAC_PI_2()) + } + + pub fn frac_pi_3() -> Self { + Angle::radians(T::FRAC_PI_3()) + } + + pub fn frac_pi_4() -> Self { + Angle::radians(T::FRAC_PI_4()) + } +} + +impl<T> Angle<T> +where + T: NumCast + Copy, +{ + /// Cast from one numeric representation to another. + #[inline] + pub fn cast<NewT: NumCast>(&self) -> Angle<NewT> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another. + pub fn try_cast<NewT: NumCast>(&self) -> Option<Angle<NewT>> { + NumCast::from(self.radians).map(|radians| Angle { radians }) + } + + // Convenience functions for common casts. + + /// Cast angle to `f32`. + #[inline] + pub fn to_f32(&self) -> Angle<f32> { + self.cast() + } + + /// Cast angle `f64`. + #[inline] + pub fn to_f64(&self) -> Angle<f64> { + self.cast() + } +} + +impl<T: Add<T, Output = T>> Add for Angle<T> { + type Output = Self; + fn add(self, other: Self) -> Self { + Self::radians(self.radians + other.radians) + } +} + +impl<T: Copy + Add<T, Output = T>> Add<&Self> for Angle<T> { + type Output = Self; + fn add(self, other: &Self) -> Self { + Self::radians(self.radians + other.radians) + } +} + +impl<T: Add + Zero> Sum for Angle<T> { + fn sum<I: Iterator<Item=Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<'a, T: 'a + Add + Copy + Zero> Sum<&'a Self> for Angle<T> { + fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<T: AddAssign<T>> AddAssign for Angle<T> { + fn add_assign(&mut self, other: Angle<T>) { + self.radians += other.radians; + } +} + +impl<T: Sub<T, Output = T>> Sub<Angle<T>> for Angle<T> { + type Output = Angle<T>; + fn sub(self, other: Angle<T>) -> <Self as Sub>::Output { + Angle::radians(self.radians - other.radians) + } +} + +impl<T: SubAssign<T>> SubAssign for Angle<T> { + fn sub_assign(&mut self, other: Angle<T>) { + self.radians -= other.radians; + } +} + +impl<T: Div<T, Output = T>> Div<Angle<T>> for Angle<T> { + type Output = T; + #[inline] + fn div(self, other: Angle<T>) -> T { + self.radians / other.radians + } +} + +impl<T: Div<T, Output = T>> Div<T> for Angle<T> { + type Output = Angle<T>; + #[inline] + fn div(self, factor: T) -> Angle<T> { + Angle::radians(self.radians / factor) + } +} + +impl<T: DivAssign<T>> DivAssign<T> for Angle<T> { + fn div_assign(&mut self, factor: T) { + self.radians /= factor; + } +} + +impl<T: Mul<T, Output = T>> Mul<T> for Angle<T> { + type Output = Angle<T>; + #[inline] + fn mul(self, factor: T) -> Angle<T> { + Angle::radians(self.radians * factor) + } +} + +impl<T: MulAssign<T>> MulAssign<T> for Angle<T> { + fn mul_assign(&mut self, factor: T) { + self.radians *= factor; + } +} + +impl<T: Neg<Output = T>> Neg for Angle<T> { + type Output = Self; + fn neg(self) -> Self { + Angle::radians(-self.radians) + } +} + +impl<T: ApproxEq<T>> ApproxEq<T> for Angle<T> { + #[inline] + fn approx_epsilon() -> T { + T::approx_epsilon() + } + + #[inline] + fn approx_eq_eps(&self, other: &Angle<T>, approx_epsilon: &T) -> bool { + self.radians.approx_eq_eps(&other.radians, approx_epsilon) + } +} + +#[test] +fn wrap_angles() { + use core::f32::consts::{FRAC_PI_2, PI}; + + assert!(Angle::radians(0.0).positive().approx_eq(&Angle::zero())); + assert!(Angle::radians(FRAC_PI_2) + .positive() + .approx_eq(&Angle::frac_pi_2())); + assert!(Angle::radians(-FRAC_PI_2) + .positive() + .approx_eq(&Angle::radians(3.0 * FRAC_PI_2))); + assert!(Angle::radians(3.0 * FRAC_PI_2) + .positive() + .approx_eq(&Angle::radians(3.0 * FRAC_PI_2))); + assert!(Angle::radians(5.0 * FRAC_PI_2) + .positive() + .approx_eq(&Angle::frac_pi_2())); + assert!(Angle::radians(2.0 * PI) + .positive() + .approx_eq(&Angle::zero())); + assert!(Angle::radians(-2.0 * PI) + .positive() + .approx_eq(&Angle::zero())); + assert!(Angle::radians(PI).positive().approx_eq(&Angle::pi())); + assert!(Angle::radians(-PI).positive().approx_eq(&Angle::pi())); + + assert!(Angle::radians(FRAC_PI_2) + .signed() + .approx_eq(&Angle::frac_pi_2())); + assert!(Angle::radians(3.0 * FRAC_PI_2) + .signed() + .approx_eq(&-Angle::frac_pi_2())); + assert!(Angle::radians(5.0 * FRAC_PI_2) + .signed() + .approx_eq(&Angle::frac_pi_2())); + assert!(Angle::radians(2.0 * PI).signed().approx_eq(&Angle::zero())); + assert!(Angle::radians(-2.0 * PI).signed().approx_eq(&Angle::zero())); + assert!(Angle::radians(-PI).signed().approx_eq(&Angle::pi())); + assert!(Angle::radians(PI).signed().approx_eq(&Angle::pi())); +} + +#[test] +fn lerp() { + type A = Angle<f32>; + + let a = A::radians(1.0); + let b = A::radians(2.0); + assert!(a.lerp(b, 0.25).approx_eq(&Angle::radians(1.25))); + assert!(a.lerp(b, 0.5).approx_eq(&Angle::radians(1.5))); + assert!(a.lerp(b, 0.75).approx_eq(&Angle::radians(1.75))); + assert!(a + .lerp(b + A::two_pi(), 0.75) + .approx_eq(&Angle::radians(1.75))); + assert!(a + .lerp(b - A::two_pi(), 0.75) + .approx_eq(&Angle::radians(1.75))); + assert!(a + .lerp(b + A::two_pi() * 5.0, 0.75) + .approx_eq(&Angle::radians(1.75))); +} + +#[test] +fn sum() { + type A = Angle<f32>; + let angles = [A::radians(1.0), A::radians(2.0), A::radians(3.0)]; + let sum = A::radians(6.0); + assert_eq!(angles.iter().sum::<A>(), sum); +} diff --git a/third_party/rust/euclid/src/approxeq.rs b/third_party/rust/euclid/src/approxeq.rs new file mode 100644 index 0000000000..911f5268a4 --- /dev/null +++ b/third_party/rust/euclid/src/approxeq.rs @@ -0,0 +1,42 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// Trait for testing approximate equality +pub trait ApproxEq<Eps> { + /// Default epsilon value + fn approx_epsilon() -> Eps; + + /// Returns `true` is this object is approximately equal to the other one, using + /// a provided epsilon value. + fn approx_eq_eps(&self, other: &Self, approx_epsilon: &Eps) -> bool; + + /// Returns `true` is this object is approximately equal to the other one, using + /// the `approx_epsilon()` epsilon value. + fn approx_eq(&self, other: &Self) -> bool { + self.approx_eq_eps(other, &Self::approx_epsilon()) + } +} + +macro_rules! approx_eq { + ($ty:ty, $eps:expr) => { + impl ApproxEq<$ty> for $ty { + #[inline] + fn approx_epsilon() -> $ty { + $eps + } + #[inline] + fn approx_eq_eps(&self, other: &$ty, approx_epsilon: &$ty) -> bool { + num_traits::Float::abs(*self - *other) < *approx_epsilon + } + } + }; +} + +approx_eq!(f32, 1.0e-6); +approx_eq!(f64, 1.0e-6); diff --git a/third_party/rust/euclid/src/approxord.rs b/third_party/rust/euclid/src/approxord.rs new file mode 100644 index 0000000000..db6207da39 --- /dev/null +++ b/third_party/rust/euclid/src/approxord.rs @@ -0,0 +1,44 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Utilities for testing approximate ordering - especially true for +//! floating point types, where NaN's cannot be ordered. + +pub fn min<T: PartialOrd>(x: T, y: T) -> T { + if x <= y { + x + } else { + y + } +} + +pub fn max<T: PartialOrd>(x: T, y: T) -> T { + if x >= y { + x + } else { + y + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_min() { + assert!(min(0u32, 1u32) == 0u32); + assert!(min(-1.0f32, 0.0f32) == -1.0f32); + } + + #[test] + fn test_max() { + assert!(max(0u32, 1u32) == 1u32); + assert!(max(-1.0f32, 0.0f32) == 0.0f32); + } +} diff --git a/third_party/rust/euclid/src/box2d.rs b/third_party/rust/euclid/src/box2d.rs new file mode 100644 index 0000000000..b59dacf162 --- /dev/null +++ b/third_party/rust/euclid/src/box2d.rs @@ -0,0 +1,903 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::approxord::{max, min}; +use crate::num::*; +use crate::point::{point2, Point2D}; +use crate::rect::Rect; +use crate::scale::Scale; +use crate::side_offsets::SideOffsets2D; +use crate::size::Size2D; +use crate::vector::{vec2, Vector2D}; + +use num_traits::{NumCast, Float}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +use core::borrow::Borrow; +use core::cmp::PartialOrd; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub, Range}; + +/// A 2d axis aligned rectangle represented by its minimum and maximum coordinates. +/// +/// # Representation +/// +/// This struct is similar to [`Rect`], but stores rectangle as two endpoints +/// instead of origin point and size. Such representation has several advantages over +/// [`Rect`] representation: +/// - Several operations are more efficient with `Box2D`, including [`intersection`], +/// [`union`], and point-in-rect. +/// - The representation is less susceptible to overflow. With [`Rect`], computation +/// of second point can overflow for a large range of values of origin and size. +/// However, with `Box2D`, computation of [`size`] cannot overflow if the coordinates +/// are signed and the resulting size is unsigned. +/// +/// A known disadvantage of `Box2D` is that translating the rectangle requires translating +/// both points, whereas translating [`Rect`] only requires translating one point. +/// +/// # Empty box +/// +/// A box is considered empty (see [`is_empty`]) if any of the following is true: +/// - it's area is empty, +/// - it's area is negative (`min.x > max.x` or `min.y > max.y`), +/// - it contains NaNs. +/// +/// [`Rect`]: struct.Rect.html +/// [`intersection`]: #method.intersection +/// [`is_empty`]: #method.is_empty +/// [`union`]: #method.union +/// [`size`]: #method.size +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) +)] +pub struct Box2D<T, U> { + pub min: Point2D<T, U>, + pub max: Point2D<T, U>, +} + +impl<T: Hash, U> Hash for Box2D<T, U> { + fn hash<H: Hasher>(&self, h: &mut H) { + self.min.hash(h); + self.max.hash(h); + } +} + +impl<T: Copy, U> Copy for Box2D<T, U> {} + +impl<T: Clone, U> Clone for Box2D<T, U> { + fn clone(&self) -> Self { + Self::new(self.min.clone(), self.max.clone()) + } +} + +impl<T: PartialEq, U> PartialEq for Box2D<T, U> { + fn eq(&self, other: &Self) -> bool { + self.min.eq(&other.min) && self.max.eq(&other.max) + } +} + +impl<T: Eq, U> Eq for Box2D<T, U> {} + +impl<T: fmt::Debug, U> fmt::Debug for Box2D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Box2D") + .field(&self.min) + .field(&self.max) + .finish() + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Box2D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Box2D<T, U> {} + +impl<T, U> Box2D<T, U> { + /// Constructor. + #[inline] + pub const fn new(min: Point2D<T, U>, max: Point2D<T, U>) -> Self { + Box2D { min, max } + } + + /// Constructor. + #[inline] + pub fn from_origin_and_size(origin: Point2D<T, U>, size: Size2D<T, U>) -> Self + where + T: Copy + Add<T, Output = T> + { + Box2D { + min: origin, + max: point2(origin.x + size.width, origin.y + size.height), + } + } + + /// Creates a Box2D of the given size, at offset zero. + #[inline] + pub fn from_size(size: Size2D<T, U>) -> Self where T: Zero { + Box2D { + min: Point2D::zero(), + max: point2(size.width, size.height), + } + } +} + +impl<T, U> Box2D<T, U> +where + T: PartialOrd, +{ + /// Returns true if the box has a negative area. + /// + /// The common interpretation for a negative box is to consider it empty. It can be obtained + /// by calculating the intersection of two boxes that do not intersect. + #[inline] + pub fn is_negative(&self) -> bool { + self.max.x < self.min.x || self.max.y < self.min.y + } + + /// Returns true if the size is zero, negative or NaN. + #[inline] + pub fn is_empty(&self) -> bool { + !(self.max.x > self.min.x && self.max.y > self.min.y) + } + + /// Returns `true` if the two boxes intersect. + #[inline] + pub fn intersects(&self, other: &Self) -> bool { + self.min.x < other.max.x + && self.max.x > other.min.x + && self.min.y < other.max.y + && self.max.y > other.min.y + } + + /// Returns `true` if this box contains the point. Points are considered + /// in the box if they are on the front, left or top faces, but outside if they + /// are on the back, right or bottom faces. + #[inline] + pub fn contains(&self, p: Point2D<T, U>) -> bool { + self.min.x <= p.x && p.x < self.max.x && self.min.y <= p.y && p.y < self.max.y + } + + /// Returns `true` if this box contains the interior of the other box. Always + /// returns `true` if other is empty, and always returns `false` if other is + /// nonempty but this box is empty. + #[inline] + pub fn contains_box(&self, other: &Self) -> bool { + other.is_empty() + || (self.min.x <= other.min.x + && other.max.x <= self.max.x + && self.min.y <= other.min.y + && other.max.y <= self.max.y) + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy + PartialOrd, +{ + #[inline] + pub fn to_non_empty(&self) -> Option<Self> { + if self.is_empty() { + return None; + } + + Some(*self) + } + + /// Computes the intersection of two boxes, returning `None` if the boxes do not intersect. + #[inline] + pub fn intersection(&self, other: &Self) -> Option<Self> { + let b = self.intersection_unchecked(other); + + if b.is_empty() { + return None; + } + + Some(b) + } + + /// Computes the intersection of two boxes without check whether they do intersect. + /// + /// The result is a negative box if the boxes do not intersect. + /// This can be useful for computing the intersection of more than two boxes, as + /// it is possible to chain multiple intersection_unchecked calls and check for + /// empty/negative result at the end. + #[inline] + pub fn intersection_unchecked(&self, other: &Self) -> Self { + Box2D { + min: point2(max(self.min.x, other.min.x), max(self.min.y, other.min.y)), + max: point2(min(self.max.x, other.max.x), min(self.max.y, other.max.y)), + } + } + + /// Computes the union of two boxes. + /// + /// If either of the boxes is empty, the other one is returned. + #[inline] + pub fn union(&self, other: &Self) -> Self { + if other.is_empty() { + return *self; + } + if self.is_empty() { + return *other; + } + + Box2D { + min: point2(min(self.min.x, other.min.x), min(self.min.y, other.min.y)), + max: point2(max(self.max.x, other.max.x), max(self.max.y, other.max.y)), + } + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy + Add<T, Output = T>, +{ + /// Returns the same box, translated by a vector. + #[inline] + pub fn translate(&self, by: Vector2D<T, U>) -> Self { + Box2D { + min: self.min + by, + max: self.max + by, + } + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy + Sub<T, Output = T>, +{ + #[inline] + pub fn size(&self) -> Size2D<T, U> { + (self.max - self.min).to_size() + } + + /// Change the size of the box by adjusting the max endpoint + /// without modifying the min endpoint. + #[inline] + pub fn set_size(&mut self, size: Size2D<T, U>) { + let diff = (self.size() - size).to_vector(); + self.max -= diff; + } + + #[inline] + pub fn width(&self) -> T { + self.max.x - self.min.x + } + + #[inline] + pub fn height(&self) -> T { + self.max.y - self.min.y + } + + #[inline] + pub fn to_rect(&self) -> Rect<T, U> { + Rect { + origin: self.min, + size: self.size(), + } + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy + Add<T, Output = T> + Sub<T, Output = T>, +{ + /// Inflates the box by the specified sizes on each side respectively. + #[inline] + #[must_use] + pub fn inflate(&self, width: T, height: T) -> Self { + Box2D { + min: point2(self.min.x - width, self.min.y - height), + max: point2(self.max.x + width, self.max.y + height), + } + } + + /// Calculate the size and position of an inner box. + /// + /// Subtracts the side offsets from all sides. The horizontal, vertical + /// and applicate offsets must not be larger than the original side length. + pub fn inner_box(&self, offsets: SideOffsets2D<T, U>) -> Self { + Box2D { + min: self.min + vec2(offsets.left, offsets.top), + max: self.max - vec2(offsets.right, offsets.bottom), + } + } + + /// Calculate the b and position of an outer box. + /// + /// Add the offsets to all sides. The expanded box is returned. + pub fn outer_box(&self, offsets: SideOffsets2D<T, U>) -> Self { + Box2D { + min: self.min - vec2(offsets.left, offsets.top), + max: self.max + vec2(offsets.right, offsets.bottom), + } + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy + Zero + PartialOrd, +{ + /// Returns the smallest box containing all of the provided points. + pub fn from_points<I>(points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow<Point2D<T, U>>, + { + let mut points = points.into_iter(); + + let (mut min_x, mut min_y) = match points.next() { + Some(first) => first.borrow().to_tuple(), + None => return Box2D::zero(), + }; + + let (mut max_x, mut max_y) = (min_x, min_y); + for point in points { + let p = point.borrow(); + if p.x < min_x { + min_x = p.x + } + if p.x > max_x { + max_x = p.x + } + if p.y < min_y { + min_y = p.y + } + if p.y > max_y { + max_y = p.y + } + } + + Box2D { + min: point2(min_x, min_y), + max: point2(max_x, max_y), + } + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>, +{ + /// Linearly interpolate between this box and another box. + #[inline] + pub fn lerp(&self, other: Self, t: T) -> Self { + Self::new(self.min.lerp(other.min, t), self.max.lerp(other.max, t)) + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy + One + Add<Output = T> + Div<Output = T>, +{ + pub fn center(&self) -> Point2D<T, U> { + let two = T::one() + T::one(); + (self.min + self.max.to_vector()) / two + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy + Mul<T, Output = T> + Sub<T, Output = T>, +{ + #[inline] + pub fn area(&self) -> T { + let size = self.size(); + size.width * size.height + } +} + +impl<T, U> Box2D<T, U> +where + T: Zero, +{ + /// Constructor, setting all sides to zero. + pub fn zero() -> Self { + Box2D::new(Point2D::zero(), Point2D::zero()) + } +} + +impl<T: Copy + Mul, U> Mul<T> for Box2D<T, U> { + type Output = Box2D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Box2D::new(self.min * scale, self.max * scale) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<T> for Box2D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self *= Scale::new(scale); + } +} + +impl<T: Copy + Div, U> Div<T> for Box2D<T, U> { + type Output = Box2D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Box2D::new(self.min / scale, self.max / scale) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<T> for Box2D<T, U> { + #[inline] + fn div_assign(&mut self, scale: T) { + *self /= Scale::new(scale); + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Box2D<T, U1> { + type Output = Box2D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + Box2D::new(self.min * scale, self.max * scale) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Box2D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: Scale<T, U, U>) { + self.min *= scale; + self.max *= scale; + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Box2D<T, U2> { + type Output = Box2D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + Box2D::new(self.min / scale, self.max / scale) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Box2D<T, U> { + #[inline] + fn div_assign(&mut self, scale: Scale<T, U, U>) { + self.min /= scale; + self.max /= scale; + } +} + +impl<T, U> Box2D<T, U> +where + T: Copy, +{ + #[inline] + pub fn x_range(&self) -> Range<T> { + self.min.x..self.max.x + } + + #[inline] + pub fn y_range(&self) -> Range<T> { + self.min.y..self.max.y + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Box2D<T, UnknownUnit> { + Box2D::new(self.min.to_untyped(), self.max.to_untyped()) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(c: &Box2D<T, UnknownUnit>) -> Box2D<T, U> { + Box2D::new(Point2D::from_untyped(c.min), Point2D::from_untyped(c.max)) + } + + /// Cast the unit + #[inline] + pub fn cast_unit<V>(&self) -> Box2D<T, V> { + Box2D::new(self.min.cast_unit(), self.max.cast_unit()) + } + + #[inline] + pub fn scale<S: Copy>(&self, x: S, y: S) -> Self + where + T: Mul<S, Output = T>, + { + Box2D { + min: point2(self.min.x * x, self.min.y * y), + max: point2(self.max.x * x, self.max.y * y), + } + } +} + +impl<T: NumCast + Copy, U> Box2D<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), round_in or round_out() before casting. + #[inline] + pub fn cast<NewT: NumCast>(&self) -> Box2D<NewT, U> { + Box2D::new(self.min.cast(), self.max.cast()) + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), round_in or round_out() before casting. + pub fn try_cast<NewT: NumCast>(&self) -> Option<Box2D<NewT, U>> { + match (self.min.try_cast(), self.max.try_cast()) { + (Some(a), Some(b)) => Some(Box2D::new(a, b)), + _ => None, + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` box. + #[inline] + pub fn to_f32(&self) -> Box2D<f32, U> { + self.cast() + } + + /// Cast into an `f64` box. + #[inline] + pub fn to_f64(&self) -> Box2D<f64, U> { + self.cast() + } + + /// Cast into an `usize` box, truncating decimals if any. + /// + /// When casting from floating point boxes, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_usize(&self) -> Box2D<usize, U> { + self.cast() + } + + /// Cast into an `u32` box, truncating decimals if any. + /// + /// When casting from floating point boxes, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_u32(&self) -> Box2D<u32, U> { + self.cast() + } + + /// Cast into an `i32` box, truncating decimals if any. + /// + /// When casting from floating point boxes, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_i32(&self) -> Box2D<i32, U> { + self.cast() + } + + /// Cast into an `i64` box, truncating decimals if any. + /// + /// When casting from floating point boxes, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_i64(&self) -> Box2D<i64, U> { + self.cast() + } +} + +impl<T: Float, U> Box2D<T, U> { + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.min.is_finite() && self.max.is_finite() + } +} + +impl<T, U> Box2D<T, U> +where + T: Round, +{ + /// Return a box with edges rounded to integer coordinates, such that + /// the returned box has the same set of pixel centers as the original + /// one. + /// Values equal to 0.5 round up. + /// Suitable for most places where integral device coordinates + /// are needed, but note that any translation should be applied first to + /// avoid pixel rounding errors. + /// Note that this is *not* rounding to nearest integer if the values are negative. + /// They are always rounding as floor(n + 0.5). + #[must_use] + pub fn round(&self) -> Self { + Box2D::new(self.min.round(), self.max.round()) + } +} + +impl<T, U> Box2D<T, U> +where + T: Floor + Ceil, +{ + /// Return a box with faces/edges rounded to integer coordinates, such that + /// the original box contains the resulting box. + #[must_use] + pub fn round_in(&self) -> Self { + let min = self.min.ceil(); + let max = self.max.floor(); + Box2D { min, max } + } + + /// Return a box with faces/edges rounded to integer coordinates, such that + /// the original box is contained in the resulting box. + #[must_use] + pub fn round_out(&self) -> Self { + let min = self.min.floor(); + let max = self.max.ceil(); + Box2D { min, max } + } +} + +impl<T, U> From<Size2D<T, U>> for Box2D<T, U> +where + T: Copy + Zero + PartialOrd, +{ + fn from(b: Size2D<T, U>) -> Self { + Self::from_size(b) + } +} + +impl<T: Default, U> Default for Box2D<T, U> { + fn default() -> Self { + Box2D { + min: Default::default(), + max: Default::default(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::default::Box2D; + use crate::side_offsets::SideOffsets2D; + use crate::{point2, size2, vec2, Point2D}; + //use super::*; + + #[test] + fn test_size() { + let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); + assert_eq!(b.size().width, 20.0); + assert_eq!(b.size().height, 20.0); + } + + #[test] + fn test_width_height() { + let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); + assert!(b.width() == 20.0); + assert!(b.height() == 20.0); + } + + #[test] + fn test_center() { + let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); + assert_eq!(b.center(), Point2D::zero()); + } + + #[test] + fn test_area() { + let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); + assert_eq!(b.area(), 400.0); + } + + #[test] + fn test_from_points() { + let b = Box2D::from_points(&[point2(50.0, 160.0), point2(100.0, 25.0)]); + assert_eq!(b.min, point2(50.0, 25.0)); + assert_eq!(b.max, point2(100.0, 160.0)); + } + + #[test] + fn test_round_in() { + let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round_in(); + assert_eq!(b.min.x, -25.0); + assert_eq!(b.min.y, -40.0); + assert_eq!(b.max.x, 60.0); + assert_eq!(b.max.y, 36.0); + } + + #[test] + fn test_round_out() { + let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round_out(); + assert_eq!(b.min.x, -26.0); + assert_eq!(b.min.y, -41.0); + assert_eq!(b.max.x, 61.0); + assert_eq!(b.max.y, 37.0); + } + + #[test] + fn test_round() { + let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round(); + assert_eq!(b.min.x, -25.0); + assert_eq!(b.min.y, -40.0); + assert_eq!(b.max.x, 60.0); + assert_eq!(b.max.y, 37.0); + } + + #[test] + fn test_from_size() { + let b = Box2D::from_size(size2(30.0, 40.0)); + assert!(b.min == Point2D::zero()); + assert!(b.size().width == 30.0); + assert!(b.size().height == 40.0); + } + + #[test] + fn test_inner_box() { + let b = Box2D::from_points(&[point2(50.0, 25.0), point2(100.0, 160.0)]); + let b = b.inner_box(SideOffsets2D::new(10.0, 20.0, 5.0, 10.0)); + assert_eq!(b.max.x, 80.0); + assert_eq!(b.max.y, 155.0); + assert_eq!(b.min.x, 60.0); + assert_eq!(b.min.y, 35.0); + } + + #[test] + fn test_outer_box() { + let b = Box2D::from_points(&[point2(50.0, 25.0), point2(100.0, 160.0)]); + let b = b.outer_box(SideOffsets2D::new(10.0, 20.0, 5.0, 10.0)); + assert_eq!(b.max.x, 120.0); + assert_eq!(b.max.y, 165.0); + assert_eq!(b.min.x, 40.0); + assert_eq!(b.min.y, 15.0); + } + + #[test] + fn test_translate() { + let size = size2(15.0, 15.0); + let mut center = (size / 2.0).to_vector().to_point(); + let b = Box2D::from_size(size); + assert_eq!(b.center(), center); + let translation = vec2(10.0, 2.5); + let b = b.translate(translation); + center += translation; + assert_eq!(b.center(), center); + assert_eq!(b.max.x, 25.0); + assert_eq!(b.max.y, 17.5); + assert_eq!(b.min.x, 10.0); + assert_eq!(b.min.y, 2.5); + } + + #[test] + fn test_union() { + let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(0.0, 20.0)]); + let b2 = Box2D::from_points(&[point2(0.0, 20.0), point2(20.0, -20.0)]); + let b = b1.union(&b2); + assert_eq!(b.max.x, 20.0); + assert_eq!(b.max.y, 20.0); + assert_eq!(b.min.x, -20.0); + assert_eq!(b.min.y, -20.0); + } + + #[test] + fn test_intersects() { + let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); + let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); + assert!(b1.intersects(&b2)); + } + + #[test] + fn test_intersection_unchecked() { + let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); + let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); + let b = b1.intersection_unchecked(&b2); + assert_eq!(b.max.x, 10.0); + assert_eq!(b.max.y, 20.0); + assert_eq!(b.min.x, -10.0); + assert_eq!(b.min.y, -20.0); + } + + #[test] + fn test_intersection() { + let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); + let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); + assert!(b1.intersection(&b2).is_some()); + + let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(-10.0, 20.0)]); + let b2 = Box2D::from_points(&[point2(10.0, 20.0), point2(15.0, -20.0)]); + assert!(b1.intersection(&b2).is_none()); + } + + #[test] + fn test_scale() { + let b = Box2D::from_points(&[point2(-10.0, -10.0), point2(10.0, 10.0)]); + let b = b.scale(0.5, 0.5); + assert_eq!(b.max.x, 5.0); + assert_eq!(b.max.y, 5.0); + assert_eq!(b.min.x, -5.0); + assert_eq!(b.min.y, -5.0); + } + + #[test] + fn test_lerp() { + let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(-10.0, -10.0)]); + let b2 = Box2D::from_points(&[point2(10.0, 10.0), point2(20.0, 20.0)]); + let b = b1.lerp(b2, 0.5); + assert_eq!(b.center(), Point2D::zero()); + assert_eq!(b.size().width, 10.0); + assert_eq!(b.size().height, 10.0); + } + + #[test] + fn test_contains() { + let b = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); + assert!(b.contains(point2(-15.3, 10.5))); + } + + #[test] + fn test_contains_box() { + let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); + let b2 = Box2D::from_points(&[point2(-14.3, -16.5), point2(6.7, 17.6)]); + assert!(b1.contains_box(&b2)); + } + + #[test] + fn test_inflate() { + let b = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); + let b = b.inflate(10.0, 5.0); + assert_eq!(b.size().width, 60.0); + assert_eq!(b.size().height, 50.0); + assert_eq!(b.center(), Point2D::zero()); + } + + #[test] + fn test_is_empty() { + for i in 0..2 { + let mut coords_neg = [-20.0, -20.0]; + let mut coords_pos = [20.0, 20.0]; + coords_neg[i] = 0.0; + coords_pos[i] = 0.0; + let b = Box2D::from_points(&[Point2D::from(coords_neg), Point2D::from(coords_pos)]); + assert!(b.is_empty()); + } + } + + #[test] + fn test_nan_empty() { + use std::f32::NAN; + assert!(Box2D { min: point2(NAN, 2.0), max: point2(1.0, 3.0) }.is_empty()); + assert!(Box2D { min: point2(0.0, NAN), max: point2(1.0, 2.0) }.is_empty()); + assert!(Box2D { min: point2(1.0, -2.0), max: point2(NAN, 2.0) }.is_empty()); + assert!(Box2D { min: point2(1.0, -2.0), max: point2(0.0, NAN) }.is_empty()); + } + + #[test] + fn test_from_origin_and_size() { + let b = Box2D::from_origin_and_size(point2(1.0, 2.0), size2(3.0, 4.0)); + assert_eq!(b.min, point2(1.0, 2.0)); + assert_eq!(b.size(), size2(3.0, 4.0)); + } + + #[test] + fn test_set_size() { + let mut b = Box2D { + min: point2(1.0, 2.0), + max: point2(3.0, 4.0), + }; + b.set_size(size2(5.0, 6.0)); + + assert_eq!(b.min, point2(1.0, 2.0)); + assert_eq!(b.size(), size2(5.0, 6.0)); + } +} diff --git a/third_party/rust/euclid/src/box3d.rs b/third_party/rust/euclid/src/box3d.rs new file mode 100644 index 0000000000..34123f321d --- /dev/null +++ b/third_party/rust/euclid/src/box3d.rs @@ -0,0 +1,931 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::approxord::{max, min}; +use crate::num::*; +use crate::point::{point3, Point3D}; +use crate::scale::Scale; +use crate::size::Size3D; +use crate::vector::Vector3D; + +use num_traits::{NumCast, Float}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +use core::borrow::Borrow; +use core::cmp::PartialOrd; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub, Range}; + +/// An axis aligned 3D box represented by its minimum and maximum coordinates. +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) +)] +pub struct Box3D<T, U> { + pub min: Point3D<T, U>, + pub max: Point3D<T, U>, +} + +impl<T: Hash, U> Hash for Box3D<T, U> { + fn hash<H: Hasher>(&self, h: &mut H) { + self.min.hash(h); + self.max.hash(h); + } +} + +impl<T: Copy, U> Copy for Box3D<T, U> {} + +impl<T: Clone, U> Clone for Box3D<T, U> { + fn clone(&self) -> Self { + Self::new(self.min.clone(), self.max.clone()) + } +} + +impl<T: PartialEq, U> PartialEq for Box3D<T, U> { + fn eq(&self, other: &Self) -> bool { + self.min.eq(&other.min) && self.max.eq(&other.max) + } +} + +impl<T: Eq, U> Eq for Box3D<T, U> {} + +impl<T: fmt::Debug, U> fmt::Debug for Box3D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Box3D") + .field(&self.min) + .field(&self.max) + .finish() + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Box3D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Box3D<T, U> {} + +impl<T, U> Box3D<T, U> { + /// Constructor. + #[inline] + pub const fn new(min: Point3D<T, U>, max: Point3D<T, U>) -> Self { + Box3D { min, max } + } + + /// Creates a Box3D of the given size, at offset zero. + #[inline] + pub fn from_size(size: Size3D<T, U>) -> Self where T: Zero { + Box3D { + min: Point3D::zero(), + max: point3(size.width, size.height, size.depth), + } + } +} + +impl<T, U> Box3D<T, U> +where + T: PartialOrd, +{ + /// Returns true if the box has a negative volume. + /// + /// The common interpretation for a negative box is to consider it empty. It can be obtained + /// by calculating the intersection of two boxes that do not intersect. + #[inline] + pub fn is_negative(&self) -> bool { + self.max.x < self.min.x || self.max.y < self.min.y || self.max.z < self.min.z + } + + /// Returns true if the size is zero, negative or NaN. + #[inline] + pub fn is_empty(&self) -> bool { + !(self.max.x > self.min.x && self.max.y > self.min.y && self.max.z > self.min.z) + } + + #[inline] + pub fn intersects(&self, other: &Self) -> bool { + self.min.x < other.max.x + && self.max.x > other.min.x + && self.min.y < other.max.y + && self.max.y > other.min.y + && self.min.z < other.max.z + && self.max.z > other.min.z + } + + /// Returns `true` if this box3d contains the point. Points are considered + /// in the box3d if they are on the front, left or top faces, but outside if they + /// are on the back, right or bottom faces. + #[inline] + pub fn contains(&self, other: Point3D<T, U>) -> bool { + self.min.x <= other.x + && other.x < self.max.x + && self.min.y <= other.y + && other.y < self.max.y + && self.min.z <= other.z + && other.z < self.max.z + } + + /// Returns `true` if this box3d contains the interior of the other box3d. Always + /// returns `true` if other is empty, and always returns `false` if other is + /// nonempty but this box3d is empty. + #[inline] + pub fn contains_box(&self, other: &Self) -> bool { + other.is_empty() + || (self.min.x <= other.min.x + && other.max.x <= self.max.x + && self.min.y <= other.min.y + && other.max.y <= self.max.y + && self.min.z <= other.min.z + && other.max.z <= self.max.z) + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy + PartialOrd, +{ + #[inline] + pub fn to_non_empty(&self) -> Option<Self> { + if self.is_empty() { + return None; + } + + Some(*self) + } + + #[inline] + pub fn intersection(&self, other: &Self) -> Option<Self> { + let b = self.intersection_unchecked(other); + + if b.is_empty() { + return None; + } + + Some(b) + } + + pub fn intersection_unchecked(&self, other: &Self) -> Self { + let intersection_min = Point3D::new( + max(self.min.x, other.min.x), + max(self.min.y, other.min.y), + max(self.min.z, other.min.z), + ); + + let intersection_max = Point3D::new( + min(self.max.x, other.max.x), + min(self.max.y, other.max.y), + min(self.max.z, other.max.z), + ); + + Box3D::new(intersection_min, intersection_max) + } + + /// Computes the union of two boxes. + /// + /// If either of the boxes is empty, the other one is returned. + #[inline] + pub fn union(&self, other: &Self) -> Self { + if other.is_empty() { + return *self; + } + if self.is_empty() { + return *other; + } + + Box3D::new( + Point3D::new( + min(self.min.x, other.min.x), + min(self.min.y, other.min.y), + min(self.min.z, other.min.z), + ), + Point3D::new( + max(self.max.x, other.max.x), + max(self.max.y, other.max.y), + max(self.max.z, other.max.z), + ), + ) + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy + Add<T, Output = T>, +{ + /// Returns the same box3d, translated by a vector. + #[inline] + #[must_use] + pub fn translate(&self, by: Vector3D<T, U>) -> Self { + Box3D { + min: self.min + by, + max: self.max + by, + } + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy + Sub<T, Output = T>, +{ + #[inline] + pub fn size(&self) -> Size3D<T, U> { + Size3D::new( + self.max.x - self.min.x, + self.max.y - self.min.y, + self.max.z - self.min.z, + ) + } + + #[inline] + pub fn width(&self) -> T { + self.max.x - self.min.x + } + + #[inline] + pub fn height(&self) -> T { + self.max.y - self.min.y + } + + #[inline] + pub fn depth(&self) -> T { + self.max.z - self.min.z + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy + Add<T, Output = T> + Sub<T, Output = T>, +{ + /// Inflates the box by the specified sizes on each side respectively. + #[inline] + #[must_use] + pub fn inflate(&self, width: T, height: T, depth: T) -> Self { + Box3D::new( + Point3D::new(self.min.x - width, self.min.y - height, self.min.z - depth), + Point3D::new(self.max.x + width, self.max.y + height, self.max.z + depth), + ) + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy + Zero + PartialOrd, +{ + /// Returns the smallest box containing all of the provided points. + pub fn from_points<I>(points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow<Point3D<T, U>>, + { + let mut points = points.into_iter(); + + let (mut min_x, mut min_y, mut min_z) = match points.next() { + Some(first) => first.borrow().to_tuple(), + None => return Box3D::zero(), + }; + let (mut max_x, mut max_y, mut max_z) = (min_x, min_y, min_z); + + for point in points { + let p = point.borrow(); + if p.x < min_x { + min_x = p.x + } + if p.x > max_x { + max_x = p.x + } + if p.y < min_y { + min_y = p.y + } + if p.y > max_y { + max_y = p.y + } + if p.z < min_z { + min_z = p.z + } + if p.z > max_z { + max_z = p.z + } + } + + Box3D { + min: point3(min_x, min_y, min_z), + max: point3(max_x, max_y, max_z), + } + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>, +{ + /// Linearly interpolate between this box3d and another box3d. + #[inline] + pub fn lerp(&self, other: Self, t: T) -> Self { + Self::new(self.min.lerp(other.min, t), self.max.lerp(other.max, t)) + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy + One + Add<Output = T> + Div<Output = T>, +{ + pub fn center(&self) -> Point3D<T, U> { + let two = T::one() + T::one(); + (self.min + self.max.to_vector()) / two + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy + Mul<T, Output = T> + Sub<T, Output = T>, +{ + #[inline] + pub fn volume(&self) -> T { + let size = self.size(); + size.width * size.height * size.depth + } + + #[inline] + pub fn xy_area(&self) -> T { + let size = self.size(); + size.width * size.height + } + + #[inline] + pub fn yz_area(&self) -> T { + let size = self.size(); + size.depth * size.height + } + + #[inline] + pub fn xz_area(&self) -> T { + let size = self.size(); + size.depth * size.width + } +} + +impl<T, U> Box3D<T, U> +where + T: Zero, +{ + /// Constructor, setting all sides to zero. + pub fn zero() -> Self { + Box3D::new(Point3D::zero(), Point3D::zero()) + } +} + +impl<T: Copy + Mul, U> Mul<T> for Box3D<T, U> { + type Output = Box3D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Box3D::new(self.min * scale, self.max * scale) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<T> for Box3D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: T) { + self.min *= scale; + self.max *= scale; + } +} + +impl<T: Copy + Div, U> Div<T> for Box3D<T, U> { + type Output = Box3D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Box3D::new(self.min / scale.clone(), self.max / scale) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<T> for Box3D<T, U> { + #[inline] + fn div_assign(&mut self, scale: T) { + self.min /= scale; + self.max /= scale; + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Box3D<T, U1> { + type Output = Box3D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + Box3D::new(self.min * scale.clone(), self.max * scale) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Box3D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: Scale<T, U, U>) { + self.min *= scale.clone(); + self.max *= scale; + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Box3D<T, U2> { + type Output = Box3D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + Box3D::new(self.min / scale.clone(), self.max / scale) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Box3D<T, U> { + #[inline] + fn div_assign(&mut self, scale: Scale<T, U, U>) { + self.min /= scale.clone(); + self.max /= scale; + } +} + +impl<T, U> Box3D<T, U> +where + T: Copy, +{ + #[inline] + pub fn x_range(&self) -> Range<T> { + self.min.x..self.max.x + } + + #[inline] + pub fn y_range(&self) -> Range<T> { + self.min.y..self.max.y + } + + #[inline] + pub fn z_range(&self) -> Range<T> { + self.min.z..self.max.z + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Box3D<T, UnknownUnit> { + Box3D { + min: self.min.to_untyped(), + max: self.max.to_untyped(), + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(c: &Box3D<T, UnknownUnit>) -> Box3D<T, U> { + Box3D { + min: Point3D::from_untyped(c.min), + max: Point3D::from_untyped(c.max), + } + } + + /// Cast the unit + #[inline] + pub fn cast_unit<V>(&self) -> Box3D<T, V> { + Box3D::new(self.min.cast_unit(), self.max.cast_unit()) + } + + #[inline] + pub fn scale<S: Copy>(&self, x: S, y: S, z: S) -> Self + where + T: Mul<S, Output = T>, + { + Box3D::new( + Point3D::new(self.min.x * x, self.min.y * y, self.min.z * z), + Point3D::new(self.max.x * x, self.max.y * y, self.max.z * z), + ) + } +} + +impl<T: NumCast + Copy, U> Box3D<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), round_in or round_out() before casting. + #[inline] + pub fn cast<NewT: NumCast>(&self) -> Box3D<NewT, U> { + Box3D::new(self.min.cast(), self.max.cast()) + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), round_in or round_out() before casting. + pub fn try_cast<NewT: NumCast>(&self) -> Option<Box3D<NewT, U>> { + match (self.min.try_cast(), self.max.try_cast()) { + (Some(a), Some(b)) => Some(Box3D::new(a, b)), + _ => None, + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` box3d. + #[inline] + pub fn to_f32(&self) -> Box3D<f32, U> { + self.cast() + } + + /// Cast into an `f64` box3d. + #[inline] + pub fn to_f64(&self) -> Box3D<f64, U> { + self.cast() + } + + /// Cast into an `usize` box3d, truncating decimals if any. + /// + /// When casting from floating point cuboids, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_usize(&self) -> Box3D<usize, U> { + self.cast() + } + + /// Cast into an `u32` box3d, truncating decimals if any. + /// + /// When casting from floating point cuboids, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_u32(&self) -> Box3D<u32, U> { + self.cast() + } + + /// Cast into an `i32` box3d, truncating decimals if any. + /// + /// When casting from floating point cuboids, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_i32(&self) -> Box3D<i32, U> { + self.cast() + } + + /// Cast into an `i64` box3d, truncating decimals if any. + /// + /// When casting from floating point cuboids, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_i64(&self) -> Box3D<i64, U> { + self.cast() + } +} + +impl<T: Float, U> Box3D<T, U> { + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.min.is_finite() && self.max.is_finite() + } +} + +impl<T, U> Box3D<T, U> +where + T: Round, +{ + /// Return a box3d with edges rounded to integer coordinates, such that + /// the returned box3d has the same set of pixel centers as the original + /// one. + /// Values equal to 0.5 round up. + /// Suitable for most places where integral device coordinates + /// are needed, but note that any translation should be applied first to + /// avoid pixel rounding errors. + /// Note that this is *not* rounding to nearest integer if the values are negative. + /// They are always rounding as floor(n + 0.5). + #[must_use] + pub fn round(&self) -> Self { + Box3D::new(self.min.round(), self.max.round()) + } +} + +impl<T, U> Box3D<T, U> +where + T: Floor + Ceil, +{ + /// Return a box3d with faces/edges rounded to integer coordinates, such that + /// the original box3d contains the resulting box3d. + #[must_use] + pub fn round_in(&self) -> Self { + Box3D { + min: self.min.ceil(), + max: self.max.floor(), + } + } + + /// Return a box3d with faces/edges rounded to integer coordinates, such that + /// the original box3d is contained in the resulting box3d. + #[must_use] + pub fn round_out(&self) -> Self { + Box3D { + min: self.min.floor(), + max: self.max.ceil(), + } + } +} + +impl<T, U> From<Size3D<T, U>> for Box3D<T, U> +where + T: Copy + Zero + PartialOrd, +{ + fn from(b: Size3D<T, U>) -> Self { + Self::from_size(b) + } +} + +impl<T: Default, U> Default for Box3D<T, U> { + fn default() -> Self { + Box3D { + min: Default::default(), + max: Default::default(), + } + } +} + +/// Shorthand for `Box3D::new(Point3D::new(x1, y1, z1), Point3D::new(x2, y2, z2))`. +pub fn box3d<T: Copy, U>( + min_x: T, + min_y: T, + min_z: T, + max_x: T, + max_y: T, + max_z: T, +) -> Box3D<T, U> { + Box3D::new( + Point3D::new(min_x, min_y, min_z), + Point3D::new(max_x, max_y, max_z), + ) +} + +#[cfg(test)] +mod tests { + use crate::default::{Box3D, Point3D}; + use crate::{point3, size3, vec3}; + + #[test] + fn test_new() { + let b = Box3D::new(point3(-1.0, -1.0, -1.0), point3(1.0, 1.0, 1.0)); + assert!(b.min.x == -1.0); + assert!(b.min.y == -1.0); + assert!(b.min.z == -1.0); + assert!(b.max.x == 1.0); + assert!(b.max.y == 1.0); + assert!(b.max.z == 1.0); + } + + #[test] + fn test_size() { + let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0)); + assert!(b.size().width == 20.0); + assert!(b.size().height == 20.0); + assert!(b.size().depth == 20.0); + } + + #[test] + fn test_width_height_depth() { + let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0)); + assert!(b.width() == 20.0); + assert!(b.height() == 20.0); + assert!(b.depth() == 20.0); + } + + #[test] + fn test_center() { + let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0)); + assert!(b.center() == Point3D::zero()); + } + + #[test] + fn test_volume() { + let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0)); + assert!(b.volume() == 8000.0); + } + + #[test] + fn test_area() { + let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0)); + assert!(b.xy_area() == 400.0); + assert!(b.yz_area() == 400.0); + assert!(b.xz_area() == 400.0); + } + + #[test] + fn test_from_points() { + let b = Box3D::from_points(&[point3(50.0, 160.0, 12.5), point3(100.0, 25.0, 200.0)]); + assert!(b.min == point3(50.0, 25.0, 12.5)); + assert!(b.max == point3(100.0, 160.0, 200.0)); + } + + #[test] + fn test_min_max() { + let b = Box3D::from_points(&[point3(50.0, 25.0, 12.5), point3(100.0, 160.0, 200.0)]); + assert!(b.min.x == 50.0); + assert!(b.min.y == 25.0); + assert!(b.min.z == 12.5); + assert!(b.max.x == 100.0); + assert!(b.max.y == 160.0); + assert!(b.max.z == 200.0); + } + + #[test] + fn test_round_in() { + let b = + Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)]).round_in(); + assert!(b.min.x == -25.0); + assert!(b.min.y == -40.0); + assert!(b.min.z == -70.0); + assert!(b.max.x == 60.0); + assert!(b.max.y == 36.0); + assert!(b.max.z == 89.0); + } + + #[test] + fn test_round_out() { + let b = Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)]) + .round_out(); + assert!(b.min.x == -26.0); + assert!(b.min.y == -41.0); + assert!(b.min.z == -71.0); + assert!(b.max.x == 61.0); + assert!(b.max.y == 37.0); + assert!(b.max.z == 90.0); + } + + #[test] + fn test_round() { + let b = + Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)]).round(); + assert!(b.min.x == -25.0); + assert!(b.min.y == -40.0); + assert!(b.min.z == -71.0); + assert!(b.max.x == 60.0); + assert!(b.max.y == 37.0); + assert!(b.max.z == 90.0); + } + + #[test] + fn test_from_size() { + let b = Box3D::from_size(size3(30.0, 40.0, 50.0)); + assert!(b.min == Point3D::zero()); + assert!(b.size().width == 30.0); + assert!(b.size().height == 40.0); + assert!(b.size().depth == 50.0); + } + + #[test] + fn test_translate() { + let size = size3(15.0, 15.0, 200.0); + let mut center = (size / 2.0).to_vector().to_point(); + let b = Box3D::from_size(size); + assert!(b.center() == center); + let translation = vec3(10.0, 2.5, 9.5); + let b = b.translate(translation); + center += translation; + assert!(b.center() == center); + assert!(b.max.x == 25.0); + assert!(b.max.y == 17.5); + assert!(b.max.z == 209.5); + assert!(b.min.x == 10.0); + assert!(b.min.y == 2.5); + assert!(b.min.z == 9.5); + } + + #[test] + fn test_union() { + let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(0.0, 20.0, 20.0)]); + let b2 = Box3D::from_points(&[point3(0.0, 20.0, 20.0), point3(20.0, -20.0, -20.0)]); + let b = b1.union(&b2); + assert!(b.max.x == 20.0); + assert!(b.max.y == 20.0); + assert!(b.max.z == 20.0); + assert!(b.min.x == -20.0); + assert!(b.min.y == -20.0); + assert!(b.min.z == -20.0); + assert!(b.volume() == (40.0 * 40.0 * 40.0)); + } + + #[test] + fn test_intersects() { + let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]); + let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]); + assert!(b1.intersects(&b2)); + } + + #[test] + fn test_intersection_unchecked() { + let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]); + let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]); + let b = b1.intersection_unchecked(&b2); + assert!(b.max.x == 10.0); + assert!(b.max.y == 20.0); + assert!(b.max.z == 20.0); + assert!(b.min.x == -10.0); + assert!(b.min.y == -20.0); + assert!(b.min.z == -20.0); + assert!(b.volume() == (20.0 * 40.0 * 40.0)); + } + + #[test] + fn test_intersection() { + let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]); + let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]); + assert!(b1.intersection(&b2).is_some()); + + let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(-10.0, 20.0, 20.0)]); + let b2 = Box3D::from_points(&[point3(10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]); + assert!(b1.intersection(&b2).is_none()); + } + + #[test] + fn test_scale() { + let b = Box3D::from_points(&[point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0)]); + let b = b.scale(0.5, 0.5, 0.5); + assert!(b.max.x == 5.0); + assert!(b.max.y == 5.0); + assert!(b.max.z == 5.0); + assert!(b.min.x == -5.0); + assert!(b.min.y == -5.0); + assert!(b.min.z == -5.0); + } + + #[test] + fn test_zero() { + let b = Box3D::<f64>::zero(); + assert!(b.max.x == 0.0); + assert!(b.max.y == 0.0); + assert!(b.max.z == 0.0); + assert!(b.min.x == 0.0); + assert!(b.min.y == 0.0); + assert!(b.min.z == 0.0); + } + + #[test] + fn test_lerp() { + let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(-10.0, -10.0, -10.0)]); + let b2 = Box3D::from_points(&[point3(10.0, 10.0, 10.0), point3(20.0, 20.0, 20.0)]); + let b = b1.lerp(b2, 0.5); + assert!(b.center() == Point3D::zero()); + assert!(b.size().width == 10.0); + assert!(b.size().height == 10.0); + assert!(b.size().depth == 10.0); + } + + #[test] + fn test_contains() { + let b = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]); + assert!(b.contains(point3(-15.3, 10.5, 18.4))); + } + + #[test] + fn test_contains_box() { + let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]); + let b2 = Box3D::from_points(&[point3(-14.3, -16.5, -19.3), point3(6.7, 17.6, 2.5)]); + assert!(b1.contains_box(&b2)); + } + + #[test] + fn test_inflate() { + let b = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]); + let b = b.inflate(10.0, 5.0, 2.0); + assert!(b.size().width == 60.0); + assert!(b.size().height == 50.0); + assert!(b.size().depth == 44.0); + assert!(b.center() == Point3D::zero()); + } + + #[test] + fn test_is_empty() { + for i in 0..3 { + let mut coords_neg = [-20.0, -20.0, -20.0]; + let mut coords_pos = [20.0, 20.0, 20.0]; + coords_neg[i] = 0.0; + coords_pos[i] = 0.0; + let b = Box3D::from_points(&[Point3D::from(coords_neg), Point3D::from(coords_pos)]); + assert!(b.is_empty()); + } + } + + #[test] + fn test_nan_empty_or_negative() { + use std::f32::NAN; + assert!(Box3D { min: point3(NAN, 2.0, 1.0), max: point3(1.0, 3.0, 5.0) }.is_empty()); + assert!(Box3D { min: point3(0.0, NAN, 1.0), max: point3(1.0, 2.0, 5.0) }.is_empty()); + assert!(Box3D { min: point3(1.0, -2.0, NAN), max: point3(3.0, 2.0, 5.0) }.is_empty()); + assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(NAN, 2.0, 5.0) }.is_empty()); + assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(0.0, NAN, 5.0) }.is_empty()); + assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(0.0, 1.0, NAN) }.is_empty()); + } +} diff --git a/third_party/rust/euclid/src/homogen.rs b/third_party/rust/euclid/src/homogen.rs new file mode 100644 index 0000000000..afd4f22957 --- /dev/null +++ b/third_party/rust/euclid/src/homogen.rs @@ -0,0 +1,223 @@ +// 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::point::{Point2D, Point3D}; +use crate::vector::{Vector2D, Vector3D}; + +use crate::num::{One, Zero}; + +use core::cmp::{Eq, PartialEq}; +use core::fmt; +use core::hash::Hash; +use core::marker::PhantomData; +use core::ops::Div; +#[cfg(feature = "serde")] +use serde; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// Homogeneous vector in 3D space. +#[repr(C)] +pub struct HomogeneousVector<T, U> { + pub x: T, + pub y: T, + pub z: T, + pub w: T, + #[doc(hidden)] + pub _unit: PhantomData<U>, +} + +impl<T: Copy, U> Copy for HomogeneousVector<T, U> {} + +impl<T: Clone, U> Clone for HomogeneousVector<T, U> { + fn clone(&self) -> Self { + HomogeneousVector { + x: self.x.clone(), + y: self.y.clone(), + z: self.z.clone(), + w: self.w.clone(), + _unit: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T, U> serde::Deserialize<'de> for HomogeneousVector<T, U> +where + T: serde::Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let (x, y, z, w) = serde::Deserialize::deserialize(deserializer)?; + Ok(HomogeneousVector { + x, + y, + z, + w, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl<T, U> serde::Serialize for HomogeneousVector<T, U> +where + T: serde::Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + (&self.x, &self.y, &self.z, &self.w).serialize(serializer) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for HomogeneousVector<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for HomogeneousVector<T, U> {} + +impl<T, U> Eq for HomogeneousVector<T, U> where T: Eq {} + +impl<T, U> PartialEq for HomogeneousVector<T, U> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z && self.w == other.w + } +} + +impl<T, U> Hash for HomogeneousVector<T, U> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + self.z.hash(h); + self.w.hash(h); + } +} + +impl<T, U> HomogeneousVector<T, U> { + /// Constructor taking scalar values directly. + #[inline] + pub const fn new(x: T, y: T, z: T, w: T) -> Self { + HomogeneousVector { + x, + y, + z, + w, + _unit: PhantomData, + } + } +} + +impl<T: Copy + Div<T, Output = T> + Zero + PartialOrd, U> HomogeneousVector<T, U> { + /// Convert into Cartesian 2D point. + /// + /// Returns None if the point is on or behind the W=0 hemisphere. + #[inline] + pub fn to_point2d(self) -> Option<Point2D<T, U>> { + if self.w > T::zero() { + Some(Point2D::new(self.x / self.w, self.y / self.w)) + } else { + None + } + } + + /// Convert into Cartesian 3D point. + /// + /// Returns None if the point is on or behind the W=0 hemisphere. + #[inline] + pub fn to_point3d(self) -> Option<Point3D<T, U>> { + if self.w > T::zero() { + Some(Point3D::new( + self.x / self.w, + self.y / self.w, + self.z / self.w, + )) + } else { + None + } + } +} + +impl<T: Zero, U> From<Vector2D<T, U>> for HomogeneousVector<T, U> { + #[inline] + fn from(v: Vector2D<T, U>) -> Self { + HomogeneousVector::new(v.x, v.y, T::zero(), T::zero()) + } +} + +impl<T: Zero, U> From<Vector3D<T, U>> for HomogeneousVector<T, U> { + #[inline] + fn from(v: Vector3D<T, U>) -> Self { + HomogeneousVector::new(v.x, v.y, v.z, T::zero()) + } +} + +impl<T: Zero + One, U> From<Point2D<T, U>> for HomogeneousVector<T, U> { + #[inline] + fn from(p: Point2D<T, U>) -> Self { + HomogeneousVector::new(p.x, p.y, T::zero(), T::one()) + } +} + +impl<T: One, U> From<Point3D<T, U>> for HomogeneousVector<T, U> { + #[inline] + fn from(p: Point3D<T, U>) -> Self { + HomogeneousVector::new(p.x, p.y, p.z, T::one()) + } +} + +impl<T: fmt::Debug, U> fmt::Debug for HomogeneousVector<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("") + .field(&self.x) + .field(&self.y) + .field(&self.z) + .field(&self.w) + .finish() + } +} + +#[cfg(test)] +mod homogeneous { + use super::HomogeneousVector; + use crate::default::{Point2D, Point3D}; + + #[test] + fn roundtrip() { + assert_eq!( + Some(Point2D::new(1.0, 2.0)), + HomogeneousVector::from(Point2D::new(1.0, 2.0)).to_point2d() + ); + assert_eq!( + Some(Point3D::new(1.0, -2.0, 0.1)), + HomogeneousVector::from(Point3D::new(1.0, -2.0, 0.1)).to_point3d() + ); + } + + #[test] + fn negative() { + assert_eq!( + None, + HomogeneousVector::<f32, ()>::new(1.0, 2.0, 3.0, 0.0).to_point2d() + ); + assert_eq!( + None, + HomogeneousVector::<f32, ()>::new(1.0, -2.0, -3.0, -2.0).to_point3d() + ); + } +} diff --git a/third_party/rust/euclid/src/length.rs b/third_party/rust/euclid/src/length.rs new file mode 100644 index 0000000000..e65d38b61d --- /dev/null +++ b/third_party/rust/euclid/src/length.rs @@ -0,0 +1,609 @@ +// Copyright 2014 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +//! A one-dimensional length, tagged with its units. + +use crate::approxeq::ApproxEq; +use crate::num::Zero; +use crate::scale::Scale; +use crate::approxord::{max, min}; + +use crate::num::One; +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::iter::Sum; +use core::marker::PhantomData; +use core::ops::{Add, Div, Mul, Neg, Sub}; +use core::ops::{AddAssign, DivAssign, MulAssign, SubAssign}; +use num_traits::{NumCast, Saturating}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A one-dimensional distance, with value represented by `T` and unit of measurement `Unit`. +/// +/// `T` can be any numeric type, for example a primitive type like `u64` or `f32`. +/// +/// `Unit` is not used in the representation of a `Length` value. It is used only at compile time +/// to ensure that a `Length` stored with one unit is converted explicitly before being used in an +/// expression that requires a different unit. It may be a type without values, such as an empty +/// enum. +/// +/// You can multiply a `Length` by a `scale::Scale` to convert it from one unit to +/// another. See the [`Scale`] docs for an example. +/// +/// [`Scale`]: struct.Scale.html +#[repr(C)] +pub struct Length<T, Unit>(pub T, #[doc(hidden)] pub PhantomData<Unit>); + +impl<T: Clone, U> Clone for Length<T, U> { + fn clone(&self) -> Self { + Length(self.0.clone(), PhantomData) + } +} + +impl<T: Copy, U> Copy for Length<T, U> {} + +#[cfg(feature = "serde")] +impl<'de, T, U> Deserialize<'de> for Length<T, U> +where + T: Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + Ok(Length(Deserialize::deserialize(deserializer)?, PhantomData)) + } +} + +#[cfg(feature = "serde")] +impl<T, U> Serialize for Length<T, U> +where + T: Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Length<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Length<T, U> {} + +impl<T, U> Length<T, U> { + /// Associate a value with a unit of measure. + #[inline] + pub const fn new(x: T) -> Self { + Length(x, PhantomData) + } +} + +impl<T: Clone, U> Length<T, U> { + /// Unpack the underlying value from the wrapper. + pub fn get(self) -> T { + self.0 + } + + /// Cast the unit + #[inline] + pub fn cast_unit<V>(self) -> Length<T, V> { + Length::new(self.0) + } + + /// Linearly interpolate between this length and another length. + /// + /// # Example + /// + /// ```rust + /// use euclid::default::Length; + /// + /// let from = Length::new(0.0); + /// let to = Length::new(8.0); + /// + /// assert_eq!(from.lerp(to, -1.0), Length::new(-8.0)); + /// assert_eq!(from.lerp(to, 0.0), Length::new( 0.0)); + /// assert_eq!(from.lerp(to, 0.5), Length::new( 4.0)); + /// assert_eq!(from.lerp(to, 1.0), Length::new( 8.0)); + /// assert_eq!(from.lerp(to, 2.0), Length::new(16.0)); + /// ``` + #[inline] + pub fn lerp(self, other: Self, t: T) -> Self + where + T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>, + { + let one_t = T::one() - t.clone(); + Length::new(one_t * self.0.clone() + t * other.0) + } +} + +impl<T: PartialOrd, U> Length<T, U> { + /// Returns minimum between this length and another length. + #[inline] + pub fn min(self, other: Self) -> Self { + min(self, other) + } + + /// Returns maximum between this length and another length. + #[inline] + pub fn max(self, other: Self) -> Self { + max(self, other) + } +} + +impl<T: NumCast + Clone, U> Length<T, U> { + /// Cast from one numeric representation to another, preserving the units. + #[inline] + pub fn cast<NewT: NumCast>(self) -> Length<NewT, U> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + pub fn try_cast<NewT: NumCast>(self) -> Option<Length<NewT, U>> { + NumCast::from(self.0).map(Length::new) + } +} + +impl<T: fmt::Debug, U> fmt::Debug for Length<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<T: Default, U> Default for Length<T, U> { + #[inline] + fn default() -> Self { + Length::new(Default::default()) + } +} + +impl<T: Hash, U> Hash for Length<T, U> { + fn hash<H: Hasher>(&self, h: &mut H) { + self.0.hash(h); + } +} + +// length + length +impl<T: Add, U> Add for Length<T, U> { + type Output = Length<T::Output, U>; + + fn add(self, other: Self) -> Self::Output { + Length::new(self.0 + other.0) + } +} + +// length + &length +impl<T: Add + Copy, U> Add<&Self> for Length<T, U> { + type Output = Length<T::Output, U>; + + fn add(self, other: &Self) -> Self::Output { + Length::new(self.0 + other.0) + } +} + +// length_iter.copied().sum() +impl<T: Add<Output = T> + Zero, U> Sum for Length<T, U> { + fn sum<I: Iterator<Item=Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +// length_iter.sum() +impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Length<T, U> { + fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +// length += length +impl<T: AddAssign, U> AddAssign for Length<T, U> { + fn add_assign(&mut self, other: Self) { + self.0 += other.0; + } +} + +// length - length +impl<T: Sub, U> Sub for Length<T, U> { + type Output = Length<T::Output, U>; + + fn sub(self, other: Length<T, U>) -> Self::Output { + Length::new(self.0 - other.0) + } +} + +// length -= length +impl<T: SubAssign, U> SubAssign for Length<T, U> { + fn sub_assign(&mut self, other: Self) { + self.0 -= other.0; + } +} + +// Saturating length + length and length - length. +impl<T: Saturating, U> Saturating for Length<T, U> { + fn saturating_add(self, other: Self) -> Self { + Length::new(self.0.saturating_add(other.0)) + } + + fn saturating_sub(self, other: Self) -> Self { + Length::new(self.0.saturating_sub(other.0)) + } +} + +// length / length +impl<Src, Dst, T: Div> Div<Length<T, Src>> for Length<T, Dst> { + type Output = Scale<T::Output, Src, Dst>; + + #[inline] + fn div(self, other: Length<T, Src>) -> Self::Output { + Scale::new(self.0 / other.0) + } +} + +// length * scalar +impl<T: Mul, U> Mul<T> for Length<T, U> { + type Output = Length<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Length::new(self.0 * scale) + } +} + +// length *= scalar +impl<T: Copy + Mul<T, Output = T>, U> MulAssign<T> for Length<T, U> { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +// length / scalar +impl<T: Div, U> Div<T> for Length<T, U> { + type Output = Length<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Length::new(self.0 / scale) + } +} + +// length /= scalar +impl<T: Copy + Div<T, Output = T>, U> DivAssign<T> for Length<T, U> { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +// length * scaleFactor +impl<Src, Dst, T: Mul> Mul<Scale<T, Src, Dst>> for Length<T, Src> { + type Output = Length<T::Output, Dst>; + + #[inline] + fn mul(self, scale: Scale<T, Src, Dst>) -> Self::Output { + Length::new(self.0 * scale.0) + } +} + +// length / scaleFactor +impl<Src, Dst, T: Div> Div<Scale<T, Src, Dst>> for Length<T, Dst> { + type Output = Length<T::Output, Src>; + + #[inline] + fn div(self, scale: Scale<T, Src, Dst>) -> Self::Output { + Length::new(self.0 / scale.0) + } +} + +// -length +impl<U, T: Neg> Neg for Length<T, U> { + type Output = Length<T::Output, U>; + + #[inline] + fn neg(self) -> Self::Output { + Length::new(-self.0) + } +} + +impl<T: PartialEq, U> PartialEq for Length<T, U> { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl<T: PartialOrd, U> PartialOrd for Length<T, U> { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + self.0.partial_cmp(&other.0) + } +} + +impl<T: Eq, U> Eq for Length<T, U> {} + +impl<T: Ord, U> Ord for Length<T, U> { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl<T: Zero, U> Zero for Length<T, U> { + #[inline] + fn zero() -> Self { + Length::new(Zero::zero()) + } +} + +impl<U, T: ApproxEq<T>> ApproxEq<T> for Length<T, U> { + #[inline] + fn approx_epsilon() -> T { + T::approx_epsilon() + } + + #[inline] + fn approx_eq_eps(&self, other: &Length<T, U>, approx_epsilon: &T) -> bool { + self.0.approx_eq_eps(&other.0, approx_epsilon) + } +} + +#[cfg(test)] +mod tests { + use super::Length; + use crate::num::Zero; + + use crate::scale::Scale; + use core::f32::INFINITY; + use num_traits::Saturating; + + enum Inch {} + enum Mm {} + enum Cm {} + enum Second {} + + #[cfg(feature = "serde")] + mod serde { + use super::*; + + extern crate serde_test; + use self::serde_test::assert_tokens; + use self::serde_test::Token; + + #[test] + fn test_length_serde() { + let one_cm: Length<f32, Mm> = Length::new(10.0); + + assert_tokens(&one_cm, &[Token::F32(10.0)]); + } + } + + #[test] + fn test_clone() { + // A cloned Length is a separate length with the state matching the + // original Length at the point it was cloned. + let mut variable_length: Length<f32, Inch> = Length::new(12.0); + + let one_foot = variable_length.clone(); + variable_length.0 = 24.0; + + assert_eq!(one_foot.get(), 12.0); + assert_eq!(variable_length.get(), 24.0); + } + + #[test] + fn test_add() { + let length1: Length<u8, Mm> = Length::new(250); + let length2: Length<u8, Mm> = Length::new(5); + + assert_eq!((length1 + length2).get(), 255); + assert_eq!((length1 + &length2).get(), 255); + } + + #[test] + fn test_sum() { + type L = Length<f32, Mm>; + let lengths = [L::new(1.0), L::new(2.0), L::new(3.0)]; + + assert_eq!(lengths.iter().sum::<L>(), L::new(6.0)); + } + + #[test] + fn test_addassign() { + let one_cm: Length<f32, Mm> = Length::new(10.0); + let mut measurement: Length<f32, Mm> = Length::new(5.0); + + measurement += one_cm; + + assert_eq!(measurement.get(), 15.0); + } + + #[test] + fn test_sub() { + let length1: Length<u8, Mm> = Length::new(250); + let length2: Length<u8, Mm> = Length::new(5); + + let result = length1 - length2; + + assert_eq!(result.get(), 245); + } + + #[test] + fn test_subassign() { + let one_cm: Length<f32, Mm> = Length::new(10.0); + let mut measurement: Length<f32, Mm> = Length::new(5.0); + + measurement -= one_cm; + + assert_eq!(measurement.get(), -5.0); + } + + #[test] + fn test_saturating_add() { + let length1: Length<u8, Mm> = Length::new(250); + let length2: Length<u8, Mm> = Length::new(6); + + let result = length1.saturating_add(length2); + + assert_eq!(result.get(), 255); + } + + #[test] + fn test_saturating_sub() { + let length1: Length<u8, Mm> = Length::new(5); + let length2: Length<u8, Mm> = Length::new(10); + + let result = length1.saturating_sub(length2); + + assert_eq!(result.get(), 0); + } + + #[test] + fn test_division_by_length() { + // Division results in a Scale from denominator units + // to numerator units. + let length: Length<f32, Cm> = Length::new(5.0); + let duration: Length<f32, Second> = Length::new(10.0); + + let result = length / duration; + + let expected: Scale<f32, Second, Cm> = Scale::new(0.5); + assert_eq!(result, expected); + } + + #[test] + fn test_multiplication() { + let length_mm: Length<f32, Mm> = Length::new(10.0); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = length_mm * cm_per_mm; + + let expected: Length<f32, Cm> = Length::new(1.0); + assert_eq!(result, expected); + } + + #[test] + fn test_multiplication_with_scalar() { + let length_mm: Length<f32, Mm> = Length::new(10.0); + + let result = length_mm * 2.0; + + let expected: Length<f32, Mm> = Length::new(20.0); + assert_eq!(result, expected); + } + + #[test] + fn test_multiplication_assignment() { + let mut length: Length<f32, Mm> = Length::new(10.0); + + length *= 2.0; + + let expected: Length<f32, Mm> = Length::new(20.0); + assert_eq!(length, expected); + } + + #[test] + fn test_division_by_scalefactor() { + let length: Length<f32, Cm> = Length::new(5.0); + let cm_per_second: Scale<f32, Second, Cm> = Scale::new(10.0); + + let result = length / cm_per_second; + + let expected: Length<f32, Second> = Length::new(0.5); + assert_eq!(result, expected); + } + + #[test] + fn test_division_by_scalar() { + let length: Length<f32, Cm> = Length::new(5.0); + + let result = length / 2.0; + + let expected: Length<f32, Cm> = Length::new(2.5); + assert_eq!(result, expected); + } + + #[test] + fn test_division_assignment() { + let mut length: Length<f32, Mm> = Length::new(10.0); + + length /= 2.0; + + let expected: Length<f32, Mm> = Length::new(5.0); + assert_eq!(length, expected); + } + + #[test] + fn test_negation() { + let length: Length<f32, Cm> = Length::new(5.0); + + let result = -length; + + let expected: Length<f32, Cm> = Length::new(-5.0); + assert_eq!(result, expected); + } + + #[test] + fn test_cast() { + let length_as_i32: Length<i32, Cm> = Length::new(5); + + let result: Length<f32, Cm> = length_as_i32.cast(); + + let length_as_f32: Length<f32, Cm> = Length::new(5.0); + assert_eq!(result, length_as_f32); + } + + #[test] + fn test_equality() { + let length_5_point_0: Length<f32, Cm> = Length::new(5.0); + let length_5_point_1: Length<f32, Cm> = Length::new(5.1); + let length_0_point_1: Length<f32, Cm> = Length::new(0.1); + + assert!(length_5_point_0 == length_5_point_1 - length_0_point_1); + assert!(length_5_point_0 != length_5_point_1); + } + + #[test] + fn test_order() { + let length_5_point_0: Length<f32, Cm> = Length::new(5.0); + let length_5_point_1: Length<f32, Cm> = Length::new(5.1); + let length_0_point_1: Length<f32, Cm> = Length::new(0.1); + + assert!(length_5_point_0 < length_5_point_1); + assert!(length_5_point_0 <= length_5_point_1); + assert!(length_5_point_0 <= length_5_point_1 - length_0_point_1); + assert!(length_5_point_1 > length_5_point_0); + assert!(length_5_point_1 >= length_5_point_0); + assert!(length_5_point_0 >= length_5_point_1 - length_0_point_1); + } + + #[test] + fn test_zero_add() { + type LengthCm = Length<f32, Cm>; + let length: LengthCm = Length::new(5.0); + + let result = length - LengthCm::zero(); + + assert_eq!(result, length); + } + + #[test] + fn test_zero_division() { + type LengthCm = Length<f32, Cm>; + let length: LengthCm = Length::new(5.0); + let length_zero: LengthCm = Length::zero(); + + let result = length / length_zero; + + let expected: Scale<f32, Cm, Cm> = Scale::new(INFINITY); + assert_eq!(result, expected); + } +} diff --git a/third_party/rust/euclid/src/lib.rs b/third_party/rust/euclid/src/lib.rs new file mode 100644 index 0000000000..ec7f52ff26 --- /dev/null +++ b/third_party/rust/euclid/src/lib.rs @@ -0,0 +1,115 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![cfg_attr(not(test), no_std)] + +//! A collection of strongly typed math tools for computer graphics with an inclination +//! towards 2d graphics and layout. +//! +//! All types are generic over the scalar type of their component (`f32`, `i32`, etc.), +//! and tagged with a generic Unit parameter which is useful to prevent mixing +//! values from different spaces. For example it should not be legal to translate +//! a screen-space position by a world-space vector and this can be expressed using +//! the generic Unit parameter. +//! +//! This unit system is not mandatory and all structures have an alias +//! with the default unit: `UnknownUnit`. +//! for example ```default::Point2D<T>``` is equivalent to ```Point2D<T, UnknownUnit>```. +//! Client code typically creates a set of aliases for each type and doesn't need +//! to deal with the specifics of typed units further. For example: +//! +//! ```rust +//! use euclid::*; +//! pub struct ScreenSpace; +//! pub type ScreenPoint = Point2D<f32, ScreenSpace>; +//! pub type ScreenSize = Size2D<f32, ScreenSpace>; +//! pub struct WorldSpace; +//! pub type WorldPoint = Point3D<f32, WorldSpace>; +//! pub type ProjectionMatrix = Transform3D<f32, WorldSpace, ScreenSpace>; +//! // etc... +//! ``` +//! +//! All euclid types are marked `#[repr(C)]` in order to facilitate exposing them to +//! foreign function interfaces (provided the underlying scalar type is also `repr(C)`). +//! +#![deny(unconditional_recursion)] + +pub use crate::angle::Angle; +pub use crate::box2d::Box2D; +pub use crate::homogen::HomogeneousVector; +pub use crate::length::Length; +pub use crate::point::{point2, point3, Point2D, Point3D}; +pub use crate::scale::Scale; +pub use crate::transform2d::Transform2D; +pub use crate::transform3d::Transform3D; +pub use crate::vector::{bvec2, bvec3, BoolVector2D, BoolVector3D}; +pub use crate::vector::{vec2, vec3, Vector2D, Vector3D}; + +pub use crate::box3d::{box3d, Box3D}; +pub use crate::rect::{rect, Rect}; +pub use crate::rigid::RigidTransform3D; +pub use crate::rotation::{Rotation2D, Rotation3D}; +pub use crate::side_offsets::SideOffsets2D; +pub use crate::size::{size2, size3, Size2D, Size3D}; +pub use crate::translation::{Translation2D, Translation3D}; +pub use crate::trig::Trig; + +#[macro_use] +mod macros; + +mod angle; +pub mod approxeq; +pub mod approxord; +mod box2d; +mod box3d; +mod homogen; +mod length; +pub mod num; +mod point; +mod rect; +mod rigid; +mod rotation; +mod scale; +mod side_offsets; +mod size; +mod transform2d; +mod transform3d; +mod translation; +mod trig; +mod vector; + +/// The default unit. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct UnknownUnit; + +pub mod default { + //! A set of aliases for all types, tagged with the default unknown unit. + + use super::UnknownUnit; + pub type Length<T> = super::Length<T, UnknownUnit>; + pub type Point2D<T> = super::Point2D<T, UnknownUnit>; + pub type Point3D<T> = super::Point3D<T, UnknownUnit>; + pub type Vector2D<T> = super::Vector2D<T, UnknownUnit>; + pub type Vector3D<T> = super::Vector3D<T, UnknownUnit>; + pub type HomogeneousVector<T> = super::HomogeneousVector<T, UnknownUnit>; + pub type Size2D<T> = super::Size2D<T, UnknownUnit>; + pub type Size3D<T> = super::Size3D<T, UnknownUnit>; + pub type Rect<T> = super::Rect<T, UnknownUnit>; + pub type Box2D<T> = super::Box2D<T, UnknownUnit>; + pub type Box3D<T> = super::Box3D<T, UnknownUnit>; + pub type SideOffsets2D<T> = super::SideOffsets2D<T, UnknownUnit>; + pub type Transform2D<T> = super::Transform2D<T, UnknownUnit, UnknownUnit>; + pub type Transform3D<T> = super::Transform3D<T, UnknownUnit, UnknownUnit>; + pub type Rotation2D<T> = super::Rotation2D<T, UnknownUnit, UnknownUnit>; + pub type Rotation3D<T> = super::Rotation3D<T, UnknownUnit, UnknownUnit>; + pub type Translation2D<T> = super::Translation2D<T, UnknownUnit, UnknownUnit>; + pub type Translation3D<T> = super::Translation3D<T, UnknownUnit, UnknownUnit>; + pub type Scale<T> = super::Scale<T, UnknownUnit, UnknownUnit>; + pub type RigidTransform3D<T> = super::RigidTransform3D<T, UnknownUnit, UnknownUnit>; +} diff --git a/third_party/rust/euclid/src/macros.rs b/third_party/rust/euclid/src/macros.rs new file mode 100644 index 0000000000..9cc392eb30 --- /dev/null +++ b/third_party/rust/euclid/src/macros.rs @@ -0,0 +1,30 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +macro_rules! mint_vec { + ($name:ident [ $($field:ident),* ] = $std_name:ident) => { + #[cfg(feature = "mint")] + impl<T, U> From<mint::$std_name<T>> for $name<T, U> { + fn from(v: mint::$std_name<T>) -> Self { + $name { + $( $field: v.$field, )* + _unit: PhantomData, + } + } + } + #[cfg(feature = "mint")] + impl<T, U> Into<mint::$std_name<T>> for $name<T, U> { + fn into(self) -> mint::$std_name<T> { + mint::$std_name { + $( $field: self.$field, )* + } + } + } + } +} diff --git a/third_party/rust/euclid/src/num.rs b/third_party/rust/euclid/src/num.rs new file mode 100644 index 0000000000..e5135d074e --- /dev/null +++ b/third_party/rust/euclid/src/num.rs @@ -0,0 +1,128 @@ +// Copyright 2014 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +//! A one-dimensional length, tagged with its units. + +use num_traits; + +// Euclid has its own Zero and One traits instead of of using the num_traits equivalents. +// Unfortunately, num_traits::Zero requires Add, which opens a bag of sad things: +// - Most importantly, for Point2D to implement Zero it would need to implement Add<Self> which we +// don't want (we allow "Point + Vector" and "Vector + Vector" semantics and purposefully disallow +// "Point + Point". +// - Some operations that require, say, One and Div (for example Scale::inv) currently return a +// type parameterized over T::Output which is ambiguous with num_traits::One because it inherits +// Mul which also has an Output associated type. To fix it need to complicate type signatures +// by using <T as Trait>::Output which makes the code and documentation harder to read. +// +// On the other hand, euclid::num::Zero/One are automatically implemented for all types that +// implement their num_traits counterpart. Euclid users never need to explicitly use +// euclid::num::Zero/One and can/should only manipulate the num_traits equivalents without risk +// of compatibility issues with euclid. + +pub trait Zero { + fn zero() -> Self; +} + +impl<T: num_traits::Zero> Zero for T { + fn zero() -> T { + num_traits::Zero::zero() + } +} + +pub trait One { + fn one() -> Self; +} + +impl<T: num_traits::One> One for T { + fn one() -> T { + num_traits::One::one() + } +} + +/// Defines the nearest integer value to the original value. +pub trait Round: Copy { + /// Rounds to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + #[must_use] + fn round(self) -> Self; +} +/// Defines the biggest integer equal or lower than the original value. +pub trait Floor: Copy { + /// Rounds to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + #[must_use] + fn floor(self) -> Self; +} +/// Defines the smallest integer equal or greater than the original value. +pub trait Ceil: Copy { + /// Rounds to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + #[must_use] + fn ceil(self) -> Self; +} + +macro_rules! num_int { + ($ty:ty) => { + impl Round for $ty { + #[inline] + fn round(self) -> $ty { + self + } + } + impl Floor for $ty { + #[inline] + fn floor(self) -> $ty { + self + } + } + impl Ceil for $ty { + #[inline] + fn ceil(self) -> $ty { + self + } + } + }; +} + +macro_rules! num_float { + ($ty:ty) => { + impl Round for $ty { + #[inline] + fn round(self) -> $ty { + (self + 0.5).floor() + } + } + impl Floor for $ty { + #[inline] + fn floor(self) -> $ty { + num_traits::Float::floor(self) + } + } + impl Ceil for $ty { + #[inline] + fn ceil(self) -> $ty { + num_traits::Float::ceil(self) + } + } + }; +} + +num_int!(i16); +num_int!(u16); +num_int!(i32); +num_int!(u32); +num_int!(i64); +num_int!(u64); +num_int!(isize); +num_int!(usize); +num_float!(f32); +num_float!(f64); diff --git a/third_party/rust/euclid/src/point.rs b/third_party/rust/euclid/src/point.rs new file mode 100644 index 0000000000..f364f8626a --- /dev/null +++ b/third_party/rust/euclid/src/point.rs @@ -0,0 +1,2041 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::approxeq::ApproxEq; +use crate::approxord::{max, min}; +use crate::length::Length; +use crate::num::*; +use crate::scale::Scale; +use crate::size::{Size2D, Size3D}; +use crate::vector::{vec2, vec3, Vector2D, Vector3D}; +use core::cmp::{Eq, PartialEq}; +use core::fmt; +use core::hash::Hash; +use core::marker::PhantomData; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +#[cfg(feature = "mint")] +use mint; +use num_traits::real::Real; +use num_traits::{Float, NumCast}; +#[cfg(feature = "serde")] +use serde; + +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A 2d Point tagged with a unit. +#[repr(C)] +pub struct Point2D<T, U> { + pub x: T, + pub y: T, + #[doc(hidden)] + pub _unit: PhantomData<U>, +} + +impl<T: Copy, U> Copy for Point2D<T, U> {} + +impl<T: Clone, U> Clone for Point2D<T, U> { + fn clone(&self) -> Self { + Point2D { + x: self.x.clone(), + y: self.y.clone(), + _unit: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T, U> serde::Deserialize<'de> for Point2D<T, U> +where + T: serde::Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let (x, y) = serde::Deserialize::deserialize(deserializer)?; + Ok(Point2D { + x, + y, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl<T, U> serde::Serialize for Point2D<T, U> +where + T: serde::Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + (&self.x, &self.y).serialize(serializer) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a, T, U> arbitrary::Arbitrary<'a> for Point2D<T, U> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> + { + let (x, y) = arbitrary::Arbitrary::arbitrary(u)?; + Ok(Point2D { + x, + y, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Point2D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Point2D<T, U> {} + +impl<T, U> Eq for Point2D<T, U> where T: Eq {} + +impl<T, U> PartialEq for Point2D<T, U> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +impl<T, U> Hash for Point2D<T, U> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + } +} + +mint_vec!(Point2D[x, y] = Point2); + +impl<T: fmt::Debug, U> fmt::Debug for Point2D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("").field(&self.x).field(&self.y).finish() + } +} + +impl<T: Default, U> Default for Point2D<T, U> { + fn default() -> Self { + Point2D::new(Default::default(), Default::default()) + } +} + +impl<T, U> Point2D<T, U> { + /// Constructor, setting all components to zero. + #[inline] + pub fn origin() -> Self + where + T: Zero, + { + point2(Zero::zero(), Zero::zero()) + } + + /// The same as [`origin()`](#method.origin). + #[inline] + pub fn zero() -> Self + where + T: Zero, + { + Self::origin() + } + + /// Constructor taking scalar values directly. + #[inline] + pub const fn new(x: T, y: T) -> Self { + Point2D { + x, + y, + _unit: PhantomData, + } + } + + /// Constructor taking properly Lengths instead of scalar values. + #[inline] + pub fn from_lengths(x: Length<T, U>, y: Length<T, U>) -> Self { + point2(x.0, y.0) + } + + /// Constructor setting all components to the same value. + #[inline] + pub fn splat(v: T) -> Self + where + T: Clone, + { + Point2D { + x: v.clone(), + y: v, + _unit: PhantomData, + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Point2D<T, UnknownUnit>) -> Self { + point2(p.x, p.y) + } +} + +impl<T: Copy, U> Point2D<T, U> { + /// Create a 3d point from this one, using the specified z value. + #[inline] + pub fn extend(self, z: T) -> Point3D<T, U> { + point3(self.x, self.y, z) + } + + /// Cast this point into a vector. + /// + /// Equivalent to subtracting the origin from this point. + #[inline] + pub fn to_vector(self) -> Vector2D<T, U> { + Vector2D { + x: self.x, + y: self.y, + _unit: PhantomData, + } + } + + /// Swap x and y. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point2D, point2}; + /// enum Mm {} + /// + /// let point: Point2D<_, Mm> = point2(1, -8); + /// + /// assert_eq!(point.yx(), point2(-8, 1)); + /// ``` + #[inline] + pub fn yx(self) -> Self { + point2(self.y, self.x) + } + + /// Drop the units, preserving only the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point2D, point2}; + /// enum Mm {} + /// + /// let point: Point2D<_, Mm> = point2(1, -8); + /// + /// assert_eq!(point.x, point.to_untyped().x); + /// assert_eq!(point.y, point.to_untyped().y); + /// ``` + #[inline] + pub fn to_untyped(self) -> Point2D<T, UnknownUnit> { + point2(self.x, self.y) + } + + /// Cast the unit, preserving the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point2D, point2}; + /// enum Mm {} + /// enum Cm {} + /// + /// let point: Point2D<_, Mm> = point2(1, -8); + /// + /// assert_eq!(point.x, point.cast_unit::<Cm>().x); + /// assert_eq!(point.y, point.cast_unit::<Cm>().y); + /// ``` + #[inline] + pub fn cast_unit<V>(self) -> Point2D<T, V> { + point2(self.x, self.y) + } + + /// Cast into an array with x and y. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point2D, point2}; + /// enum Mm {} + /// + /// let point: Point2D<_, Mm> = point2(1, -8); + /// + /// assert_eq!(point.to_array(), [1, -8]); + /// ``` + #[inline] + pub fn to_array(self) -> [T; 2] { + [self.x, self.y] + } + + /// Cast into a tuple with x and y. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point2D, point2}; + /// enum Mm {} + /// + /// let point: Point2D<_, Mm> = point2(1, -8); + /// + /// assert_eq!(point.to_tuple(), (1, -8)); + /// ``` + #[inline] + pub fn to_tuple(self) -> (T, T) { + (self.x, self.y) + } + + /// Convert into a 3d point with z-coordinate equals to zero. + #[inline] + pub fn to_3d(self) -> Point3D<T, U> + where + T: Zero, + { + point3(self.x, self.y, Zero::zero()) + } + + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::point2; + /// enum Mm {} + /// + /// assert_eq!(point2::<_, Mm>(-0.1, -0.8).round(), point2::<_, Mm>(0.0, -1.0)) + /// ``` + #[inline] + #[must_use] + pub fn round(self) -> Self + where + T: Round, + { + point2(self.x.round(), self.y.round()) + } + + /// Rounds each component to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::point2; + /// enum Mm {} + /// + /// assert_eq!(point2::<_, Mm>(-0.1, -0.8).ceil(), point2::<_, Mm>(0.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn ceil(self) -> Self + where + T: Ceil, + { + point2(self.x.ceil(), self.y.ceil()) + } + + /// Rounds each component to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::point2; + /// enum Mm {} + /// + /// assert_eq!(point2::<_, Mm>(-0.1, -0.8).floor(), point2::<_, Mm>(-1.0, -1.0)) + /// ``` + #[inline] + #[must_use] + pub fn floor(self) -> Self + where + T: Floor, + { + point2(self.x.floor(), self.y.floor()) + } + + /// Linearly interpolate between this point and another point. + /// + /// # Example + /// + /// ```rust + /// use euclid::point2; + /// use euclid::default::Point2D; + /// + /// let from: Point2D<_> = point2(0.0, 10.0); + /// let to: Point2D<_> = point2(8.0, -4.0); + /// + /// assert_eq!(from.lerp(to, -1.0), point2(-8.0, 24.0)); + /// assert_eq!(from.lerp(to, 0.0), point2( 0.0, 10.0)); + /// assert_eq!(from.lerp(to, 0.5), point2( 4.0, 3.0)); + /// assert_eq!(from.lerp(to, 1.0), point2( 8.0, -4.0)); + /// assert_eq!(from.lerp(to, 2.0), point2(16.0, -18.0)); + /// ``` + #[inline] + pub fn lerp(self, other: Self, t: T) -> Self + where + T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>, + { + let one_t = T::one() - t; + point2(one_t * self.x + t * other.x, one_t * self.y + t * other.y) + } +} + +impl<T: PartialOrd, U> Point2D<T, U> { + #[inline] + pub fn min(self, other: Self) -> Self { + point2(min(self.x, other.x), min(self.y, other.y)) + } + + #[inline] + pub fn max(self, other: Self) -> Self { + point2(max(self.x, other.x), max(self.y, other.y)) + } + + /// Returns the point each component of which clamped by corresponding + /// components of `start` and `end`. + /// + /// Shortcut for `self.max(start).min(end)`. + #[inline] + pub fn clamp(self, start: Self, end: Self) -> Self + where + T: Copy, + { + self.max(start).min(end) + } +} + +impl<T: NumCast + Copy, U> Point2D<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + #[inline] + pub fn cast<NewT: NumCast>(self) -> Point2D<NewT, U> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + pub fn try_cast<NewT: NumCast>(self) -> Option<Point2D<NewT, U>> { + match (NumCast::from(self.x), NumCast::from(self.y)) { + (Some(x), Some(y)) => Some(point2(x, y)), + _ => None, + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` point. + #[inline] + pub fn to_f32(self) -> Point2D<f32, U> { + self.cast() + } + + /// Cast into an `f64` point. + #[inline] + pub fn to_f64(self) -> Point2D<f64, U> { + self.cast() + } + + /// Cast into an `usize` point, truncating decimals if any. + /// + /// When casting from floating point points, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_usize(self) -> Point2D<usize, U> { + self.cast() + } + + /// Cast into an `u32` point, truncating decimals if any. + /// + /// When casting from floating point points, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_u32(self) -> Point2D<u32, U> { + self.cast() + } + + /// Cast into an i32 point, truncating decimals if any. + /// + /// When casting from floating point points, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i32(self) -> Point2D<i32, U> { + self.cast() + } + + /// Cast into an i64 point, truncating decimals if any. + /// + /// When casting from floating point points, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i64(self) -> Point2D<i64, U> { + self.cast() + } +} + +impl<T: Float, U> Point2D<T, U> { + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.x.is_finite() && self.y.is_finite() + } +} + +impl<T: Copy + Add<T, Output = T>, U> Point2D<T, U> { + #[inline] + pub fn add_size(self, other: &Size2D<T, U>) -> Self { + point2(self.x + other.width, self.y + other.height) + } +} + +impl<T: Real + Sub<T, Output = T>, U> Point2D<T, U> { + #[inline] + pub fn distance_to(self, other: Self) -> T { + (self - other).length() + } +} + +impl<T: Neg, U> Neg for Point2D<T, U> { + type Output = Point2D<T::Output, U>; + + #[inline] + fn neg(self) -> Self::Output { + point2(-self.x, -self.y) + } +} + +impl<T: Add, U> Add<Size2D<T, U>> for Point2D<T, U> { + type Output = Point2D<T::Output, U>; + + #[inline] + fn add(self, other: Size2D<T, U>) -> Self::Output { + point2(self.x + other.width, self.y + other.height) + } +} + +impl<T: AddAssign, U> AddAssign<Size2D<T, U>> for Point2D<T, U> { + #[inline] + fn add_assign(&mut self, other: Size2D<T, U>) { + self.x += other.width; + self.y += other.height; + } +} + +impl<T: Add, U> Add<Vector2D<T, U>> for Point2D<T, U> { + type Output = Point2D<T::Output, U>; + + #[inline] + fn add(self, other: Vector2D<T, U>) -> Self::Output { + point2(self.x + other.x, self.y + other.y) + } +} + +impl<T: Copy + Add<T, Output = T>, U> AddAssign<Vector2D<T, U>> for Point2D<T, U> { + #[inline] + fn add_assign(&mut self, other: Vector2D<T, U>) { + *self = *self + other + } +} + +impl<T: Sub, U> Sub for Point2D<T, U> { + type Output = Vector2D<T::Output, U>; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + vec2(self.x - other.x, self.y - other.y) + } +} + +impl<T: Sub, U> Sub<Size2D<T, U>> for Point2D<T, U> { + type Output = Point2D<T::Output, U>; + + #[inline] + fn sub(self, other: Size2D<T, U>) -> Self::Output { + point2(self.x - other.width, self.y - other.height) + } +} + +impl<T: SubAssign, U> SubAssign<Size2D<T, U>> for Point2D<T, U> { + #[inline] + fn sub_assign(&mut self, other: Size2D<T, U>) { + self.x -= other.width; + self.y -= other.height; + } +} + +impl<T: Sub, U> Sub<Vector2D<T, U>> for Point2D<T, U> { + type Output = Point2D<T::Output, U>; + + #[inline] + fn sub(self, other: Vector2D<T, U>) -> Self::Output { + point2(self.x - other.x, self.y - other.y) + } +} + +impl<T: Copy + Sub<T, Output = T>, U> SubAssign<Vector2D<T, U>> for Point2D<T, U> { + #[inline] + fn sub_assign(&mut self, other: Vector2D<T, U>) { + *self = *self - other + } +} + +impl<T: Copy + Mul, U> Mul<T> for Point2D<T, U> { + type Output = Point2D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + point2(self.x * scale, self.y * scale) + } +} + +impl<T: Copy + Mul<T, Output = T>, U> MulAssign<T> for Point2D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Point2D<T, U1> { + type Output = Point2D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + point2(self.x * scale.0, self.y * scale.0) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Point2D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: Scale<T, U, U>) { + self.x *= scale.0; + self.y *= scale.0; + } +} + +impl<T: Copy + Div, U> Div<T> for Point2D<T, U> { + type Output = Point2D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + point2(self.x / scale, self.y / scale) + } +} + +impl<T: Copy + Div<T, Output = T>, U> DivAssign<T> for Point2D<T, U> { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Point2D<T, U2> { + type Output = Point2D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + point2(self.x / scale.0, self.y / scale.0) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Point2D<T, U> { + #[inline] + fn div_assign(&mut self, scale: Scale<T, U, U>) { + self.x /= scale.0; + self.y /= scale.0; + } +} + +impl<T: Zero, U> Zero for Point2D<T, U> { + #[inline] + fn zero() -> Self { + Self::origin() + } +} + +impl<T: Round, U> Round for Point2D<T, U> { + /// See [Point2D::round()](#method.round) + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl<T: Ceil, U> Ceil for Point2D<T, U> { + /// See [Point2D::ceil()](#method.ceil) + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl<T: Floor, U> Floor for Point2D<T, U> { + /// See [Point2D::floor()](#method.floor) + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl<T: ApproxEq<T>, U> ApproxEq<Point2D<T, U>> for Point2D<T, U> { + #[inline] + fn approx_epsilon() -> Self { + point2(T::approx_epsilon(), T::approx_epsilon()) + } + + #[inline] + fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool { + self.x.approx_eq_eps(&other.x, &eps.x) && self.y.approx_eq_eps(&other.y, &eps.y) + } +} + +impl<T, U> Into<[T; 2]> for Point2D<T, U> { + fn into(self) -> [T; 2] { + [self.x, self.y] + } +} + +impl<T, U> From<[T; 2]> for Point2D<T, U> { + fn from([x, y]: [T; 2]) -> Self { + point2(x, y) + } +} + +impl<T, U> Into<(T, T)> for Point2D<T, U> { + fn into(self) -> (T, T) { + (self.x, self.y) + } +} + +impl<T, U> From<(T, T)> for Point2D<T, U> { + fn from(tuple: (T, T)) -> Self { + point2(tuple.0, tuple.1) + } +} + +/// A 3d Point tagged with a unit. +#[repr(C)] +pub struct Point3D<T, U> { + pub x: T, + pub y: T, + pub z: T, + #[doc(hidden)] + pub _unit: PhantomData<U>, +} + +mint_vec!(Point3D[x, y, z] = Point3); + +impl<T: Copy, U> Copy for Point3D<T, U> {} + +impl<T: Clone, U> Clone for Point3D<T, U> { + fn clone(&self) -> Self { + Point3D { + x: self.x.clone(), + y: self.y.clone(), + z: self.z.clone(), + _unit: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T, U> serde::Deserialize<'de> for Point3D<T, U> +where + T: serde::Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let (x, y, z) = serde::Deserialize::deserialize(deserializer)?; + Ok(Point3D { + x, + y, + z, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl<T, U> serde::Serialize for Point3D<T, U> +where + T: serde::Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + (&self.x, &self.y, &self.z).serialize(serializer) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Point3D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Point3D<T, U> {} + +impl<T, U> Eq for Point3D<T, U> where T: Eq {} + +impl<T, U> PartialEq for Point3D<T, U> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z + } +} + +impl<T, U> Hash for Point3D<T, U> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + self.z.hash(h); + } +} + +impl<T: fmt::Debug, U> fmt::Debug for Point3D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("") + .field(&self.x) + .field(&self.y) + .field(&self.z) + .finish() + } +} + +impl<T: Default, U> Default for Point3D<T, U> { + fn default() -> Self { + Point3D::new(Default::default(), Default::default(), Default::default()) + } +} + +impl<T, U> Point3D<T, U> { + /// Constructor, setting all components to zero. + #[inline] + pub fn origin() -> Self + where + T: Zero, + { + point3(Zero::zero(), Zero::zero(), Zero::zero()) + } + + /// The same as [`origin()`](#method.origin). + #[inline] + pub fn zero() -> Self + where + T: Zero, + { + Self::origin() + } + + /// Constructor taking scalar values directly. + #[inline] + pub const fn new(x: T, y: T, z: T) -> Self { + Point3D { + x, + y, + z, + _unit: PhantomData, + } + } + + /// Constructor taking properly Lengths instead of scalar values. + #[inline] + pub fn from_lengths(x: Length<T, U>, y: Length<T, U>, z: Length<T, U>) -> Self { + point3(x.0, y.0, z.0) + } + + /// Constructor setting all components to the same value. + #[inline] + pub fn splat(v: T) -> Self + where + T: Clone, + { + Point3D { + x: v.clone(), + y: v.clone(), + z: v, + _unit: PhantomData, + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Point3D<T, UnknownUnit>) -> Self { + point3(p.x, p.y, p.z) + } +} + +impl<T: Copy, U> Point3D<T, U> { + /// Cast this point into a vector. + /// + /// Equivalent to subtracting the origin to this point. + #[inline] + pub fn to_vector(self) -> Vector3D<T, U> { + Vector3D { + x: self.x, + y: self.y, + z: self.z, + _unit: PhantomData, + } + } + + /// Returns a 2d point using this point's x and y coordinates + #[inline] + pub fn xy(self) -> Point2D<T, U> { + point2(self.x, self.y) + } + + /// Returns a 2d point using this point's x and z coordinates + #[inline] + pub fn xz(self) -> Point2D<T, U> { + point2(self.x, self.z) + } + + /// Returns a 2d point using this point's x and z coordinates + #[inline] + pub fn yz(self) -> Point2D<T, U> { + point2(self.y, self.z) + } + + /// Cast into an array with x, y and z. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point3D, point3}; + /// enum Mm {} + /// + /// let point: Point3D<_, Mm> = point3(1, -8, 0); + /// + /// assert_eq!(point.to_array(), [1, -8, 0]); + /// ``` + #[inline] + pub fn to_array(self) -> [T; 3] { + [self.x, self.y, self.z] + } + + #[inline] + pub fn to_array_4d(self) -> [T; 4] + where + T: One, + { + [self.x, self.y, self.z, One::one()] + } + + /// Cast into a tuple with x, y and z. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point3D, point3}; + /// enum Mm {} + /// + /// let point: Point3D<_, Mm> = point3(1, -8, 0); + /// + /// assert_eq!(point.to_tuple(), (1, -8, 0)); + /// ``` + #[inline] + pub fn to_tuple(self) -> (T, T, T) { + (self.x, self.y, self.z) + } + + #[inline] + pub fn to_tuple_4d(self) -> (T, T, T, T) + where + T: One, + { + (self.x, self.y, self.z, One::one()) + } + + /// Drop the units, preserving only the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point3D, point3}; + /// enum Mm {} + /// + /// let point: Point3D<_, Mm> = point3(1, -8, 0); + /// + /// assert_eq!(point.x, point.to_untyped().x); + /// assert_eq!(point.y, point.to_untyped().y); + /// assert_eq!(point.z, point.to_untyped().z); + /// ``` + #[inline] + pub fn to_untyped(self) -> Point3D<T, UnknownUnit> { + point3(self.x, self.y, self.z) + } + + /// Cast the unit, preserving the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use euclid::{Point3D, point3}; + /// enum Mm {} + /// enum Cm {} + /// + /// let point: Point3D<_, Mm> = point3(1, -8, 0); + /// + /// assert_eq!(point.x, point.cast_unit::<Cm>().x); + /// assert_eq!(point.y, point.cast_unit::<Cm>().y); + /// assert_eq!(point.z, point.cast_unit::<Cm>().z); + /// ``` + #[inline] + pub fn cast_unit<V>(self) -> Point3D<T, V> { + point3(self.x, self.y, self.z) + } + + /// Convert into a 2d point. + #[inline] + pub fn to_2d(self) -> Point2D<T, U> { + self.xy() + } + + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::point3; + /// enum Mm {} + /// + /// assert_eq!(point3::<_, Mm>(-0.1, -0.8, 0.4).round(), point3::<_, Mm>(0.0, -1.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn round(self) -> Self + where + T: Round, + { + point3(self.x.round(), self.y.round(), self.z.round()) + } + + /// Rounds each component to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::point3; + /// enum Mm {} + /// + /// assert_eq!(point3::<_, Mm>(-0.1, -0.8, 0.4).ceil(), point3::<_, Mm>(0.0, 0.0, 1.0)) + /// ``` + #[inline] + #[must_use] + pub fn ceil(self) -> Self + where + T: Ceil, + { + point3(self.x.ceil(), self.y.ceil(), self.z.ceil()) + } + + /// Rounds each component to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::point3; + /// enum Mm {} + /// + /// assert_eq!(point3::<_, Mm>(-0.1, -0.8, 0.4).floor(), point3::<_, Mm>(-1.0, -1.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn floor(self) -> Self + where + T: Floor, + { + point3(self.x.floor(), self.y.floor(), self.z.floor()) + } + + /// Linearly interpolate between this point and another point. + /// + /// # Example + /// + /// ```rust + /// use euclid::point3; + /// use euclid::default::Point3D; + /// + /// let from: Point3D<_> = point3(0.0, 10.0, -1.0); + /// let to: Point3D<_> = point3(8.0, -4.0, 0.0); + /// + /// assert_eq!(from.lerp(to, -1.0), point3(-8.0, 24.0, -2.0)); + /// assert_eq!(from.lerp(to, 0.0), point3( 0.0, 10.0, -1.0)); + /// assert_eq!(from.lerp(to, 0.5), point3( 4.0, 3.0, -0.5)); + /// assert_eq!(from.lerp(to, 1.0), point3( 8.0, -4.0, 0.0)); + /// assert_eq!(from.lerp(to, 2.0), point3(16.0, -18.0, 1.0)); + /// ``` + #[inline] + pub fn lerp(self, other: Self, t: T) -> Self + where + T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>, + { + let one_t = T::one() - t; + point3( + one_t * self.x + t * other.x, + one_t * self.y + t * other.y, + one_t * self.z + t * other.z, + ) + } +} + +impl<T: PartialOrd, U> Point3D<T, U> { + #[inline] + pub fn min(self, other: Self) -> Self { + point3( + min(self.x, other.x), + min(self.y, other.y), + min(self.z, other.z), + ) + } + + #[inline] + pub fn max(self, other: Self) -> Self { + point3( + max(self.x, other.x), + max(self.y, other.y), + max(self.z, other.z), + ) + } + + /// Returns the point each component of which clamped by corresponding + /// components of `start` and `end`. + /// + /// Shortcut for `self.max(start).min(end)`. + #[inline] + pub fn clamp(self, start: Self, end: Self) -> Self + where + T: Copy, + { + self.max(start).min(end) + } +} + +impl<T: NumCast + Copy, U> Point3D<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + #[inline] + pub fn cast<NewT: NumCast>(self) -> Point3D<NewT, U> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + pub fn try_cast<NewT: NumCast>(self) -> Option<Point3D<NewT, U>> { + match ( + NumCast::from(self.x), + NumCast::from(self.y), + NumCast::from(self.z), + ) { + (Some(x), Some(y), Some(z)) => Some(point3(x, y, z)), + _ => None, + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` point. + #[inline] + pub fn to_f32(self) -> Point3D<f32, U> { + self.cast() + } + + /// Cast into an `f64` point. + #[inline] + pub fn to_f64(self) -> Point3D<f64, U> { + self.cast() + } + + /// Cast into an `usize` point, truncating decimals if any. + /// + /// When casting from floating point points, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_usize(self) -> Point3D<usize, U> { + self.cast() + } + + /// Cast into an `u32` point, truncating decimals if any. + /// + /// When casting from floating point points, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_u32(self) -> Point3D<u32, U> { + self.cast() + } + + /// Cast into an `i32` point, truncating decimals if any. + /// + /// When casting from floating point points, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i32(self) -> Point3D<i32, U> { + self.cast() + } + + /// Cast into an `i64` point, truncating decimals if any. + /// + /// When casting from floating point points, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i64(self) -> Point3D<i64, U> { + self.cast() + } +} + +impl<T: Float, U> Point3D<T, U> { + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.x.is_finite() && self.y.is_finite() && self.z.is_finite() + } +} + +impl<T: Copy + Add<T, Output = T>, U> Point3D<T, U> { + #[inline] + pub fn add_size(self, other: Size3D<T, U>) -> Self { + point3( + self.x + other.width, + self.y + other.height, + self.z + other.depth, + ) + } +} + +impl<T: Real + Sub<T, Output = T>, U> Point3D<T, U> { + #[inline] + pub fn distance_to(self, other: Self) -> T { + (self - other).length() + } +} + +impl<T: Neg, U> Neg for Point3D<T, U> { + type Output = Point3D<T::Output, U>; + + #[inline] + fn neg(self) -> Self::Output { + point3(-self.x, -self.y, -self.z) + } +} + +impl<T: Add, U> Add<Size3D<T, U>> for Point3D<T, U> { + type Output = Point3D<T::Output, U>; + + #[inline] + fn add(self, other: Size3D<T, U>) -> Self::Output { + point3( + self.x + other.width, + self.y + other.height, + self.z + other.depth, + ) + } +} + +impl<T: AddAssign, U> AddAssign<Size3D<T, U>> for Point3D<T, U> { + #[inline] + fn add_assign(&mut self, other: Size3D<T, U>) { + self.x += other.width; + self.y += other.height; + self.z += other.depth; + } +} + +impl<T: Add, U> Add<Vector3D<T, U>> for Point3D<T, U> { + type Output = Point3D<T::Output, U>; + + #[inline] + fn add(self, other: Vector3D<T, U>) -> Self::Output { + point3(self.x + other.x, self.y + other.y, self.z + other.z) + } +} + +impl<T: Copy + Add<T, Output = T>, U> AddAssign<Vector3D<T, U>> for Point3D<T, U> { + #[inline] + fn add_assign(&mut self, other: Vector3D<T, U>) { + *self = *self + other + } +} + +impl<T: Sub, U> Sub for Point3D<T, U> { + type Output = Vector3D<T::Output, U>; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + vec3(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl<T: Sub, U> Sub<Size3D<T, U>> for Point3D<T, U> { + type Output = Point3D<T::Output, U>; + + #[inline] + fn sub(self, other: Size3D<T, U>) -> Self::Output { + point3( + self.x - other.width, + self.y - other.height, + self.z - other.depth, + ) + } +} + +impl<T: SubAssign, U> SubAssign<Size3D<T, U>> for Point3D<T, U> { + #[inline] + fn sub_assign(&mut self, other: Size3D<T, U>) { + self.x -= other.width; + self.y -= other.height; + self.z -= other.depth; + } +} + +impl<T: Sub, U> Sub<Vector3D<T, U>> for Point3D<T, U> { + type Output = Point3D<T::Output, U>; + + #[inline] + fn sub(self, other: Vector3D<T, U>) -> Self::Output { + point3(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl<T: Copy + Sub<T, Output = T>, U> SubAssign<Vector3D<T, U>> for Point3D<T, U> { + #[inline] + fn sub_assign(&mut self, other: Vector3D<T, U>) { + *self = *self - other + } +} + +impl<T: Copy + Mul, U> Mul<T> for Point3D<T, U> { + type Output = Point3D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + point3( + self.x * scale, + self.y * scale, + self.z * scale, + ) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<T> for Point3D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: T) { + self.x *= scale; + self.y *= scale; + self.z *= scale; + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Point3D<T, U1> { + type Output = Point3D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + point3( + self.x * scale.0, + self.y * scale.0, + self.z * scale.0, + ) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Point3D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: Scale<T, U, U>) { + *self *= scale.0; + } +} + +impl<T: Copy + Div, U> Div<T> for Point3D<T, U> { + type Output = Point3D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + point3( + self.x / scale, + self.y / scale, + self.z / scale, + ) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<T> for Point3D<T, U> { + #[inline] + fn div_assign(&mut self, scale: T) { + self.x /= scale; + self.y /= scale; + self.z /= scale; + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Point3D<T, U2> { + type Output = Point3D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + point3( + self.x / scale.0, + self.y / scale.0, + self.z / scale.0, + ) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Point3D<T, U> { + #[inline] + fn div_assign(&mut self, scale: Scale<T, U, U>) { + *self /= scale.0; + } +} + +impl<T: Zero, U> Zero for Point3D<T, U> { + #[inline] + fn zero() -> Self { + Self::origin() + } +} + +impl<T: Round, U> Round for Point3D<T, U> { + /// See [Point3D::round()](#method.round) + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl<T: Ceil, U> Ceil for Point3D<T, U> { + /// See [Point3D::ceil()](#method.ceil) + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl<T: Floor, U> Floor for Point3D<T, U> { + /// See [Point3D::floor()](#method.floor) + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl<T: ApproxEq<T>, U> ApproxEq<Point3D<T, U>> for Point3D<T, U> { + #[inline] + fn approx_epsilon() -> Self { + point3( + T::approx_epsilon(), + T::approx_epsilon(), + T::approx_epsilon(), + ) + } + + #[inline] + fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool { + self.x.approx_eq_eps(&other.x, &eps.x) + && self.y.approx_eq_eps(&other.y, &eps.y) + && self.z.approx_eq_eps(&other.z, &eps.z) + } +} + +impl<T, U> Into<[T; 3]> for Point3D<T, U> { + fn into(self) -> [T; 3] { + [self.x, self.y, self.z] + } +} + +impl<T, U> From<[T; 3]> for Point3D<T, U> { + fn from([x, y, z]: [T; 3]) -> Self { + point3(x, y, z) + } +} + +impl<T, U> Into<(T, T, T)> for Point3D<T, U> { + fn into(self) -> (T, T, T) { + (self.x, self.y, self.z) + } +} + +impl<T, U> From<(T, T, T)> for Point3D<T, U> { + fn from(tuple: (T, T, T)) -> Self { + point3(tuple.0, tuple.1, tuple.2) + } +} + +/// Shorthand for `Point2D::new(x, y)`. +#[inline] +pub const fn point2<T, U>(x: T, y: T) -> Point2D<T, U> { + Point2D { + x, + y, + _unit: PhantomData, + } +} + +/// Shorthand for `Point3D::new(x, y)`. +#[inline] +pub const fn point3<T, U>(x: T, y: T, z: T) -> Point3D<T, U> { + Point3D { + x, + y, + z, + _unit: PhantomData, + } +} + +#[cfg(test)] +mod point2d { + use crate::default::Point2D; + use crate::point2; + + #[cfg(feature = "mint")] + use mint; + + #[test] + pub fn test_min() { + let p1 = Point2D::new(1.0, 3.0); + let p2 = Point2D::new(2.0, 2.0); + + let result = p1.min(p2); + + assert_eq!(result, Point2D::new(1.0, 2.0)); + } + + #[test] + pub fn test_max() { + let p1 = Point2D::new(1.0, 3.0); + let p2 = Point2D::new(2.0, 2.0); + + let result = p1.max(p2); + + assert_eq!(result, Point2D::new(2.0, 3.0)); + } + + #[cfg(feature = "mint")] + #[test] + pub fn test_mint() { + let p1 = Point2D::new(1.0, 3.0); + let pm: mint::Point2<_> = p1.into(); + let p2 = Point2D::from(pm); + + assert_eq!(p1, p2); + } + + #[test] + pub fn test_conv_vector() { + for i in 0..100 { + // We don't care about these values as long as they are not the same. + let x = i as f32 * 0.012345; + let y = i as f32 * 0.987654; + let p: Point2D<f32> = point2(x, y); + assert_eq!(p.to_vector().to_point(), p); + } + } + + #[test] + pub fn test_swizzling() { + let p: Point2D<i32> = point2(1, 2); + assert_eq!(p.yx(), point2(2, 1)); + } + + #[test] + pub fn test_distance_to() { + let p1 = Point2D::new(1.0, 2.0); + let p2 = Point2D::new(2.0, 2.0); + + assert_eq!(p1.distance_to(p2), 1.0); + + let p1 = Point2D::new(1.0, 2.0); + let p2 = Point2D::new(1.0, 4.0); + + assert_eq!(p1.distance_to(p2), 2.0); + } + + mod ops { + use crate::default::Point2D; + use crate::scale::Scale; + use crate::{size2, vec2, Vector2D}; + + pub enum Mm {} + pub enum Cm {} + + pub type Point2DMm<T> = crate::Point2D<T, Mm>; + pub type Point2DCm<T> = crate::Point2D<T, Cm>; + + #[test] + pub fn test_neg() { + assert_eq!(-Point2D::new(1.0, 2.0), Point2D::new(-1.0, -2.0)); + assert_eq!(-Point2D::new(0.0, 0.0), Point2D::new(-0.0, -0.0)); + assert_eq!(-Point2D::new(-1.0, -2.0), Point2D::new(1.0, 2.0)); + } + + #[test] + pub fn test_add_size() { + let p1 = Point2DMm::new(1.0, 2.0); + let p2 = size2(3.0, 4.0); + + let result = p1 + p2; + + assert_eq!(result, Point2DMm::new(4.0, 6.0)); + } + + #[test] + pub fn test_add_assign_size() { + let mut p1 = Point2DMm::new(1.0, 2.0); + + p1 += size2(3.0, 4.0); + + assert_eq!(p1, Point2DMm::new(4.0, 6.0)); + } + + #[test] + pub fn test_add_vec() { + let p1 = Point2DMm::new(1.0, 2.0); + let p2 = vec2(3.0, 4.0); + + let result = p1 + p2; + + assert_eq!(result, Point2DMm::new(4.0, 6.0)); + } + + #[test] + pub fn test_add_assign_vec() { + let mut p1 = Point2DMm::new(1.0, 2.0); + + p1 += vec2(3.0, 4.0); + + assert_eq!(p1, Point2DMm::new(4.0, 6.0)); + } + + #[test] + pub fn test_sub() { + let p1 = Point2DMm::new(1.0, 2.0); + let p2 = Point2DMm::new(3.0, 4.0); + + let result = p1 - p2; + + assert_eq!(result, Vector2D::<_, Mm>::new(-2.0, -2.0)); + } + + #[test] + pub fn test_sub_size() { + let p1 = Point2DMm::new(1.0, 2.0); + let p2 = size2(3.0, 4.0); + + let result = p1 - p2; + + assert_eq!(result, Point2DMm::new(-2.0, -2.0)); + } + + #[test] + pub fn test_sub_assign_size() { + let mut p1 = Point2DMm::new(1.0, 2.0); + + p1 -= size2(3.0, 4.0); + + assert_eq!(p1, Point2DMm::new(-2.0, -2.0)); + } + + #[test] + pub fn test_sub_vec() { + let p1 = Point2DMm::new(1.0, 2.0); + let p2 = vec2(3.0, 4.0); + + let result = p1 - p2; + + assert_eq!(result, Point2DMm::new(-2.0, -2.0)); + } + + #[test] + pub fn test_sub_assign_vec() { + let mut p1 = Point2DMm::new(1.0, 2.0); + + p1 -= vec2(3.0, 4.0); + + assert_eq!(p1, Point2DMm::new(-2.0, -2.0)); + } + + #[test] + pub fn test_mul_scalar() { + let p1: Point2D<f32> = Point2D::new(3.0, 5.0); + + let result = p1 * 5.0; + + assert_eq!(result, Point2D::new(15.0, 25.0)); + } + + #[test] + pub fn test_mul_assign_scalar() { + let mut p1 = Point2D::new(3.0, 5.0); + + p1 *= 5.0; + + assert_eq!(p1, Point2D::new(15.0, 25.0)); + } + + #[test] + pub fn test_mul_scale() { + let p1 = Point2DMm::new(1.0, 2.0); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = p1 * cm_per_mm; + + assert_eq!(result, Point2DCm::new(0.1, 0.2)); + } + + #[test] + pub fn test_mul_assign_scale() { + let mut p1 = Point2DMm::new(1.0, 2.0); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + p1 *= scale; + + assert_eq!(p1, Point2DMm::new(0.1, 0.2)); + } + + #[test] + pub fn test_div_scalar() { + let p1: Point2D<f32> = Point2D::new(15.0, 25.0); + + let result = p1 / 5.0; + + assert_eq!(result, Point2D::new(3.0, 5.0)); + } + + #[test] + pub fn test_div_assign_scalar() { + let mut p1: Point2D<f32> = Point2D::new(15.0, 25.0); + + p1 /= 5.0; + + assert_eq!(p1, Point2D::new(3.0, 5.0)); + } + + #[test] + pub fn test_div_scale() { + let p1 = Point2DCm::new(0.1, 0.2); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = p1 / cm_per_mm; + + assert_eq!(result, Point2DMm::new(1.0, 2.0)); + } + + #[test] + pub fn test_div_assign_scale() { + let mut p1 = Point2DMm::new(0.1, 0.2); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + p1 /= scale; + + assert_eq!(p1, Point2DMm::new(1.0, 2.0)); + } + + #[test] + pub fn test_point_debug_formatting() { + let n = 1.23456789; + let p1 = Point2D::new(n, -n); + let should_be = format!("({:.4}, {:.4})", n, -n); + + let got = format!("{:.4?}", p1); + + assert_eq!(got, should_be); + } + } +} + +#[cfg(test)] +mod point3d { + use crate::default; + use crate::default::Point3D; + use crate::{point2, point3}; + #[cfg(feature = "mint")] + use mint; + + #[test] + pub fn test_min() { + let p1 = Point3D::new(1.0, 3.0, 5.0); + let p2 = Point3D::new(2.0, 2.0, -1.0); + + let result = p1.min(p2); + + assert_eq!(result, Point3D::new(1.0, 2.0, -1.0)); + } + + #[test] + pub fn test_max() { + let p1 = Point3D::new(1.0, 3.0, 5.0); + let p2 = Point3D::new(2.0, 2.0, -1.0); + + let result = p1.max(p2); + + assert_eq!(result, Point3D::new(2.0, 3.0, 5.0)); + } + + #[test] + pub fn test_conv_vector() { + use crate::point3; + for i in 0..100 { + // We don't care about these values as long as they are not the same. + let x = i as f32 * 0.012345; + let y = i as f32 * 0.987654; + let z = x * y; + let p: Point3D<f32> = point3(x, y, z); + assert_eq!(p.to_vector().to_point(), p); + } + } + + #[test] + pub fn test_swizzling() { + let p: default::Point3D<i32> = point3(1, 2, 3); + assert_eq!(p.xy(), point2(1, 2)); + assert_eq!(p.xz(), point2(1, 3)); + assert_eq!(p.yz(), point2(2, 3)); + } + + #[test] + pub fn test_distance_to() { + let p1 = Point3D::new(1.0, 2.0, 3.0); + let p2 = Point3D::new(2.0, 2.0, 3.0); + + assert_eq!(p1.distance_to(p2), 1.0); + + let p1 = Point3D::new(1.0, 2.0, 3.0); + let p2 = Point3D::new(1.0, 4.0, 3.0); + + assert_eq!(p1.distance_to(p2), 2.0); + + let p1 = Point3D::new(1.0, 2.0, 3.0); + let p2 = Point3D::new(1.0, 2.0, 6.0); + + assert_eq!(p1.distance_to(p2), 3.0); + } + + #[cfg(feature = "mint")] + #[test] + pub fn test_mint() { + let p1 = Point3D::new(1.0, 3.0, 5.0); + let pm: mint::Point3<_> = p1.into(); + let p2 = Point3D::from(pm); + + assert_eq!(p1, p2); + } + + mod ops { + use crate::default::Point3D; + use crate::scale::Scale; + use crate::{size3, vec3, Vector3D}; + + pub enum Mm {} + pub enum Cm {} + + pub type Point3DMm<T> = crate::Point3D<T, Mm>; + pub type Point3DCm<T> = crate::Point3D<T, Cm>; + + #[test] + pub fn test_neg() { + assert_eq!(-Point3D::new(1.0, 2.0, 3.0), Point3D::new(-1.0, -2.0, -3.0)); + assert_eq!(-Point3D::new(0.0, 0.0, 0.0), Point3D::new(-0.0, -0.0, -0.0)); + assert_eq!(-Point3D::new(-1.0, -2.0, -3.0), Point3D::new(1.0, 2.0, 3.0)); + } + + #[test] + pub fn test_add_size() { + let p1 = Point3DMm::new(1.0, 2.0, 3.0); + let p2 = size3(4.0, 5.0, 6.0); + + let result = p1 + p2; + + assert_eq!(result, Point3DMm::new(5.0, 7.0, 9.0)); + } + + #[test] + pub fn test_add_assign_size() { + let mut p1 = Point3DMm::new(1.0, 2.0, 3.0); + + p1 += size3(4.0, 5.0, 6.0); + + assert_eq!(p1, Point3DMm::new(5.0, 7.0, 9.0)); + } + + #[test] + pub fn test_add_vec() { + let p1 = Point3DMm::new(1.0, 2.0, 3.0); + let p2 = vec3(4.0, 5.0, 6.0); + + let result = p1 + p2; + + assert_eq!(result, Point3DMm::new(5.0, 7.0, 9.0)); + } + + #[test] + pub fn test_add_assign_vec() { + let mut p1 = Point3DMm::new(1.0, 2.0, 3.0); + + p1 += vec3(4.0, 5.0, 6.0); + + assert_eq!(p1, Point3DMm::new(5.0, 7.0, 9.0)); + } + + #[test] + pub fn test_sub() { + let p1 = Point3DMm::new(1.0, 2.0, 3.0); + let p2 = Point3DMm::new(4.0, 5.0, 6.0); + + let result = p1 - p2; + + assert_eq!(result, Vector3D::<_, Mm>::new(-3.0, -3.0, -3.0)); + } + + #[test] + pub fn test_sub_size() { + let p1 = Point3DMm::new(1.0, 2.0, 3.0); + let p2 = size3(4.0, 5.0, 6.0); + + let result = p1 - p2; + + assert_eq!(result, Point3DMm::new(-3.0, -3.0, -3.0)); + } + + #[test] + pub fn test_sub_assign_size() { + let mut p1 = Point3DMm::new(1.0, 2.0, 3.0); + + p1 -= size3(4.0, 5.0, 6.0); + + assert_eq!(p1, Point3DMm::new(-3.0, -3.0, -3.0)); + } + + #[test] + pub fn test_sub_vec() { + let p1 = Point3DMm::new(1.0, 2.0, 3.0); + let p2 = vec3(4.0, 5.0, 6.0); + + let result = p1 - p2; + + assert_eq!(result, Point3DMm::new(-3.0, -3.0, -3.0)); + } + + #[test] + pub fn test_sub_assign_vec() { + let mut p1 = Point3DMm::new(1.0, 2.0, 3.0); + + p1 -= vec3(4.0, 5.0, 6.0); + + assert_eq!(p1, Point3DMm::new(-3.0, -3.0, -3.0)); + } + + #[test] + pub fn test_mul_scalar() { + let p1: Point3D<f32> = Point3D::new(3.0, 5.0, 7.0); + + let result = p1 * 5.0; + + assert_eq!(result, Point3D::new(15.0, 25.0, 35.0)); + } + + #[test] + pub fn test_mul_assign_scalar() { + let mut p1: Point3D<f32> = Point3D::new(3.0, 5.0, 7.0); + + p1 *= 5.0; + + assert_eq!(p1, Point3D::new(15.0, 25.0, 35.0)); + } + + #[test] + pub fn test_mul_scale() { + let p1 = Point3DMm::new(1.0, 2.0, 3.0); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = p1 * cm_per_mm; + + assert_eq!(result, Point3DCm::new(0.1, 0.2, 0.3)); + } + + #[test] + pub fn test_mul_assign_scale() { + let mut p1 = Point3DMm::new(1.0, 2.0, 3.0); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + p1 *= scale; + + assert_eq!(p1, Point3DMm::new(0.1, 0.2, 0.3)); + } + + #[test] + pub fn test_div_scalar() { + let p1: Point3D<f32> = Point3D::new(15.0, 25.0, 35.0); + + let result = p1 / 5.0; + + assert_eq!(result, Point3D::new(3.0, 5.0, 7.0)); + } + + #[test] + pub fn test_div_assign_scalar() { + let mut p1: Point3D<f32> = Point3D::new(15.0, 25.0, 35.0); + + p1 /= 5.0; + + assert_eq!(p1, Point3D::new(3.0, 5.0, 7.0)); + } + + #[test] + pub fn test_div_scale() { + let p1 = Point3DCm::new(0.1, 0.2, 0.3); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = p1 / cm_per_mm; + + assert_eq!(result, Point3DMm::new(1.0, 2.0, 3.0)); + } + + #[test] + pub fn test_div_assign_scale() { + let mut p1 = Point3DMm::new(0.1, 0.2, 0.3); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + p1 /= scale; + + assert_eq!(p1, Point3DMm::new(1.0, 2.0, 3.0)); + } + } +} diff --git a/third_party/rust/euclid/src/rect.rs b/third_party/rust/euclid/src/rect.rs new file mode 100644 index 0000000000..04721db811 --- /dev/null +++ b/third_party/rust/euclid/src/rect.rs @@ -0,0 +1,931 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::box2d::Box2D; +use crate::num::*; +use crate::point::Point2D; +use crate::scale::Scale; +use crate::side_offsets::SideOffsets2D; +use crate::size::Size2D; +use crate::vector::Vector2D; + +use num_traits::{NumCast, Float}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +use core::borrow::Borrow; +use core::cmp::PartialOrd; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Range, Sub}; + +/// A 2d Rectangle optionally tagged with a unit. +/// +/// # Representation +/// +/// `Rect` is represented by an origin point and a size. +/// +/// See [`Box2D`] for a rectangle represented by two endpoints. +/// +/// # Empty rectangle +/// +/// A rectangle is considered empty (see [`is_empty`]) if any of the following is true: +/// - it's area is empty, +/// - it's area is negative (`size.x < 0` or `size.y < 0`), +/// - it contains NaNs. +/// +/// [`is_empty`]: #method.is_empty +/// [`Box2D`]: struct.Box2D.html +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) +)] +pub struct Rect<T, U> { + pub origin: Point2D<T, U>, + pub size: Size2D<T, U>, +} + +#[cfg(feature = "arbitrary")] +impl<'a, T, U> arbitrary::Arbitrary<'a> for Rect<T, U> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> + { + let (origin, size) = arbitrary::Arbitrary::arbitrary(u)?; + Ok(Rect { + origin, + size, + }) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Rect<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Rect<T, U> {} + +impl<T: Hash, U> Hash for Rect<T, U> { + fn hash<H: Hasher>(&self, h: &mut H) { + self.origin.hash(h); + self.size.hash(h); + } +} + +impl<T: Copy, U> Copy for Rect<T, U> {} + +impl<T: Clone, U> Clone for Rect<T, U> { + fn clone(&self) -> Self { + Self::new(self.origin.clone(), self.size.clone()) + } +} + +impl<T: PartialEq, U> PartialEq for Rect<T, U> { + fn eq(&self, other: &Self) -> bool { + self.origin.eq(&other.origin) && self.size.eq(&other.size) + } +} + +impl<T: Eq, U> Eq for Rect<T, U> {} + +impl<T: fmt::Debug, U> fmt::Debug for Rect<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Rect(")?; + fmt::Debug::fmt(&self.size, f)?; + write!(f, " at ")?; + fmt::Debug::fmt(&self.origin, f)?; + write!(f, ")") + } +} + +impl<T: Default, U> Default for Rect<T, U> { + fn default() -> Self { + Rect::new(Default::default(), Default::default()) + } +} + +impl<T, U> Rect<T, U> { + /// Constructor. + #[inline] + pub const fn new(origin: Point2D<T, U>, size: Size2D<T, U>) -> Self { + Rect { origin, size } + } +} + +impl<T, U> Rect<T, U> +where + T: Zero, +{ + /// Constructor, setting all sides to zero. + #[inline] + pub fn zero() -> Self { + Rect::new(Point2D::origin(), Size2D::zero()) + } + + /// Creates a rect of the given size, at offset zero. + #[inline] + pub fn from_size(size: Size2D<T, U>) -> Self { + Rect { + origin: Point2D::zero(), + size, + } + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + Add<T, Output = T>, +{ + #[inline] + pub fn min(&self) -> Point2D<T, U> { + self.origin + } + + #[inline] + pub fn max(&self) -> Point2D<T, U> { + self.origin + self.size + } + + #[inline] + pub fn max_x(&self) -> T { + self.origin.x + self.size.width + } + + #[inline] + pub fn min_x(&self) -> T { + self.origin.x + } + + #[inline] + pub fn max_y(&self) -> T { + self.origin.y + self.size.height + } + + #[inline] + pub fn min_y(&self) -> T { + self.origin.y + } + + #[inline] + pub fn width(&self) -> T { + self.size.width + } + + #[inline] + pub fn height(&self) -> T { + self.size.height + } + + #[inline] + pub fn x_range(&self) -> Range<T> { + self.min_x()..self.max_x() + } + + #[inline] + pub fn y_range(&self) -> Range<T> { + self.min_y()..self.max_y() + } + + /// Returns the same rectangle, translated by a vector. + #[inline] + #[must_use] + pub fn translate(&self, by: Vector2D<T, U>) -> Self { + Self::new(self.origin + by, self.size) + } + + #[inline] + pub fn to_box2d(&self) -> Box2D<T, U> { + Box2D { + min: self.min(), + max: self.max(), + } + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + PartialOrd + Add<T, Output = T>, +{ + /// Returns true if this rectangle contains the point. Points are considered + /// in the rectangle if they are on the left or top edge, but outside if they + /// are on the right or bottom edge. + #[inline] + pub fn contains(&self, p: Point2D<T, U>) -> bool { + self.to_box2d().contains(p) + } + + #[inline] + pub fn intersects(&self, other: &Self) -> bool { + self.to_box2d().intersects(&other.to_box2d()) + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>, +{ + #[inline] + pub fn intersection(&self, other: &Self) -> Option<Self> { + let box2d = self.to_box2d().intersection_unchecked(&other.to_box2d()); + + if box2d.is_empty() { + return None; + } + + Some(box2d.to_rect()) + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + Add<T, Output = T> + Sub<T, Output = T>, +{ + #[inline] + #[must_use] + pub fn inflate(&self, width: T, height: T) -> Self { + Rect::new( + Point2D::new(self.origin.x - width, self.origin.y - height), + Size2D::new( + self.size.width + width + width, + self.size.height + height + height, + ), + ) + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + Zero + PartialOrd + Add<T, Output = T>, +{ + /// Returns true if this rectangle contains the interior of rect. Always + /// returns true if rect is empty, and always returns false if rect is + /// nonempty but this rectangle is empty. + #[inline] + pub fn contains_rect(&self, rect: &Self) -> bool { + rect.is_empty() + || (self.min_x() <= rect.min_x() + && rect.max_x() <= self.max_x() + && self.min_y() <= rect.min_y() + && rect.max_y() <= self.max_y()) + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + Zero + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>, +{ + /// Calculate the size and position of an inner rectangle. + /// + /// Subtracts the side offsets from all sides. The horizontal and vertical + /// offsets must not be larger than the original side length. + /// This method assumes y oriented downward. + pub fn inner_rect(&self, offsets: SideOffsets2D<T, U>) -> Self { + let rect = Rect::new( + Point2D::new(self.origin.x + offsets.left, self.origin.y + offsets.top), + Size2D::new( + self.size.width - offsets.horizontal(), + self.size.height - offsets.vertical(), + ), + ); + debug_assert!(rect.size.width >= Zero::zero()); + debug_assert!(rect.size.height >= Zero::zero()); + rect + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + Add<T, Output = T> + Sub<T, Output = T>, +{ + /// Calculate the size and position of an outer rectangle. + /// + /// Add the offsets to all sides. The expanded rectangle is returned. + /// This method assumes y oriented downward. + pub fn outer_rect(&self, offsets: SideOffsets2D<T, U>) -> Self { + Rect::new( + Point2D::new(self.origin.x - offsets.left, self.origin.y - offsets.top), + Size2D::new( + self.size.width + offsets.horizontal(), + self.size.height + offsets.vertical(), + ), + ) + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + Zero + PartialOrd + Sub<T, Output = T>, +{ + /// Returns the smallest rectangle defined by the top/bottom/left/right-most + /// points provided as parameter. + /// + /// Note: This function has a behavior that can be surprising because + /// the right-most and bottom-most points are exactly on the edge + /// of the rectangle while the `contains` function is has exclusive + /// semantic on these edges. This means that the right-most and bottom-most + /// points provided to `from_points` will count as not contained by the rect. + /// This behavior may change in the future. + pub fn from_points<I>(points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow<Point2D<T, U>>, + { + Box2D::from_points(points).to_rect() + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>, +{ + /// Linearly interpolate between this rectangle and another rectangle. + #[inline] + pub fn lerp(&self, other: Self, t: T) -> Self { + Self::new( + self.origin.lerp(other.origin, t), + self.size.lerp(other.size, t), + ) + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + One + Add<Output = T> + Div<Output = T>, +{ + pub fn center(&self) -> Point2D<T, U> { + let two = T::one() + T::one(); + self.origin + self.size.to_vector() / two + } +} + +impl<T, U> Rect<T, U> +where + T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T> + Zero, +{ + #[inline] + pub fn union(&self, other: &Self) -> Self { + self.to_box2d().union(&other.to_box2d()).to_rect() + } +} + +impl<T, U> Rect<T, U> { + #[inline] + pub fn scale<S: Copy>(&self, x: S, y: S) -> Self + where + T: Copy + Mul<S, Output = T>, + { + Rect::new( + Point2D::new(self.origin.x * x, self.origin.y * y), + Size2D::new(self.size.width * x, self.size.height * y), + ) + } +} + +impl<T: Copy + Mul<T, Output = T>, U> Rect<T, U> { + #[inline] + pub fn area(&self) -> T { + self.size.area() + } +} + +impl<T: Copy + Zero + PartialOrd, U> Rect<T, U> { + #[inline] + pub fn is_empty(&self) -> bool { + self.size.is_empty() + } +} + +impl<T: Copy + Zero + PartialOrd, U> Rect<T, U> { + #[inline] + pub fn to_non_empty(&self) -> Option<Self> { + if self.is_empty() { + return None; + } + + Some(*self) + } +} + +impl<T: Copy + Mul, U> Mul<T> for Rect<T, U> { + type Output = Rect<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Rect::new(self.origin * scale, self.size * scale) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<T> for Rect<T, U> { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self *= Scale::new(scale); + } +} + +impl<T: Copy + Div, U> Div<T> for Rect<T, U> { + type Output = Rect<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Rect::new(self.origin / scale.clone(), self.size / scale) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<T> for Rect<T, U> { + #[inline] + fn div_assign(&mut self, scale: T) { + *self /= Scale::new(scale); + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Rect<T, U1> { + type Output = Rect<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + Rect::new(self.origin * scale.clone(), self.size * scale) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Rect<T, U> { + #[inline] + fn mul_assign(&mut self, scale: Scale<T, U, U>) { + self.origin *= scale.clone(); + self.size *= scale; + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Rect<T, U2> { + type Output = Rect<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + Rect::new(self.origin / scale.clone(), self.size / scale) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Rect<T, U> { + #[inline] + fn div_assign(&mut self, scale: Scale<T, U, U>) { + self.origin /= scale.clone(); + self.size /= scale; + } +} + +impl<T: Copy, U> Rect<T, U> { + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Rect<T, UnknownUnit> { + Rect::new(self.origin.to_untyped(), self.size.to_untyped()) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(r: &Rect<T, UnknownUnit>) -> Rect<T, U> { + Rect::new( + Point2D::from_untyped(r.origin), + Size2D::from_untyped(r.size), + ) + } + + /// Cast the unit + #[inline] + pub fn cast_unit<V>(&self) -> Rect<T, V> { + Rect::new(self.origin.cast_unit(), self.size.cast_unit()) + } +} + +impl<T: NumCast + Copy, U> Rect<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), round_in or round_out() before casting. + #[inline] + pub fn cast<NewT: NumCast>(&self) -> Rect<NewT, U> { + Rect::new(self.origin.cast(), self.size.cast()) + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), round_in or round_out() before casting. + pub fn try_cast<NewT: NumCast>(&self) -> Option<Rect<NewT, U>> { + match (self.origin.try_cast(), self.size.try_cast()) { + (Some(origin), Some(size)) => Some(Rect::new(origin, size)), + _ => None, + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` rectangle. + #[inline] + pub fn to_f32(&self) -> Rect<f32, U> { + self.cast() + } + + /// Cast into an `f64` rectangle. + #[inline] + pub fn to_f64(&self) -> Rect<f64, U> { + self.cast() + } + + /// Cast into an `usize` rectangle, truncating decimals if any. + /// + /// When casting from floating point rectangles, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_usize(&self) -> Rect<usize, U> { + self.cast() + } + + /// Cast into an `u32` rectangle, truncating decimals if any. + /// + /// When casting from floating point rectangles, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_u32(&self) -> Rect<u32, U> { + self.cast() + } + + /// Cast into an `u64` rectangle, truncating decimals if any. + /// + /// When casting from floating point rectangles, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_u64(&self) -> Rect<u64, U> { + self.cast() + } + + /// Cast into an `i32` rectangle, truncating decimals if any. + /// + /// When casting from floating point rectangles, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_i32(&self) -> Rect<i32, U> { + self.cast() + } + + /// Cast into an `i64` rectangle, truncating decimals if any. + /// + /// When casting from floating point rectangles, it is worth considering whether + /// to `round()`, `round_in()` or `round_out()` before the cast in order to + /// obtain the desired conversion behavior. + #[inline] + pub fn to_i64(&self) -> Rect<i64, U> { + self.cast() + } +} + +impl<T: Float, U> Rect<T, U> { + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.origin.is_finite() && self.size.is_finite() + } +} + +impl<T: Floor + Ceil + Round + Add<T, Output = T> + Sub<T, Output = T>, U> Rect<T, U> { + /// Return a rectangle with edges rounded to integer coordinates, such that + /// the returned rectangle has the same set of pixel centers as the original + /// one. + /// Edges at offset 0.5 round up. + /// Suitable for most places where integral device coordinates + /// are needed, but note that any translation should be applied first to + /// avoid pixel rounding errors. + /// Note that this is *not* rounding to nearest integer if the values are negative. + /// They are always rounding as floor(n + 0.5). + /// + /// # Usage notes + /// Note, that when using with floating-point `T` types that method can significantly + /// loose precision for large values, so if you need to call this method very often it + /// is better to use [`Box2D`]. + /// + /// [`Box2D`]: struct.Box2D.html + #[must_use] + pub fn round(&self) -> Self { + self.to_box2d().round().to_rect() + } + + /// Return a rectangle with edges rounded to integer coordinates, such that + /// the original rectangle contains the resulting rectangle. + /// + /// # Usage notes + /// Note, that when using with floating-point `T` types that method can significantly + /// loose precision for large values, so if you need to call this method very often it + /// is better to use [`Box2D`]. + /// + /// [`Box2D`]: struct.Box2D.html + #[must_use] + pub fn round_in(&self) -> Self { + self.to_box2d().round_in().to_rect() + } + + /// Return a rectangle with edges rounded to integer coordinates, such that + /// the original rectangle is contained in the resulting rectangle. + /// + /// # Usage notes + /// Note, that when using with floating-point `T` types that method can significantly + /// loose precision for large values, so if you need to call this method very often it + /// is better to use [`Box2D`]. + /// + /// [`Box2D`]: struct.Box2D.html + #[must_use] + pub fn round_out(&self) -> Self { + self.to_box2d().round_out().to_rect() + } +} + +impl<T, U> From<Size2D<T, U>> for Rect<T, U> +where + T: Zero, +{ + fn from(size: Size2D<T, U>) -> Self { + Self::from_size(size) + } +} + +/// Shorthand for `Rect::new(Point2D::new(x, y), Size2D::new(w, h))`. +pub const fn rect<T, U>(x: T, y: T, w: T, h: T) -> Rect<T, U> { + Rect::new(Point2D::new(x, y), Size2D::new(w, h)) +} + +#[cfg(test)] +mod tests { + use crate::default::{Point2D, Rect, Size2D}; + use crate::side_offsets::SideOffsets2D; + use crate::{point2, rect, size2, vec2}; + + #[test] + fn test_translate() { + let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32)); + let pp = p.translate(vec2(10, 15)); + + assert!(pp.size.width == 50); + assert!(pp.size.height == 40); + assert!(pp.origin.x == 10); + assert!(pp.origin.y == 15); + + let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); + let rr = r.translate(vec2(0, -10)); + + assert!(rr.size.width == 50); + assert!(rr.size.height == 40); + assert!(rr.origin.x == -10); + assert!(rr.origin.y == -15); + } + + #[test] + fn test_union() { + let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40)); + let q = Rect::new(Point2D::new(20, 20), Size2D::new(5, 5)); + let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15)); + let s = Rect::new(Point2D::new(20, -15), Size2D::new(250, 200)); + + let pq = p.union(&q); + assert!(pq.origin == Point2D::new(0, 0)); + assert!(pq.size == Size2D::new(50, 40)); + + let pr = p.union(&r); + assert!(pr.origin == Point2D::new(-15, -30)); + assert!(pr.size == Size2D::new(200, 70)); + + let ps = p.union(&s); + assert!(ps.origin == Point2D::new(0, -15)); + assert!(ps.size == Size2D::new(270, 200)); + } + + #[test] + fn test_intersection() { + let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20)); + let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10)); + let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8)); + + let pq = p.intersection(&q); + assert!(pq.is_some()); + let pq = pq.unwrap(); + assert!(pq.origin == Point2D::new(5, 15)); + assert!(pq.size == Size2D::new(5, 5)); + + let pr = p.intersection(&r); + assert!(pr.is_some()); + let pr = pr.unwrap(); + assert!(pr.origin == Point2D::new(0, 0)); + assert!(pr.size == Size2D::new(3, 3)); + + let qr = q.intersection(&r); + assert!(qr.is_none()); + } + + #[test] + fn test_intersection_overflow() { + // test some scenarios where the intersection can overflow but + // the min_x() and max_x() don't. Gecko currently fails these cases + let p = Rect::new(Point2D::new(-2147483648, -2147483648), Size2D::new(0, 0)); + let q = Rect::new( + Point2D::new(2136893440, 2136893440), + Size2D::new(279552, 279552), + ); + let r = Rect::new(Point2D::new(-2147483648, -2147483648), Size2D::new(1, 1)); + + assert!(p.is_empty()); + let pq = p.intersection(&q); + assert!(pq.is_none()); + + let qr = q.intersection(&r); + assert!(qr.is_none()); + } + + #[test] + fn test_contains() { + let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200)); + + assert!(r.contains(Point2D::new(0, 50))); + assert!(r.contains(Point2D::new(-10, 200))); + + // The `contains` method is inclusive of the top/left edges, but not the + // bottom/right edges. + assert!(r.contains(Point2D::new(-20, 15))); + assert!(!r.contains(Point2D::new(80, 15))); + assert!(!r.contains(Point2D::new(80, 215))); + assert!(!r.contains(Point2D::new(-20, 215))); + + // Points beyond the top-left corner. + assert!(!r.contains(Point2D::new(-25, 15))); + assert!(!r.contains(Point2D::new(-15, 10))); + + // Points beyond the top-right corner. + assert!(!r.contains(Point2D::new(85, 20))); + assert!(!r.contains(Point2D::new(75, 10))); + + // Points beyond the bottom-right corner. + assert!(!r.contains(Point2D::new(85, 210))); + assert!(!r.contains(Point2D::new(75, 220))); + + // Points beyond the bottom-left corner. + assert!(!r.contains(Point2D::new(-25, 210))); + assert!(!r.contains(Point2D::new(-15, 220))); + + let r = Rect::new(Point2D::new(-20.0, 15.0), Size2D::new(100.0, 200.0)); + assert!(r.contains_rect(&r)); + assert!(!r.contains_rect(&r.translate(vec2(0.1, 0.0)))); + assert!(!r.contains_rect(&r.translate(vec2(-0.1, 0.0)))); + assert!(!r.contains_rect(&r.translate(vec2(0.0, 0.1)))); + assert!(!r.contains_rect(&r.translate(vec2(0.0, -0.1)))); + // Empty rectangles are always considered as contained in other rectangles, + // even if their origin is not. + let p = Point2D::new(1.0, 1.0); + assert!(!r.contains(p)); + assert!(r.contains_rect(&Rect::new(p, Size2D::zero()))); + } + + #[test] + fn test_scale() { + let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32)); + let pp = p.scale(10, 15); + + assert!(pp.size.width == 500); + assert!(pp.size.height == 600); + assert!(pp.origin.x == 0); + assert!(pp.origin.y == 0); + + let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); + let rr = r.scale(1, 20); + + assert!(rr.size.width == 50); + assert!(rr.size.height == 800); + assert!(rr.origin.x == -10); + assert!(rr.origin.y == -100); + } + + #[test] + fn test_inflate() { + let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 10)); + let pp = p.inflate(10, 20); + + assert!(pp.size.width == 30); + assert!(pp.size.height == 50); + assert!(pp.origin.x == -10); + assert!(pp.origin.y == -20); + + let r = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20)); + let rr = r.inflate(-2, -5); + + assert!(rr.size.width == 6); + assert!(rr.size.height == 10); + assert!(rr.origin.x == 2); + assert!(rr.origin.y == 5); + } + + #[test] + fn test_inner_outer_rect() { + let inner_rect = Rect::new(point2(20, 40), size2(80, 100)); + let offsets = SideOffsets2D::new(20, 10, 10, 10); + let outer_rect = inner_rect.outer_rect(offsets); + assert_eq!(outer_rect.origin.x, 10); + assert_eq!(outer_rect.origin.y, 20); + assert_eq!(outer_rect.size.width, 100); + assert_eq!(outer_rect.size.height, 130); + assert_eq!(outer_rect.inner_rect(offsets), inner_rect); + } + + #[test] + fn test_min_max_x_y() { + let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32)); + assert!(p.max_y() == 40); + assert!(p.min_y() == 0); + assert!(p.max_x() == 50); + assert!(p.min_x() == 0); + + let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); + assert!(r.max_y() == 35); + assert!(r.min_y() == -5); + assert!(r.max_x() == 40); + assert!(r.min_x() == -10); + } + + #[test] + fn test_width_height() { + let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); + assert!(r.width() == 50); + assert!(r.height() == 40); + } + + #[test] + fn test_is_empty() { + assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 0u32)).is_empty()); + assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(10u32, 0u32)).is_empty()); + assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 10u32)).is_empty()); + assert!(!Rect::new(Point2D::new(0u32, 0u32), Size2D::new(1u32, 1u32)).is_empty()); + assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 0u32)).is_empty()); + assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(10u32, 0u32)).is_empty()); + assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 10u32)).is_empty()); + assert!(!Rect::new(Point2D::new(10u32, 10u32), Size2D::new(1u32, 1u32)).is_empty()); + } + + #[test] + fn test_round() { + let mut x = -2.0; + let mut y = -2.0; + let mut w = -2.0; + let mut h = -2.0; + while x < 2.0 { + while y < 2.0 { + while w < 2.0 { + while h < 2.0 { + let rect = Rect::new(Point2D::new(x, y), Size2D::new(w, h)); + + assert!(rect.contains_rect(&rect.round_in())); + assert!(rect.round_in().inflate(1.0, 1.0).contains_rect(&rect)); + + assert!(rect.round_out().contains_rect(&rect)); + assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round_out())); + + assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round())); + assert!(rect.round().inflate(1.0, 1.0).contains_rect(&rect)); + + h += 0.1; + } + w += 0.1; + } + y += 0.1; + } + x += 0.1 + } + } + + #[test] + fn test_center() { + let r: Rect<i32> = rect(-2, 5, 4, 10); + assert_eq!(r.center(), point2(0, 10)); + + let r: Rect<f32> = rect(1.0, 2.0, 3.0, 4.0); + assert_eq!(r.center(), point2(2.5, 4.0)); + } + + #[test] + fn test_nan() { + let r1: Rect<f32> = rect(-2.0, 5.0, 4.0, std::f32::NAN); + let r2: Rect<f32> = rect(std::f32::NAN, -1.0, 3.0, 10.0); + + assert_eq!(r1.intersection(&r2), None); + } +} 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()))); + } +} diff --git a/third_party/rust/euclid/src/rotation.rs b/third_party/rust/euclid/src/rotation.rs new file mode 100644 index 0000000000..474984c007 --- /dev/null +++ b/third_party/rust/euclid/src/rotation.rs @@ -0,0 +1,1014 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::approxeq::ApproxEq; +use crate::trig::Trig; +use crate::{point2, point3, vec3, Angle, Point2D, Point3D, Vector2D, Vector3D}; +use crate::{Transform2D, Transform3D, UnknownUnit}; +use core::cmp::{Eq, PartialEq}; +use core::fmt; +use core::hash::Hash; +use core::marker::PhantomData; +use core::ops::{Add, Mul, Neg, Sub}; +use num_traits::real::Real; +use num_traits::{NumCast, One, Zero}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A transform that can represent rotations in 2d, represented as an angle in radians. +#[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 Rotation2D<T, Src, Dst> { + /// Angle in radians + pub angle: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +impl<T: Copy, Src, Dst> Copy for Rotation2D<T, Src, Dst> {} + +impl<T: Clone, Src, Dst> Clone for Rotation2D<T, Src, Dst> { + fn clone(&self) -> Self { + Rotation2D { + angle: self.angle.clone(), + _unit: PhantomData, + } + } +} + +impl<T, Src, Dst> Eq for Rotation2D<T, Src, Dst> where T: Eq {} + +impl<T, Src, Dst> PartialEq for Rotation2D<T, Src, Dst> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.angle == other.angle + } +} + +impl<T, Src, Dst> Hash for Rotation2D<T, Src, Dst> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.angle.hash(h); + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, Src, Dst> Zeroable for Rotation2D<T, Src, Dst> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Rotation2D<T, Src, Dst> {} + +impl<T, Src, Dst> Rotation2D<T, Src, Dst> { + /// Creates a rotation from an angle in radians. + #[inline] + pub fn new(angle: Angle<T>) -> Self { + Rotation2D { + angle: angle.radians, + _unit: PhantomData, + } + } + + /// Creates a rotation from an angle in radians. + pub fn radians(angle: T) -> Self { + Self::new(Angle::radians(angle)) + } + + /// Creates the identity rotation. + #[inline] + pub fn identity() -> Self + where + T: Zero, + { + Self::radians(T::zero()) + } +} + +impl<T: Copy, Src, Dst> Rotation2D<T, Src, Dst> { + /// Cast the unit, preserving the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use euclid::Rotation2D; + /// enum Local {} + /// enum World {} + /// + /// enum Local2 {} + /// enum World2 {} + /// + /// let to_world: Rotation2D<_, Local, World> = Rotation2D::radians(42); + /// + /// assert_eq!(to_world.angle, to_world.cast_unit::<Local2, World2>().angle); + /// ``` + #[inline] + pub fn cast_unit<Src2, Dst2>(&self) -> Rotation2D<T, Src2, Dst2> { + Rotation2D { + angle: self.angle, + _unit: PhantomData, + } + } + + /// Drop the units, preserving only the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use euclid::Rotation2D; + /// enum Local {} + /// enum World {} + /// + /// let to_world: Rotation2D<_, Local, World> = Rotation2D::radians(42); + /// + /// assert_eq!(to_world.angle, to_world.to_untyped().angle); + /// ``` + #[inline] + pub fn to_untyped(&self) -> Rotation2D<T, UnknownUnit, UnknownUnit> { + self.cast_unit() + } + + /// Tag a unitless value with units. + /// + /// # Example + /// + /// ```rust + /// # use euclid::Rotation2D; + /// use euclid::UnknownUnit; + /// enum Local {} + /// enum World {} + /// + /// let rot: Rotation2D<_, UnknownUnit, UnknownUnit> = Rotation2D::radians(42); + /// + /// assert_eq!(rot.angle, Rotation2D::<_, Local, World>::from_untyped(&rot).angle); + /// ``` + #[inline] + pub fn from_untyped(r: &Rotation2D<T, UnknownUnit, UnknownUnit>) -> Self { + r.cast_unit() + } +} + +impl<T, Src, Dst> Rotation2D<T, Src, Dst> +where + T: Copy, +{ + /// Returns self.angle as a strongly typed `Angle<T>`. + pub fn get_angle(&self) -> Angle<T> { + Angle::radians(self.angle) + } +} + +impl<T: Real, Src, Dst> Rotation2D<T, Src, Dst> { + /// Creates a 3d rotation (around the z axis) from this 2d rotation. + #[inline] + pub fn to_3d(&self) -> Rotation3D<T, Src, Dst> { + Rotation3D::around_z(self.get_angle()) + } + + /// Returns the inverse of this rotation. + #[inline] + pub fn inverse(&self) -> Rotation2D<T, Dst, Src> { + Rotation2D::radians(-self.angle) + } + + /// Returns a rotation representing the other rotation followed by this rotation. + #[inline] + pub fn then<NewSrc>( + &self, + other: &Rotation2D<T, NewSrc, Src>, + ) -> Rotation2D<T, NewSrc, Dst> { + Rotation2D::radians(self.angle + other.angle) + } + + /// Returns the given 2d point transformed by this rotation. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T, Dst> { + let (sin, cos) = Real::sin_cos(self.angle); + point2(point.x * cos - point.y * sin, point.y * cos + point.x * sin) + } + + /// Returns the given 2d vector transformed by this rotation. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_vector(&self, vector: Vector2D<T, Src>) -> Vector2D<T, Dst> { + self.transform_point(vector.to_point()).to_vector() + } +} + +impl<T, Src, Dst> Rotation2D<T, Src, Dst> +where + T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Zero + Trig, +{ + /// Returns the matrix representation of this rotation. + #[inline] + pub fn to_transform(&self) -> Transform2D<T, Src, Dst> { + Transform2D::rotation(self.get_angle()) + } +} + +/// A transform that can represent rotations in 3d, represented as a quaternion. +/// +/// Most methods expect the quaternion to be normalized. +/// When in doubt, use `unit_quaternion` instead of `quaternion` to create +/// a rotation as the former will ensure that its result is normalized. +/// +/// Some people use the `x, y, z, w` (or `w, x, y, z`) notations. The equivalence is +/// as follows: `x -> i`, `y -> j`, `z -> k`, `w -> r`. +/// The memory layout of this type corresponds to the `x, y, z, w` notation +#[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 Rotation3D<T, Src, Dst> { + /// Component multiplied by the imaginary number `i`. + pub i: T, + /// Component multiplied by the imaginary number `j`. + pub j: T, + /// Component multiplied by the imaginary number `k`. + pub k: T, + /// The real part. + pub r: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +impl<T: Copy, Src, Dst> Copy for Rotation3D<T, Src, Dst> {} + +impl<T: Clone, Src, Dst> Clone for Rotation3D<T, Src, Dst> { + fn clone(&self) -> Self { + Rotation3D { + i: self.i.clone(), + j: self.j.clone(), + k: self.k.clone(), + r: self.r.clone(), + _unit: PhantomData, + } + } +} + +impl<T, Src, Dst> Eq for Rotation3D<T, Src, Dst> where T: Eq {} + +impl<T, Src, Dst> PartialEq for Rotation3D<T, Src, Dst> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.i == other.i && self.j == other.j && self.k == other.k && self.r == other.r + } +} + +impl<T, Src, Dst> Hash for Rotation3D<T, Src, Dst> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.i.hash(h); + self.j.hash(h); + self.k.hash(h); + self.r.hash(h); + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, Src, Dst> Zeroable for Rotation3D<T, Src, Dst> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Rotation3D<T, Src, Dst> {} + +impl<T, Src, Dst> Rotation3D<T, Src, Dst> { + /// Creates a rotation around from a quaternion representation. + /// + /// The parameters are a, b, c and r compose the quaternion `a*i + b*j + c*k + r` + /// where `a`, `b` and `c` describe the vector part and the last parameter `r` is + /// the real part. + /// + /// The resulting quaternion is not necessarily normalized. See [`unit_quaternion`]. + /// + /// [`unit_quaternion`]: #method.unit_quaternion + #[inline] + pub fn quaternion(a: T, b: T, c: T, r: T) -> Self { + Rotation3D { + i: a, + j: b, + k: c, + r, + _unit: PhantomData, + } + } + + /// Creates the identity rotation. + #[inline] + pub fn identity() -> Self + where + T: Zero + One, + { + Self::quaternion(T::zero(), T::zero(), T::zero(), T::one()) + } +} + +impl<T, Src, Dst> Rotation3D<T, Src, Dst> +where + T: Copy, +{ + /// Returns the vector part (i, j, k) of this quaternion. + #[inline] + pub fn vector_part(&self) -> Vector3D<T, UnknownUnit> { + vec3(self.i, self.j, self.k) + } + + /// Cast the unit, preserving the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use euclid::Rotation3D; + /// enum Local {} + /// enum World {} + /// + /// enum Local2 {} + /// enum World2 {} + /// + /// let to_world: Rotation3D<_, Local, World> = Rotation3D::quaternion(1, 2, 3, 4); + /// + /// assert_eq!(to_world.i, to_world.cast_unit::<Local2, World2>().i); + /// assert_eq!(to_world.j, to_world.cast_unit::<Local2, World2>().j); + /// assert_eq!(to_world.k, to_world.cast_unit::<Local2, World2>().k); + /// assert_eq!(to_world.r, to_world.cast_unit::<Local2, World2>().r); + /// ``` + #[inline] + pub fn cast_unit<Src2, Dst2>(&self) -> Rotation3D<T, Src2, Dst2> { + Rotation3D { + i: self.i, + j: self.j, + k: self.k, + r: self.r, + _unit: PhantomData, + } + } + + /// Drop the units, preserving only the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use euclid::Rotation3D; + /// enum Local {} + /// enum World {} + /// + /// let to_world: Rotation3D<_, Local, World> = Rotation3D::quaternion(1, 2, 3, 4); + /// + /// assert_eq!(to_world.i, to_world.to_untyped().i); + /// assert_eq!(to_world.j, to_world.to_untyped().j); + /// assert_eq!(to_world.k, to_world.to_untyped().k); + /// assert_eq!(to_world.r, to_world.to_untyped().r); + /// ``` + #[inline] + pub fn to_untyped(&self) -> Rotation3D<T, UnknownUnit, UnknownUnit> { + self.cast_unit() + } + + /// Tag a unitless value with units. + /// + /// # Example + /// + /// ```rust + /// # use euclid::Rotation3D; + /// use euclid::UnknownUnit; + /// enum Local {} + /// enum World {} + /// + /// let rot: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::quaternion(1, 2, 3, 4); + /// + /// assert_eq!(rot.i, Rotation3D::<_, Local, World>::from_untyped(&rot).i); + /// assert_eq!(rot.j, Rotation3D::<_, Local, World>::from_untyped(&rot).j); + /// assert_eq!(rot.k, Rotation3D::<_, Local, World>::from_untyped(&rot).k); + /// assert_eq!(rot.r, Rotation3D::<_, Local, World>::from_untyped(&rot).r); + /// ``` + #[inline] + pub fn from_untyped(r: &Rotation3D<T, UnknownUnit, UnknownUnit>) -> Self { + r.cast_unit() + } +} + +impl<T, Src, Dst> Rotation3D<T, Src, Dst> +where + T: Real, +{ + /// Creates a rotation around from a quaternion representation and normalizes it. + /// + /// The parameters are a, b, c and r compose the quaternion `a*i + b*j + c*k + r` + /// before normalization, where `a`, `b` and `c` describe the vector part and the + /// last parameter `r` is the real part. + #[inline] + pub fn unit_quaternion(i: T, j: T, k: T, r: T) -> Self { + Self::quaternion(i, j, k, r).normalize() + } + + /// Creates a rotation around a given axis. + pub fn around_axis(axis: Vector3D<T, Src>, angle: Angle<T>) -> Self { + let axis = axis.normalize(); + let two = T::one() + T::one(); + let (sin, cos) = Angle::sin_cos(angle / two); + Self::quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos) + } + + /// Creates a rotation around the x axis. + pub fn around_x(angle: Angle<T>) -> Self { + let zero = Zero::zero(); + let two = T::one() + T::one(); + let (sin, cos) = Angle::sin_cos(angle / two); + Self::quaternion(sin, zero, zero, cos) + } + + /// Creates a rotation around the y axis. + pub fn around_y(angle: Angle<T>) -> Self { + let zero = Zero::zero(); + let two = T::one() + T::one(); + let (sin, cos) = Angle::sin_cos(angle / two); + Self::quaternion(zero, sin, zero, cos) + } + + /// Creates a rotation around the z axis. + pub fn around_z(angle: Angle<T>) -> Self { + let zero = Zero::zero(); + let two = T::one() + T::one(); + let (sin, cos) = Angle::sin_cos(angle / two); + Self::quaternion(zero, zero, sin, cos) + } + + /// Creates a rotation from Euler angles. + /// + /// The rotations are applied in roll then pitch then yaw order. + /// + /// - Roll (also called bank) is a rotation around the x axis. + /// - Pitch (also called bearing) is a rotation around the y axis. + /// - Yaw (also called heading) is a rotation around the z axis. + pub fn euler(roll: Angle<T>, pitch: Angle<T>, yaw: Angle<T>) -> Self { + let half = T::one() / (T::one() + T::one()); + + let (sy, cy) = Real::sin_cos(half * yaw.get()); + let (sp, cp) = Real::sin_cos(half * pitch.get()); + let (sr, cr) = Real::sin_cos(half * roll.get()); + + Self::quaternion( + cy * sr * cp - sy * cr * sp, + cy * cr * sp + sy * sr * cp, + sy * cr * cp - cy * sr * sp, + cy * cr * cp + sy * sr * sp, + ) + } + + /// Returns the inverse of this rotation. + #[inline] + pub fn inverse(&self) -> Rotation3D<T, Dst, Src> { + Rotation3D::quaternion(-self.i, -self.j, -self.k, self.r) + } + + /// Computes the norm of this quaternion. + #[inline] + pub fn norm(&self) -> T { + self.square_norm().sqrt() + } + + /// Computes the squared norm of this quaternion. + #[inline] + pub fn square_norm(&self) -> T { + self.i * self.i + self.j * self.j + self.k * self.k + self.r * self.r + } + + /// Returns a [unit quaternion] from this one. + /// + /// [unit quaternion]: https://en.wikipedia.org/wiki/Quaternion#Unit_quaternion + #[inline] + pub fn normalize(&self) -> Self { + self.mul(T::one() / self.norm()) + } + + /// Returns `true` if [norm] of this quaternion is (approximately) one. + /// + /// [norm]: #method.norm + #[inline] + pub fn is_normalized(&self) -> bool + where + T: ApproxEq<T>, + { + let eps = NumCast::from(1.0e-5).unwrap(); + self.square_norm().approx_eq_eps(&T::one(), &eps) + } + + /// Spherical linear interpolation between this rotation and another rotation. + /// + /// `t` is expected to be between zero and one. + pub fn slerp(&self, other: &Self, t: T) -> Self + where + T: ApproxEq<T>, + { + debug_assert!(self.is_normalized()); + debug_assert!(other.is_normalized()); + + let r1 = *self; + let mut r2 = *other; + + let mut dot = r1.i * r2.i + r1.j * r2.j + r1.k * r2.k + r1.r * r2.r; + + let one = T::one(); + + if dot.approx_eq(&T::one()) { + // If the inputs are too close, linearly interpolate to avoid precision issues. + return r1.lerp(&r2, t); + } + + // If the dot product is negative, the quaternions + // have opposite handed-ness and slerp won't take + // the shorter path. Fix by reversing one quaternion. + if dot < T::zero() { + r2 = r2.mul(-T::one()); + dot = -dot; + } + + // For robustness, stay within the domain of acos. + dot = Real::min(dot, one); + + // Angle between r1 and the result. + let theta = Real::acos(dot) * t; + + // r1 and r3 form an orthonormal basis. + let r3 = r2.sub(r1.mul(dot)).normalize(); + let (sin, cos) = Real::sin_cos(theta); + r1.mul(cos).add(r3.mul(sin)) + } + + /// Basic Linear interpolation between this rotation and another rotation. + #[inline] + pub fn lerp(&self, other: &Self, t: T) -> Self { + let one_t = T::one() - t; + self.mul(one_t).add(other.mul(t)).normalize() + } + + /// Returns the given 3d point transformed by this rotation. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + pub fn transform_point3d(&self, point: Point3D<T, Src>) -> Point3D<T, Dst> + where + T: ApproxEq<T>, + { + debug_assert!(self.is_normalized()); + + let two = T::one() + T::one(); + let cross = self.vector_part().cross(point.to_vector().to_untyped()) * two; + + point3( + point.x + self.r * cross.x + self.j * cross.z - self.k * cross.y, + point.y + self.r * cross.y + self.k * cross.x - self.i * cross.z, + point.z + self.r * cross.z + self.i * cross.y - self.j * cross.x, + ) + } + + /// Returns the given 2d point transformed by this rotation then projected on the xy plane. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_point2d(&self, point: Point2D<T, Src>) -> Point2D<T, Dst> + where + T: ApproxEq<T>, + { + self.transform_point3d(point.to_3d()).xy() + } + + /// Returns the given 3d vector transformed by this rotation. + /// + /// The input vector must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_vector3d(&self, vector: Vector3D<T, Src>) -> Vector3D<T, Dst> + where + T: ApproxEq<T>, + { + self.transform_point3d(vector.to_point()).to_vector() + } + + /// Returns the given 2d vector transformed by this rotation then projected on the xy plane. + /// + /// The input vector must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_vector2d(&self, vector: Vector2D<T, Src>) -> Vector2D<T, Dst> + where + T: ApproxEq<T>, + { + self.transform_vector3d(vector.to_3d()).xy() + } + + /// Returns the matrix representation of this rotation. + #[inline] + pub fn to_transform(&self) -> Transform3D<T, Src, Dst> + where + T: ApproxEq<T>, + { + debug_assert!(self.is_normalized()); + + let i2 = self.i + self.i; + let j2 = self.j + self.j; + let k2 = self.k + self.k; + let ii = self.i * i2; + let ij = self.i * j2; + let ik = self.i * k2; + let jj = self.j * j2; + let jk = self.j * k2; + let kk = self.k * k2; + let ri = self.r * i2; + let rj = self.r * j2; + let rk = self.r * k2; + + let one = T::one(); + let zero = T::zero(); + + let m11 = one - (jj + kk); + let m12 = ij + rk; + let m13 = ik - rj; + + let m21 = ij - rk; + let m22 = one - (ii + kk); + let m23 = jk + ri; + + let m31 = ik + rj; + let m32 = jk - ri; + let m33 = one - (ii + jj); + + Transform3D::new( + m11, m12, m13, zero, + m21, m22, m23, zero, + m31, m32, m33, zero, + zero, zero, zero, one, + ) + } + + /// Returns a rotation representing this rotation followed by the other rotation. + #[inline] + pub fn then<NewDst>( + &self, + other: &Rotation3D<T, Dst, NewDst>, + ) -> Rotation3D<T, Src, NewDst> + where + T: ApproxEq<T>, + { + debug_assert!(self.is_normalized()); + Rotation3D::quaternion( + other.i * self.r + other.r * self.i + other.j * self.k - other.k * self.j, + other.j * self.r + other.r * self.j + other.k * self.i - other.i * self.k, + other.k * self.r + other.r * self.k + other.i * self.j - other.j * self.i, + other.r * self.r - other.i * self.i - other.j * self.j - other.k * self.k, + ) + } + + // add, sub and mul are used internally for intermediate computation but aren't public + // because they don't carry real semantic meanings (I think?). + + #[inline] + fn add(&self, other: Self) -> Self { + Self::quaternion( + self.i + other.i, + self.j + other.j, + self.k + other.k, + self.r + other.r, + ) + } + + #[inline] + fn sub(&self, other: Self) -> Self { + Self::quaternion( + self.i - other.i, + self.j - other.j, + self.k - other.k, + self.r - other.r, + ) + } + + #[inline] + fn mul(&self, factor: T) -> Self { + Self::quaternion( + self.i * factor, + self.j * factor, + self.k * factor, + self.r * factor, + ) + } +} + +impl<T: fmt::Debug, Src, Dst> fmt::Debug for Rotation3D<T, Src, Dst> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Quat({:?}*i + {:?}*j + {:?}*k + {:?})", + self.i, self.j, self.k, self.r + ) + } +} + +impl<T, Src, Dst> ApproxEq<T> for Rotation3D<T, Src, Dst> +where + T: Copy + Neg<Output = T> + ApproxEq<T>, +{ + fn approx_epsilon() -> T { + T::approx_epsilon() + } + + fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool { + (self.i.approx_eq_eps(&other.i, eps) + && self.j.approx_eq_eps(&other.j, eps) + && self.k.approx_eq_eps(&other.k, eps) + && self.r.approx_eq_eps(&other.r, eps)) + || (self.i.approx_eq_eps(&-other.i, eps) + && self.j.approx_eq_eps(&-other.j, eps) + && self.k.approx_eq_eps(&-other.k, eps) + && self.r.approx_eq_eps(&-other.r, eps)) + } +} + +#[test] +fn simple_rotation_2d() { + use crate::default::Rotation2D; + use core::f32::consts::{FRAC_PI_2, PI}; + + let ri = Rotation2D::identity(); + let r90 = Rotation2D::radians(FRAC_PI_2); + let rm90 = Rotation2D::radians(-FRAC_PI_2); + let r180 = Rotation2D::radians(PI); + + assert!(ri + .transform_point(point2(1.0, 2.0)) + .approx_eq(&point2(1.0, 2.0))); + assert!(r90 + .transform_point(point2(1.0, 2.0)) + .approx_eq(&point2(-2.0, 1.0))); + assert!(rm90 + .transform_point(point2(1.0, 2.0)) + .approx_eq(&point2(2.0, -1.0))); + assert!(r180 + .transform_point(point2(1.0, 2.0)) + .approx_eq(&point2(-1.0, -2.0))); + + assert!(r90 + .inverse() + .inverse() + .transform_point(point2(1.0, 2.0)) + .approx_eq(&r90.transform_point(point2(1.0, 2.0)))); +} + +#[test] +fn simple_rotation_3d_in_2d() { + use crate::default::Rotation3D; + use core::f32::consts::{FRAC_PI_2, PI}; + + let ri = Rotation3D::identity(); + let r90 = Rotation3D::around_z(Angle::radians(FRAC_PI_2)); + let rm90 = Rotation3D::around_z(Angle::radians(-FRAC_PI_2)); + let r180 = Rotation3D::around_z(Angle::radians(PI)); + + assert!(ri + .transform_point2d(point2(1.0, 2.0)) + .approx_eq(&point2(1.0, 2.0))); + assert!(r90 + .transform_point2d(point2(1.0, 2.0)) + .approx_eq(&point2(-2.0, 1.0))); + assert!(rm90 + .transform_point2d(point2(1.0, 2.0)) + .approx_eq(&point2(2.0, -1.0))); + assert!(r180 + .transform_point2d(point2(1.0, 2.0)) + .approx_eq(&point2(-1.0, -2.0))); + + assert!(r90 + .inverse() + .inverse() + .transform_point2d(point2(1.0, 2.0)) + .approx_eq(&r90.transform_point2d(point2(1.0, 2.0)))); +} + +#[test] +fn pre_post() { + use crate::default::Rotation3D; + use core::f32::consts::FRAC_PI_2; + + let r1 = Rotation3D::around_x(Angle::radians(FRAC_PI_2)); + let r2 = Rotation3D::around_y(Angle::radians(FRAC_PI_2)); + let r3 = Rotation3D::around_z(Angle::radians(FRAC_PI_2)); + + let t1 = r1.to_transform(); + let t2 = r2.to_transform(); + let t3 = r3.to_transform(); + + let p = point3(1.0, 2.0, 3.0); + + // Check that the order of transformations is correct (corresponds to what + // we do in Transform3D). + let p1 = r1.then(&r2).then(&r3).transform_point3d(p); + let p2 = t1 + .then(&t2) + .then(&t3) + .transform_point3d(p); + + assert!(p1.approx_eq(&p2.unwrap())); + + // Check that changing the order indeed matters. + let p3 = t3 + .then(&t1) + .then(&t2) + .transform_point3d(p); + assert!(!p1.approx_eq(&p3.unwrap())); +} + +#[test] +fn to_transform3d() { + use crate::default::Rotation3D; + + use core::f32::consts::{FRAC_PI_2, PI}; + let rotations = [ + Rotation3D::identity(), + Rotation3D::around_x(Angle::radians(FRAC_PI_2)), + Rotation3D::around_x(Angle::radians(-FRAC_PI_2)), + Rotation3D::around_x(Angle::radians(PI)), + Rotation3D::around_y(Angle::radians(FRAC_PI_2)), + Rotation3D::around_y(Angle::radians(-FRAC_PI_2)), + Rotation3D::around_y(Angle::radians(PI)), + Rotation3D::around_z(Angle::radians(FRAC_PI_2)), + Rotation3D::around_z(Angle::radians(-FRAC_PI_2)), + Rotation3D::around_z(Angle::radians(PI)), + ]; + + let points = [ + point3(0.0, 0.0, 0.0), + point3(1.0, 2.0, 3.0), + point3(-5.0, 3.0, -1.0), + point3(-0.5, -1.0, 1.5), + ]; + + for rotation in &rotations { + for &point in &points { + let p1 = rotation.transform_point3d(point); + let p2 = rotation.to_transform().transform_point3d(point); + assert!(p1.approx_eq(&p2.unwrap())); + } + } +} + +#[test] +fn slerp() { + use crate::default::Rotation3D; + + let q1 = Rotation3D::quaternion(1.0, 0.0, 0.0, 0.0); + let q2 = Rotation3D::quaternion(0.0, 1.0, 0.0, 0.0); + let q3 = Rotation3D::quaternion(0.0, 0.0, -1.0, 0.0); + + // The values below can be obtained with a python program: + // import numpy + // import quaternion + // q1 = numpy.quaternion(1, 0, 0, 0) + // q2 = numpy.quaternion(0, 1, 0, 0) + // quaternion.slerp_evaluate(q1, q2, 0.2) + + assert!(q1.slerp(&q2, 0.0).approx_eq(&q1)); + assert!(q1.slerp(&q2, 0.2).approx_eq(&Rotation3D::quaternion( + 0.951056516295154, + 0.309016994374947, + 0.0, + 0.0 + ))); + assert!(q1.slerp(&q2, 0.4).approx_eq(&Rotation3D::quaternion( + 0.809016994374947, + 0.587785252292473, + 0.0, + 0.0 + ))); + assert!(q1.slerp(&q2, 0.6).approx_eq(&Rotation3D::quaternion( + 0.587785252292473, + 0.809016994374947, + 0.0, + 0.0 + ))); + assert!(q1.slerp(&q2, 0.8).approx_eq(&Rotation3D::quaternion( + 0.309016994374947, + 0.951056516295154, + 0.0, + 0.0 + ))); + assert!(q1.slerp(&q2, 1.0).approx_eq(&q2)); + + assert!(q1.slerp(&q3, 0.0).approx_eq(&q1)); + assert!(q1.slerp(&q3, 0.2).approx_eq(&Rotation3D::quaternion( + 0.951056516295154, + 0.0, + -0.309016994374947, + 0.0 + ))); + assert!(q1.slerp(&q3, 0.4).approx_eq(&Rotation3D::quaternion( + 0.809016994374947, + 0.0, + -0.587785252292473, + 0.0 + ))); + assert!(q1.slerp(&q3, 0.6).approx_eq(&Rotation3D::quaternion( + 0.587785252292473, + 0.0, + -0.809016994374947, + 0.0 + ))); + assert!(q1.slerp(&q3, 0.8).approx_eq(&Rotation3D::quaternion( + 0.309016994374947, + 0.0, + -0.951056516295154, + 0.0 + ))); + assert!(q1.slerp(&q3, 1.0).approx_eq(&q3)); +} + +#[test] +fn around_axis() { + use crate::default::Rotation3D; + use core::f32::consts::{FRAC_PI_2, PI}; + + // Two sort of trivial cases: + let r1 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(PI)); + let r2 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(FRAC_PI_2)); + assert!(r1 + .transform_point3d(point3(1.0, 2.0, 0.0)) + .approx_eq(&point3(2.0, 1.0, 0.0))); + assert!(r2 + .transform_point3d(point3(1.0, 0.0, 0.0)) + .approx_eq(&point3(0.5, 0.5, -0.5.sqrt()))); + + // A more arbitrary test (made up with numpy): + let r3 = Rotation3D::around_axis(vec3(0.5, 1.0, 2.0), Angle::radians(2.291288)); + assert!(r3 + .transform_point3d(point3(1.0, 0.0, 0.0)) + .approx_eq(&point3(-0.58071821, 0.81401868, -0.01182979))); +} + +#[test] +fn from_euler() { + use crate::default::Rotation3D; + use core::f32::consts::FRAC_PI_2; + + // First test simple separate yaw pitch and roll rotations, because it is easy to come + // up with the corresponding quaternion. + // Since several quaternions can represent the same transformation we compare the result + // of transforming a point rather than the values of each quaternions. + let p = point3(1.0, 2.0, 3.0); + + let angle = Angle::radians(FRAC_PI_2); + let zero = Angle::radians(0.0); + + // roll + let roll_re = Rotation3D::euler(angle, zero, zero); + let roll_rq = Rotation3D::around_x(angle); + let roll_pe = roll_re.transform_point3d(p); + let roll_pq = roll_rq.transform_point3d(p); + + // pitch + let pitch_re = Rotation3D::euler(zero, angle, zero); + let pitch_rq = Rotation3D::around_y(angle); + let pitch_pe = pitch_re.transform_point3d(p); + let pitch_pq = pitch_rq.transform_point3d(p); + + // yaw + let yaw_re = Rotation3D::euler(zero, zero, angle); + let yaw_rq = Rotation3D::around_z(angle); + let yaw_pe = yaw_re.transform_point3d(p); + let yaw_pq = yaw_rq.transform_point3d(p); + + assert!(roll_pe.approx_eq(&roll_pq)); + assert!(pitch_pe.approx_eq(&pitch_pq)); + assert!(yaw_pe.approx_eq(&yaw_pq)); + + // Now check that the yaw pitch and roll transformations when combined are applied in + // the proper order: roll -> pitch -> yaw. + let ypr_e = Rotation3D::euler(angle, angle, angle); + let ypr_q = roll_rq.then(&pitch_rq).then(&yaw_rq); + let ypr_pe = ypr_e.transform_point3d(p); + let ypr_pq = ypr_q.transform_point3d(p); + + assert!(ypr_pe.approx_eq(&ypr_pq)); +} diff --git a/third_party/rust/euclid/src/scale.rs b/third_party/rust/euclid/src/scale.rs new file mode 100644 index 0000000000..eac377a7d0 --- /dev/null +++ b/third_party/rust/euclid/src/scale.rs @@ -0,0 +1,428 @@ +// Copyright 2014 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +//! A type-checked scaling factor between units. + +use crate::num::One; + +use crate::{Point2D, Point3D, Rect, Size2D, Vector2D, Box2D, Box3D}; +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::marker::PhantomData; +use core::ops::{Add, Div, Mul, Sub}; +use num_traits::NumCast; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A scaling factor between two different units of measurement. +/// +/// This is effectively a type-safe float, intended to be used in combination with other types like +/// `length::Length` to enforce conversion between systems of measurement at compile time. +/// +/// `Src` and `Dst` represent the units before and after multiplying a value by a `Scale`. They +/// may be types without values, such as empty enums. For example: +/// +/// ```rust +/// use euclid::Scale; +/// use euclid::Length; +/// enum Mm {}; +/// enum Inch {}; +/// +/// let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4); +/// +/// let one_foot: Length<f32, Inch> = Length::new(12.0); +/// let one_foot_in_mm: Length<f32, Mm> = one_foot * mm_per_inch; +/// ``` +#[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 Scale<T, Src, Dst>(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>); + +impl<T, Src, Dst> Scale<T, Src, Dst> { + #[inline] + pub const fn new(x: T) -> Self { + Scale(x, PhantomData) + } + + /// Creates an identity scale (1.0). + #[inline] + pub fn identity() -> Self + where + T: One + { + Scale::new(T::one()) + } + + /// Returns the given point transformed by this scale. + /// + /// # Example + /// + /// ```rust + /// use euclid::{Scale, point2}; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10); + /// + /// assert_eq!(to_mm.transform_point(point2(42, -42)), point2(420, -420)); + /// ``` + #[inline] + pub fn transform_point(self, point: Point2D<T, Src>) -> Point2D<T::Output, Dst> + where + T: Copy + Mul, + { + Point2D::new(point.x * self.0, point.y * self.0) + } + + /// Returns the given point transformed by this scale. + #[inline] + pub fn transform_point3d(self, point: Point3D<T, Src>) -> Point3D<T::Output, Dst> + where + T: Copy + Mul, + { + Point3D::new(point.x * self.0, point.y * self.0, point.z * self.0) + } + + /// Returns the given vector transformed by this scale. + /// + /// # Example + /// + /// ```rust + /// use euclid::{Scale, vec2}; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10); + /// + /// assert_eq!(to_mm.transform_vector(vec2(42, -42)), vec2(420, -420)); + /// ``` + #[inline] + pub fn transform_vector(self, vec: Vector2D<T, Src>) -> Vector2D<T::Output, Dst> + where + T: Copy + Mul, + { + Vector2D::new(vec.x * self.0, vec.y * self.0) + } + + /// Returns the given vector transformed by this scale. + /// + /// # Example + /// + /// ```rust + /// use euclid::{Scale, size2}; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10); + /// + /// assert_eq!(to_mm.transform_size(size2(42, -42)), size2(420, -420)); + /// ``` + #[inline] + pub fn transform_size(self, size: Size2D<T, Src>) -> Size2D<T::Output, Dst> + where + T: Copy + Mul, + { + Size2D::new(size.width * self.0, size.height * self.0) + } + + /// Returns the given rect transformed by this scale. + /// + /// # Example + /// + /// ```rust + /// use euclid::{Scale, rect}; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10); + /// + /// assert_eq!(to_mm.transform_rect(&rect(1, 2, 42, -42)), rect(10, 20, 420, -420)); + /// ``` + #[inline] + pub fn transform_rect(self, rect: &Rect<T, Src>) -> Rect<T::Output, Dst> + where + T: Copy + Mul, + { + Rect::new( + self.transform_point(rect.origin), + self.transform_size(rect.size), + ) + } + + /// Returns the given box transformed by this scale. + #[inline] + pub fn transform_box2d(self, b: &Box2D<T, Src>) -> Box2D<T::Output, Dst> + where + T: Copy + Mul, + { + Box2D { + min: self.transform_point(b.min), + max: self.transform_point(b.max), + } + } + + /// Returns the given box transformed by this scale. + #[inline] + pub fn transform_box3d(self, b: &Box3D<T, Src>) -> Box3D<T::Output, Dst> + where + T: Copy + Mul, + { + Box3D { + min: self.transform_point3d(b.min), + max: self.transform_point3d(b.max), + } + } + + /// Returns `true` if this scale has no effect. + /// + /// # Example + /// + /// ```rust + /// use euclid::Scale; + /// use euclid::num::One; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + /// let mm_per_mm: Scale<f32, Mm, Mm> = Scale::new(1.0); + /// + /// assert_eq!(cm_per_mm.is_identity(), false); + /// assert_eq!(mm_per_mm.is_identity(), true); + /// assert_eq!(mm_per_mm, Scale::one()); + /// ``` + #[inline] + pub fn is_identity(self) -> bool + where + T: PartialEq + One, + { + self.0 == T::one() + } + + /// Returns the underlying scalar scale factor. + #[inline] + pub fn get(self) -> T { + self.0 + } + + /// The inverse Scale (1.0 / self). + /// + /// # Example + /// + /// ```rust + /// use euclid::Scale; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let cm_per_mm: Scale<f32, Cm, Mm> = Scale::new(0.1); + /// + /// assert_eq!(cm_per_mm.inverse(), Scale::new(10.0)); + /// ``` + pub fn inverse(self) -> Scale<T::Output, Dst, Src> + where + T: One + Div, + { + let one: T = One::one(); + Scale::new(one / self.0) + } +} + +impl<T: NumCast, Src, Dst> Scale<T, Src, Dst> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// # Panics + /// + /// If the source value cannot be represented by the target type `NewT`, then + /// method panics. Use `try_cast` if that must be case. + /// + /// # Example + /// + /// ```rust + /// use euclid::Scale; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10); + /// + /// assert_eq!(to_mm.cast::<f32>(), Scale::new(10.0)); + /// ``` + /// That conversion will panic, because `i32` not enough to store such big numbers: + /// ```rust,should_panic + /// use euclid::Scale; + /// enum Mm {};// millimeter = 10^-2 meters + /// enum Em {};// exameter = 10^18 meters + /// + /// // Panics + /// let to_em: Scale<i32, Mm, Em> = Scale::new(10e20).cast(); + /// ``` + #[inline] + pub fn cast<NewT: NumCast>(self) -> Scale<NewT, Src, Dst> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// If the source value cannot be represented by the target type `NewT`, then `None` + /// is returned. + /// + /// # Example + /// + /// ```rust + /// use euclid::Scale; + /// enum Mm {}; + /// enum Cm {}; + /// enum Em {};// Exameter = 10^18 meters + /// + /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10); + /// let to_em: Scale<f32, Mm, Em> = Scale::new(10e20); + /// + /// assert_eq!(to_mm.try_cast::<f32>(), Some(Scale::new(10.0))); + /// // Integer to small to store that number + /// assert_eq!(to_em.try_cast::<i32>(), None); + /// ``` + pub fn try_cast<NewT: NumCast>(self) -> Option<Scale<NewT, Src, Dst>> { + NumCast::from(self.0).map(Scale::new) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, Src, Dst> Zeroable for Scale<T, Src, Dst> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Scale<T, Src, Dst> {} + +// scale0 * scale1 +// (A,B) * (B,C) = (A,C) +impl<T: Mul, A, B, C> Mul<Scale<T, B, C>> for Scale<T, A, B> { + type Output = Scale<T::Output, A, C>; + + #[inline] + fn mul(self, other: Scale<T, B, C>) -> Self::Output { + Scale::new(self.0 * other.0) + } +} + +// scale0 + scale1 +impl<T: Add, Src, Dst> Add for Scale<T, Src, Dst> { + type Output = Scale<T::Output, Src, Dst>; + + #[inline] + fn add(self, other: Scale<T, Src, Dst>) -> Self::Output { + Scale::new(self.0 + other.0) + } +} + +// scale0 - scale1 +impl<T: Sub, Src, Dst> Sub for Scale<T, Src, Dst> { + type Output = Scale<T::Output, Src, Dst>; + + #[inline] + fn sub(self, other: Scale<T, Src, Dst>) -> Self::Output { + Scale::new(self.0 - other.0) + } +} + +// FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed: +// https://github.com/rust-lang/rust/issues/26925 + +impl<T: PartialEq, Src, Dst> PartialEq for Scale<T, Src, Dst> { + fn eq(&self, other: &Scale<T, Src, Dst>) -> bool { + self.0 == other.0 + } +} + +impl<T: Eq, Src, Dst> Eq for Scale<T, Src, Dst> {} + +impl<T: PartialOrd, Src, Dst> PartialOrd for Scale<T, Src, Dst> { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + self.0.partial_cmp(&other.0) + } +} + +impl<T: Ord, Src, Dst> Ord for Scale<T, Src, Dst> { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl<T: Clone, Src, Dst> Clone for Scale<T, Src, Dst> { + fn clone(&self) -> Scale<T, Src, Dst> { + Scale::new(self.0.clone()) + } +} + +impl<T: Copy, Src, Dst> Copy for Scale<T, Src, Dst> {} + +impl<T: fmt::Debug, Src, Dst> fmt::Debug for Scale<T, Src, Dst> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<T: Default, Src, Dst> Default for Scale<T, Src, Dst> { + fn default() -> Self { + Self::new(T::default()) + } +} + +impl<T: Hash, Src, Dst> Hash for Scale<T, Src, Dst> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.0.hash(state) + } +} + +impl<T: One, Src, Dst> One for Scale<T, Src, Dst> { + #[inline] + fn one() -> Self { + Scale::new(T::one()) + } +} + +#[cfg(test)] +mod tests { + use super::Scale; + + enum Inch {} + enum Cm {} + enum Mm {} + + #[test] + fn test_scale() { + let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let mm_per_cm: Scale<f32, Cm, Mm> = cm_per_mm.inverse(); + assert_eq!(mm_per_cm.get(), 10.0); + + let one: Scale<f32, Mm, Mm> = cm_per_mm * mm_per_cm; + assert_eq!(one.get(), 1.0); + + let one: Scale<f32, Cm, Cm> = mm_per_cm * cm_per_mm; + assert_eq!(one.get(), 1.0); + + let cm_per_inch: Scale<f32, Inch, Cm> = mm_per_inch * cm_per_mm; + // mm cm cm + // ---- x ---- = ---- + // inch mm inch + assert_eq!(cm_per_inch, Scale::new(2.54)); + + let a: Scale<isize, Inch, Inch> = Scale::new(2); + let b: Scale<isize, Inch, Inch> = Scale::new(3); + assert_ne!(a, b); + assert_eq!(a, a.clone()); + assert_eq!(a.clone() + b.clone(), Scale::new(5)); + assert_eq!(a - b, Scale::new(-1)); + } +} diff --git a/third_party/rust/euclid/src/side_offsets.rs b/third_party/rust/euclid/src/side_offsets.rs new file mode 100644 index 0000000000..ae00fef833 --- /dev/null +++ b/third_party/rust/euclid/src/side_offsets.rs @@ -0,0 +1,520 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A group of side offsets, which correspond to top/left/bottom/right for borders, padding, +//! and margins in CSS. + +use crate::length::Length; +use crate::num::Zero; +use crate::scale::Scale; +use crate::Vector2D; +use core::cmp::{Eq, PartialEq}; +use core::fmt; +use core::hash::Hash; +use core::marker::PhantomData; +use core::ops::{Add, AddAssign, Sub, SubAssign, Div, DivAssign, Mul, MulAssign, Neg}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A group of 2D side offsets, which correspond to top/right/bottom/left for borders, padding, +/// and margins in CSS, optionally tagged with a unit. +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) +)] +pub struct SideOffsets2D<T, U> { + pub top: T, + pub right: T, + pub bottom: T, + pub left: T, + #[doc(hidden)] + pub _unit: PhantomData<U>, +} + +#[cfg(feature = "arbitrary")] +impl<'a, T, U> arbitrary::Arbitrary<'a> for SideOffsets2D<T, U> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> + { + let (top, right, bottom, left) = arbitrary::Arbitrary::arbitrary(u)?; + Ok(SideOffsets2D { + top, + right, + bottom, + left, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for SideOffsets2D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for SideOffsets2D<T, U> {} + +impl<T: Copy, U> Copy for SideOffsets2D<T, U> {} + +impl<T: Clone, U> Clone for SideOffsets2D<T, U> { + fn clone(&self) -> Self { + SideOffsets2D { + top: self.top.clone(), + right: self.right.clone(), + bottom: self.bottom.clone(), + left: self.left.clone(), + _unit: PhantomData, + } + } +} + +impl<T, U> Eq for SideOffsets2D<T, U> where T: Eq {} + +impl<T, U> PartialEq for SideOffsets2D<T, U> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.top == other.top + && self.right == other.right + && self.bottom == other.bottom + && self.left == other.left + } +} + +impl<T, U> Hash for SideOffsets2D<T, U> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.top.hash(h); + self.right.hash(h); + self.bottom.hash(h); + self.left.hash(h); + } +} + +impl<T: fmt::Debug, U> fmt::Debug for SideOffsets2D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "({:?},{:?},{:?},{:?})", + self.top, self.right, self.bottom, self.left + ) + } +} + +impl<T: Default, U> Default for SideOffsets2D<T, U> { + fn default() -> Self { + SideOffsets2D { + top: Default::default(), + right: Default::default(), + bottom: Default::default(), + left: Default::default(), + _unit: PhantomData, + } + } +} + +impl<T, U> SideOffsets2D<T, U> { + /// Constructor taking a scalar for each side. + /// + /// Sides are specified in top-right-bottom-left order following + /// CSS's convention. + pub const fn new(top: T, right: T, bottom: T, left: T) -> Self { + SideOffsets2D { + top, + right, + bottom, + left, + _unit: PhantomData, + } + } + + /// Constructor taking a typed Length for each side. + /// + /// Sides are specified in top-right-bottom-left order following + /// CSS's convention. + pub fn from_lengths( + top: Length<T, U>, + right: Length<T, U>, + bottom: Length<T, U>, + left: Length<T, U>, + ) -> Self { + SideOffsets2D::new(top.0, right.0, bottom.0, left.0) + } + + /// Construct side offsets from min and a max vector offsets. + /// + /// The outer rect of the resulting side offsets is equivalent to translating + /// a rectangle's upper-left corner with the min vector and translating the + /// bottom-right corner with the max vector. + pub fn from_vectors_outer(min: Vector2D<T, U>, max: Vector2D<T, U>) -> Self + where + T: Neg<Output = T>, + { + SideOffsets2D { + left: -min.x, + top: -min.y, + right: max.x, + bottom: max.y, + _unit: PhantomData, + } + } + + /// Construct side offsets from min and a max vector offsets. + /// + /// The inner rect of the resulting side offsets is equivalent to translating + /// a rectangle's upper-left corner with the min vector and translating the + /// bottom-right corner with the max vector. + pub fn from_vectors_inner(min: Vector2D<T, U>, max: Vector2D<T, U>) -> Self + where + T: Neg<Output = T>, + { + SideOffsets2D { + left: min.x, + top: min.y, + right: -max.x, + bottom: -max.y, + _unit: PhantomData, + } + } + + /// Constructor, setting all sides to zero. + pub fn zero() -> Self + where T: Zero, + { + SideOffsets2D::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()) + } + + /// Returns `true` if all side offsets are zero. + pub fn is_zero(&self) -> bool + where + T: Zero + PartialEq, + { + let zero = T::zero(); + self.top == zero && self.right == zero && self.bottom == zero && self.left == zero + } + + /// Constructor setting the same value to all sides, taking a scalar value directly. + pub fn new_all_same(all: T) -> Self + where T : Copy + { + SideOffsets2D::new(all, all, all, all) + } + + /// Constructor setting the same value to all sides, taking a typed Length. + pub fn from_length_all_same(all: Length<T, U>) -> Self + where T : Copy + { + SideOffsets2D::new_all_same(all.0) + } + + pub fn horizontal(&self) -> T + where T: Copy + Add<T, Output = T> + { + self.left + self.right + } + + pub fn vertical(&self) -> T + where T: Copy + Add<T, Output = T> + { + self.top + self.bottom + } +} + +impl<T, U> Add for SideOffsets2D<T, U> +where + T: Add<T, Output = T>, +{ + type Output = Self; + fn add(self, other: Self) -> Self { + SideOffsets2D::new( + self.top + other.top, + self.right + other.right, + self.bottom + other.bottom, + self.left + other.left, + ) + } +} + +impl<T, U> AddAssign<Self> for SideOffsets2D<T, U> +where + T: AddAssign<T>, +{ + fn add_assign(&mut self, other: Self) { + self.top += other.top; + self.right += other.right; + self.bottom += other.bottom; + self.left += other.left; + } +} + +impl<T, U> Sub for SideOffsets2D<T, U> +where + T: Sub<T, Output = T>, +{ + type Output = Self; + fn sub(self, other: Self) -> Self { + SideOffsets2D::new( + self.top - other.top, + self.right - other.right, + self.bottom - other.bottom, + self.left - other.left, + ) + } +} + +impl<T, U> SubAssign<Self> for SideOffsets2D<T, U> +where + T: SubAssign<T>, +{ + fn sub_assign(&mut self, other: Self) { + self.top -= other.top; + self.right -= other.right; + self.bottom -= other.bottom; + self.left -= other.left; + } +} + +impl<T, U> Neg for SideOffsets2D<T, U> +where + T: Neg<Output = T> +{ + type Output = Self; + fn neg(self) -> Self { + SideOffsets2D { + top: -self.top, + right: -self.right, + bottom: -self.bottom, + left: -self.left, + _unit: PhantomData, + } + } +} + +impl<T: Copy + Mul, U> Mul<T> for SideOffsets2D<T, U> { + type Output = SideOffsets2D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + SideOffsets2D::new( + self.top * scale, + self.right * scale, + self.bottom * scale, + self.left * scale, + ) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<T> for SideOffsets2D<T, U> { + #[inline] + fn mul_assign(&mut self, other: T) { + self.top *= other; + self.right *= other; + self.bottom *= other; + self.left *= other; + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for SideOffsets2D<T, U1> { + type Output = SideOffsets2D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + SideOffsets2D::new( + self.top * scale.0, + self.right * scale.0, + self.bottom * scale.0, + self.left * scale.0, + ) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for SideOffsets2D<T, U> { + #[inline] + fn mul_assign(&mut self, other: Scale<T, U, U>) { + *self *= other.0; + } +} + +impl<T: Copy + Div, U> Div<T> for SideOffsets2D<T, U> { + type Output = SideOffsets2D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + SideOffsets2D::new( + self.top / scale, + self.right / scale, + self.bottom / scale, + self.left / scale, + ) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<T> for SideOffsets2D<T, U> { + #[inline] + fn div_assign(&mut self, other: T) { + self.top /= other; + self.right /= other; + self.bottom /= other; + self.left /= other; + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for SideOffsets2D<T, U2> { + type Output = SideOffsets2D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + SideOffsets2D::new( + self.top / scale.0, + self.right / scale.0, + self.bottom / scale.0, + self.left / scale.0, + ) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for SideOffsets2D<T, U> { + fn div_assign(&mut self, other: Scale<T, U, U>) { + *self /= other.0; + } +} + +#[test] +fn from_vectors() { + use crate::{point2, vec2}; + type Box2D = crate::default::Box2D<i32>; + + let b = Box2D { + min: point2(10, 10), + max: point2(20, 20), + }; + + let outer = b.outer_box(SideOffsets2D::from_vectors_outer(vec2(-1, -2), vec2(3, 4))); + let inner = b.inner_box(SideOffsets2D::from_vectors_inner(vec2(1, 2), vec2(-3, -4))); + + assert_eq!( + outer, + Box2D { + min: point2(9, 8), + max: point2(23, 24) + } + ); + assert_eq!( + inner, + Box2D { + min: point2(11, 12), + max: point2(17, 16) + } + ); +} + +#[test] +fn test_is_zero() { + let s1: SideOffsets2D<f32, ()> = SideOffsets2D::new_all_same(0.0); + assert!(s1.is_zero()); + + let s2: SideOffsets2D<f32, ()> = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0); + assert!(!s2.is_zero()); +} + +#[cfg(test)] +mod ops { + use crate::Scale; + + pub enum Mm {} + pub enum Cm {} + + type SideOffsets2D<T> = crate::default::SideOffsets2D<T>; + type SideOffsets2DMm<T> = crate::SideOffsets2D<T, Mm>; + type SideOffsets2DCm<T> = crate::SideOffsets2D<T, Cm>; + + #[test] + fn test_mul_scalar() { + let s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0); + + let result = s * 3.0; + + assert_eq!(result, SideOffsets2D::new(3.0, 6.0, 9.0, 12.0)); + } + + #[test] + fn test_mul_assign_scalar() { + let mut s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0); + + s *= 2.0; + + assert_eq!(s, SideOffsets2D::new(2.0, 4.0, 6.0, 8.0)); + } + + #[test] + fn test_mul_scale() { + let s = SideOffsets2DMm::new(0.0, 1.0, 3.0, 2.0); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = s * cm_per_mm; + + assert_eq!(result, SideOffsets2DCm::new(0.0, 0.1, 0.3, 0.2)); + } + + #[test] + fn test_mul_assign_scale() { + let mut s = SideOffsets2DMm::new(2.0, 4.0, 6.0, 8.0); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + s *= scale; + + assert_eq!(s, SideOffsets2DMm::new(0.2, 0.4, 0.6, 0.8)); + } + + #[test] + fn test_div_scalar() { + let s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0); + + let result = s / 10.0; + + assert_eq!(result, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0)); + } + + #[test] + fn test_div_assign_scalar() { + let mut s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0); + + s /= 10.0; + + assert_eq!(s, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0)); + } + + #[test] + fn test_div_scale() { + let s = SideOffsets2DCm::new(0.1, 0.2, 0.3, 0.4); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = s / cm_per_mm; + + assert_eq!(result, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0)); + } + + #[test] + fn test_div_assign_scale() { + let mut s = SideOffsets2DMm::new(0.1, 0.2, 0.3, 0.4); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + s /= scale; + + assert_eq!(s, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0)); + } +} diff --git a/third_party/rust/euclid/src/size.rs b/third_party/rust/euclid/src/size.rs new file mode 100644 index 0000000000..f634c1cfb6 --- /dev/null +++ b/third_party/rust/euclid/src/size.rs @@ -0,0 +1,1854 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::approxord::{max, min}; +use crate::length::Length; +use crate::num::*; +use crate::scale::Scale; +use crate::vector::{vec2, BoolVector2D, Vector2D}; +use crate::vector::{vec3, BoolVector3D, Vector3D}; +#[cfg(feature = "mint")] +use mint; + +use core::cmp::{Eq, PartialEq}; +use core::fmt; +use core::hash::Hash; +use core::iter::Sum; +use core::marker::PhantomData; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use num_traits::{NumCast, Signed, Float}; +#[cfg(feature = "serde")] +use serde; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A 2d size tagged with a unit. +#[repr(C)] +pub struct Size2D<T, U> { + /// The extent of the element in the `U` units along the `x` axis (usually horizontal). + pub width: T, + /// The extent of the element in the `U` units along the `y` axis (usually vertical). + pub height: T, + #[doc(hidden)] + pub _unit: PhantomData<U>, +} + +impl<T: Copy, U> Copy for Size2D<T, U> {} + +impl<T: Clone, U> Clone for Size2D<T, U> { + fn clone(&self) -> Self { + Size2D { + width: self.width.clone(), + height: self.height.clone(), + _unit: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T, U> serde::Deserialize<'de> for Size2D<T, U> +where + T: serde::Deserialize<'de>, +{ + /// Deserializes 2d size from tuple of width and height. + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let (width, height) = serde::Deserialize::deserialize(deserializer)?; + Ok(Size2D { + width, + height, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl<T, U> serde::Serialize for Size2D<T, U> +where + T: serde::Serialize, +{ + /// Serializes 2d size to tuple of width and height. + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + (&self.width, &self.height).serialize(serializer) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a, T, U> arbitrary::Arbitrary<'a> for Size2D<T, U> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> + { + let (width, height) = arbitrary::Arbitrary::arbitrary(u)?; + Ok(Size2D { + width, + height, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Size2D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Size2D<T, U> {} + +impl<T, U> Eq for Size2D<T, U> where T: Eq {} + +impl<T, U> PartialEq for Size2D<T, U> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.width == other.width && self.height == other.height + } +} + +impl<T, U> Hash for Size2D<T, U> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.width.hash(h); + self.height.hash(h); + } +} + +impl<T: fmt::Debug, U> fmt::Debug for Size2D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.width, f)?; + write!(f, "x")?; + fmt::Debug::fmt(&self.height, f) + } +} + +impl<T: Default, U> Default for Size2D<T, U> { + fn default() -> Self { + Size2D::new(Default::default(), Default::default()) + } +} + +impl<T, U> Size2D<T, U> { + /// The same as [`Zero::zero()`] but available without importing trait. + /// + /// [`Zero::zero()`]: ./num/trait.Zero.html#tymethod.zero + #[inline] + pub fn zero() -> Self + where + T: Zero, + { + Size2D::new(Zero::zero(), Zero::zero()) + } + + /// Constructor taking scalar values. + #[inline] + pub const fn new(width: T, height: T) -> Self { + Size2D { + width, + height, + _unit: PhantomData, + } + } + /// Constructor taking scalar strongly typed lengths. + #[inline] + pub fn from_lengths(width: Length<T, U>, height: Length<T, U>) -> Self { + Size2D::new(width.0, height.0) + } + + /// Constructor setting all components to the same value. + #[inline] + pub fn splat(v: T) -> Self + where + T: Clone, + { + Size2D { + width: v.clone(), + height: v, + _unit: PhantomData, + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Size2D<T, UnknownUnit>) -> Self { + Size2D::new(p.width, p.height) + } +} + +impl<T: Copy, U> Size2D<T, U> { + /// Return this size as an array of two elements (width, then height). + #[inline] + pub fn to_array(self) -> [T; 2] { + [self.width, self.height] + } + + /// Return this size as a tuple of two elements (width, then height). + #[inline] + pub fn to_tuple(self) -> (T, T) { + (self.width, self.height) + } + + /// Return this size as a vector with width and height. + #[inline] + pub fn to_vector(self) -> Vector2D<T, U> { + vec2(self.width, self.height) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(self) -> Size2D<T, UnknownUnit> { + self.cast_unit() + } + + /// Cast the unit + #[inline] + pub fn cast_unit<V>(self) -> Size2D<T, V> { + Size2D::new(self.width, self.height) + } + + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::size2; + /// enum Mm {} + /// + /// assert_eq!(size2::<_, Mm>(-0.1, -0.8).round(), size2::<_, Mm>(0.0, -1.0)) + /// ``` + #[inline] + #[must_use] + pub fn round(self) -> Self + where + T: Round, + { + Size2D::new(self.width.round(), self.height.round()) + } + + /// Rounds each component to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::size2; + /// enum Mm {} + /// + /// assert_eq!(size2::<_, Mm>(-0.1, -0.8).ceil(), size2::<_, Mm>(0.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn ceil(self) -> Self + where + T: Ceil, + { + Size2D::new(self.width.ceil(), self.height.ceil()) + } + + /// Rounds each component to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::size2; + /// enum Mm {} + /// + /// assert_eq!(size2::<_, Mm>(-0.1, -0.8).floor(), size2::<_, Mm>(-1.0, -1.0)) + /// ``` + #[inline] + #[must_use] + pub fn floor(self) -> Self + where + T: Floor, + { + Size2D::new(self.width.floor(), self.height.floor()) + } + + /// Returns result of multiplication of both components + pub fn area(self) -> T::Output + where + T: Mul, + { + self.width * self.height + } + + /// Linearly interpolate each component between this size and another size. + /// + /// # Example + /// + /// ```rust + /// use euclid::size2; + /// use euclid::default::Size2D; + /// + /// let from: Size2D<_> = size2(0.0, 10.0); + /// let to: Size2D<_> = size2(8.0, -4.0); + /// + /// assert_eq!(from.lerp(to, -1.0), size2(-8.0, 24.0)); + /// assert_eq!(from.lerp(to, 0.0), size2( 0.0, 10.0)); + /// assert_eq!(from.lerp(to, 0.5), size2( 4.0, 3.0)); + /// assert_eq!(from.lerp(to, 1.0), size2( 8.0, -4.0)); + /// assert_eq!(from.lerp(to, 2.0), size2(16.0, -18.0)); + /// ``` + #[inline] + pub fn lerp(self, other: Self, t: T) -> Self + where + T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>, + { + let one_t = T::one() - t; + self * one_t + other * t + } +} + +impl<T: NumCast + Copy, U> Size2D<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + #[inline] + pub fn cast<NewT: NumCast>(self) -> Size2D<NewT, U> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + pub fn try_cast<NewT: NumCast>(self) -> Option<Size2D<NewT, U>> { + match (NumCast::from(self.width), NumCast::from(self.height)) { + (Some(w), Some(h)) => Some(Size2D::new(w, h)), + _ => None, + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` size. + #[inline] + pub fn to_f32(self) -> Size2D<f32, U> { + self.cast() + } + + /// Cast into an `f64` size. + #[inline] + pub fn to_f64(self) -> Size2D<f64, U> { + self.cast() + } + + /// Cast into an `uint` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_usize(self) -> Size2D<usize, U> { + self.cast() + } + + /// Cast into an `u32` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_u32(self) -> Size2D<u32, U> { + self.cast() + } + + /// Cast into an `u64` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_u64(self) -> Size2D<u64, U> { + self.cast() + } + + /// Cast into an `i32` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i32(self) -> Size2D<i32, U> { + self.cast() + } + + /// Cast into an `i64` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i64(self) -> Size2D<i64, U> { + self.cast() + } +} + +impl<T: Float, U> Size2D<T, U> { + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.width.is_finite() && self.height.is_finite() + } +} + +impl<T: Signed, U> Size2D<T, U> { + /// Computes the absolute value of each component. + /// + /// For `f32` and `f64`, `NaN` will be returned for component if the component is `NaN`. + /// + /// For signed integers, `::MIN` will be returned for component if the component is `::MIN`. + pub fn abs(self) -> Self { + size2(self.width.abs(), self.height.abs()) + } + + /// Returns `true` if both components is positive and `false` any component is zero or negative. + pub fn is_positive(self) -> bool { + self.width.is_positive() && self.height.is_positive() + } +} + +impl<T: PartialOrd, U> Size2D<T, U> { + /// Returns the size each component of which are minimum of this size and another. + #[inline] + pub fn min(self, other: Self) -> Self { + size2(min(self.width, other.width), min(self.height, other.height)) + } + + /// Returns the size each component of which are maximum of this size and another. + #[inline] + pub fn max(self, other: Self) -> Self { + size2(max(self.width, other.width), max(self.height, other.height)) + } + + /// Returns the size each component of which clamped by corresponding + /// components of `start` and `end`. + /// + /// Shortcut for `self.max(start).min(end)`. + #[inline] + pub fn clamp(self, start: Self, end: Self) -> Self + where + T: Copy, + { + self.max(start).min(end) + } + + // Returns true if this size is larger or equal to the other size in all dimensions. + #[inline] + pub fn contains(self, other: Self) -> bool { + self.width >= other.width && self.height >= other.height + } + + /// Returns vector with results of "greater then" operation on each component. + pub fn greater_than(self, other: Self) -> BoolVector2D { + BoolVector2D { + x: self.width > other.width, + y: self.height > other.height, + } + } + + /// Returns vector with results of "lower then" operation on each component. + pub fn lower_than(self, other: Self) -> BoolVector2D { + BoolVector2D { + x: self.width < other.width, + y: self.height < other.height, + } + } + + /// Returns `true` if any component of size is zero, negative, or NaN. + pub fn is_empty(self) -> bool + where + T: Zero, + { + let zero = T::zero(); + // The condition is experessed this way so that we return true in + // the presence of NaN. + !(self.width > zero && self.height > zero) + } +} + +impl<T: PartialEq, U> Size2D<T, U> { + /// Returns vector with results of "equal" operation on each component. + pub fn equal(self, other: Self) -> BoolVector2D { + BoolVector2D { + x: self.width == other.width, + y: self.height == other.height, + } + } + + /// Returns vector with results of "not equal" operation on each component. + pub fn not_equal(self, other: Self) -> BoolVector2D { + BoolVector2D { + x: self.width != other.width, + y: self.height != other.height, + } + } +} + +impl<T: Round, U> Round for Size2D<T, U> { + /// See [`Size2D::round()`](#method.round). + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl<T: Ceil, U> Ceil for Size2D<T, U> { + /// See [`Size2D::ceil()`](#method.ceil). + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl<T: Floor, U> Floor for Size2D<T, U> { + /// See [`Size2D::floor()`](#method.floor). + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl<T: Zero, U> Zero for Size2D<T, U> { + #[inline] + fn zero() -> Self { + Size2D::new(Zero::zero(), Zero::zero()) + } +} + +impl<T: Neg, U> Neg for Size2D<T, U> { + type Output = Size2D<T::Output, U>; + + #[inline] + fn neg(self) -> Self::Output { + Size2D::new(-self.width, -self.height) + } +} + +impl<T: Add, U> Add for Size2D<T, U> { + type Output = Size2D<T::Output, U>; + + #[inline] + fn add(self, other: Self) -> Self::Output { + Size2D::new(self.width + other.width, self.height + other.height) + } +} + +impl<T: Copy + Add<T, Output = T>, U> Add<&Self> for Size2D<T, U> { + type Output = Self; + fn add(self, other: &Self) -> Self { + Size2D::new(self.width + other.width, self.height + other.height) + } +} + +impl<T: Add<Output = T> + Zero, U> Sum for Size2D<T, U> { + fn sum<I: Iterator<Item=Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Size2D<T, U> { + fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<T: AddAssign, U> AddAssign for Size2D<T, U> { + #[inline] + fn add_assign(&mut self, other: Self) { + self.width += other.width; + self.height += other.height; + } +} + +impl<T: Sub, U> Sub for Size2D<T, U> { + type Output = Size2D<T::Output, U>; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + Size2D::new(self.width - other.width, self.height - other.height) + } +} + +impl<T: SubAssign, U> SubAssign for Size2D<T, U> { + #[inline] + fn sub_assign(&mut self, other: Self) { + self.width -= other.width; + self.height -= other.height; + } +} + +impl<T: Copy + Mul, U> Mul<T> for Size2D<T, U> { + type Output = Size2D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Size2D::new(self.width * scale, self.height * scale) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<T> for Size2D<T, U> { + #[inline] + fn mul_assign(&mut self, other: T) { + self.width *= other; + self.height *= other; + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Size2D<T, U1> { + type Output = Size2D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + Size2D::new(self.width * scale.0, self.height * scale.0) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Size2D<T, U> { + #[inline] + fn mul_assign(&mut self, other: Scale<T, U, U>) { + *self *= other.0; + } +} + +impl<T: Copy + Div, U> Div<T> for Size2D<T, U> { + type Output = Size2D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Size2D::new(self.width / scale, self.height / scale) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<T> for Size2D<T, U> { + #[inline] + fn div_assign(&mut self, other: T) { + self.width /= other; + self.height /= other; + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Size2D<T, U2> { + type Output = Size2D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + Size2D::new(self.width / scale.0, self.height / scale.0) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Size2D<T, U> { + #[inline] + fn div_assign(&mut self, other: Scale<T, U, U>) { + *self /= other.0; + } +} + +/// Shorthand for `Size2D::new(w, h)`. +#[inline] +pub const fn size2<T, U>(w: T, h: T) -> Size2D<T, U> { + Size2D::new(w, h) +} + +#[cfg(feature = "mint")] +impl<T, U> From<mint::Vector2<T>> for Size2D<T, U> { + #[inline] + fn from(v: mint::Vector2<T>) -> Self { + Size2D { + width: v.x, + height: v.y, + _unit: PhantomData, + } + } +} +#[cfg(feature = "mint")] +impl<T, U> Into<mint::Vector2<T>> for Size2D<T, U> { + #[inline] + fn into(self) -> mint::Vector2<T> { + mint::Vector2 { + x: self.width, + y: self.height, + } + } +} + +impl<T, U> From<Vector2D<T, U>> for Size2D<T, U> { + #[inline] + fn from(v: Vector2D<T, U>) -> Self { + size2(v.x, v.y) + } +} + +impl<T, U> Into<[T; 2]> for Size2D<T, U> { + #[inline] + fn into(self) -> [T; 2] { + [self.width, self.height] + } +} + +impl<T, U> From<[T; 2]> for Size2D<T, U> { + #[inline] + fn from([w, h]: [T; 2]) -> Self { + size2(w, h) + } +} + +impl<T, U> Into<(T, T)> for Size2D<T, U> { + #[inline] + fn into(self) -> (T, T) { + (self.width, self.height) + } +} + +impl<T, U> From<(T, T)> for Size2D<T, U> { + #[inline] + fn from(tuple: (T, T)) -> Self { + size2(tuple.0, tuple.1) + } +} + +#[cfg(test)] +mod size2d { + use crate::default::Size2D; + #[cfg(feature = "mint")] + use mint; + + #[test] + pub fn test_area() { + let p = Size2D::new(1.5, 2.0); + assert_eq!(p.area(), 3.0); + } + + #[cfg(feature = "mint")] + #[test] + pub fn test_mint() { + let s1 = Size2D::new(1.0, 2.0); + let sm: mint::Vector2<_> = s1.into(); + let s2 = Size2D::from(sm); + + assert_eq!(s1, s2); + } + + mod ops { + use crate::default::Size2D; + use crate::scale::Scale; + + pub enum Mm {} + pub enum Cm {} + + pub type Size2DMm<T> = crate::Size2D<T, Mm>; + pub type Size2DCm<T> = crate::Size2D<T, Cm>; + + #[test] + pub fn test_neg() { + assert_eq!(-Size2D::new(1.0, 2.0), Size2D::new(-1.0, -2.0)); + assert_eq!(-Size2D::new(0.0, 0.0), Size2D::new(-0.0, -0.0)); + assert_eq!(-Size2D::new(-1.0, -2.0), Size2D::new(1.0, 2.0)); + } + + #[test] + pub fn test_add() { + let s1 = Size2D::new(1.0, 2.0); + let s2 = Size2D::new(3.0, 4.0); + assert_eq!(s1 + s2, Size2D::new(4.0, 6.0)); + assert_eq!(s1 + &s2, Size2D::new(4.0, 6.0)); + + let s1 = Size2D::new(1.0, 2.0); + let s2 = Size2D::new(0.0, 0.0); + assert_eq!(s1 + s2, Size2D::new(1.0, 2.0)); + assert_eq!(s1 + &s2, Size2D::new(1.0, 2.0)); + + let s1 = Size2D::new(1.0, 2.0); + let s2 = Size2D::new(-3.0, -4.0); + assert_eq!(s1 + s2, Size2D::new(-2.0, -2.0)); + assert_eq!(s1 + &s2, Size2D::new(-2.0, -2.0)); + + let s1 = Size2D::new(0.0, 0.0); + let s2 = Size2D::new(0.0, 0.0); + assert_eq!(s1 + s2, Size2D::new(0.0, 0.0)); + assert_eq!(s1 + &s2, Size2D::new(0.0, 0.0)); + } + + #[test] + pub fn test_add_assign() { + let mut s = Size2D::new(1.0, 2.0); + s += Size2D::new(3.0, 4.0); + assert_eq!(s, Size2D::new(4.0, 6.0)); + + let mut s = Size2D::new(1.0, 2.0); + s += Size2D::new(0.0, 0.0); + assert_eq!(s, Size2D::new(1.0, 2.0)); + + let mut s = Size2D::new(1.0, 2.0); + s += Size2D::new(-3.0, -4.0); + assert_eq!(s, Size2D::new(-2.0, -2.0)); + + let mut s = Size2D::new(0.0, 0.0); + s += Size2D::new(0.0, 0.0); + assert_eq!(s, Size2D::new(0.0, 0.0)); + } + + #[test] + pub fn test_sum() { + let sizes = [ + Size2D::new(0.0, 1.0), + Size2D::new(1.0, 2.0), + Size2D::new(2.0, 3.0) + ]; + let sum = Size2D::new(3.0, 6.0); + assert_eq!(sizes.iter().sum::<Size2D<_>>(), sum); + } + + #[test] + pub fn test_sub() { + let s1 = Size2D::new(1.0, 2.0); + let s2 = Size2D::new(3.0, 4.0); + assert_eq!(s1 - s2, Size2D::new(-2.0, -2.0)); + + let s1 = Size2D::new(1.0, 2.0); + let s2 = Size2D::new(0.0, 0.0); + assert_eq!(s1 - s2, Size2D::new(1.0, 2.0)); + + let s1 = Size2D::new(1.0, 2.0); + let s2 = Size2D::new(-3.0, -4.0); + assert_eq!(s1 - s2, Size2D::new(4.0, 6.0)); + + let s1 = Size2D::new(0.0, 0.0); + let s2 = Size2D::new(0.0, 0.0); + assert_eq!(s1 - s2, Size2D::new(0.0, 0.0)); + } + + #[test] + pub fn test_sub_assign() { + let mut s = Size2D::new(1.0, 2.0); + s -= Size2D::new(3.0, 4.0); + assert_eq!(s, Size2D::new(-2.0, -2.0)); + + let mut s = Size2D::new(1.0, 2.0); + s -= Size2D::new(0.0, 0.0); + assert_eq!(s, Size2D::new(1.0, 2.0)); + + let mut s = Size2D::new(1.0, 2.0); + s -= Size2D::new(-3.0, -4.0); + assert_eq!(s, Size2D::new(4.0, 6.0)); + + let mut s = Size2D::new(0.0, 0.0); + s -= Size2D::new(0.0, 0.0); + assert_eq!(s, Size2D::new(0.0, 0.0)); + } + + #[test] + pub fn test_mul_scalar() { + let s1: Size2D<f32> = Size2D::new(3.0, 5.0); + + let result = s1 * 5.0; + + assert_eq!(result, Size2D::new(15.0, 25.0)); + } + + #[test] + pub fn test_mul_assign_scalar() { + let mut s1 = Size2D::new(3.0, 5.0); + + s1 *= 5.0; + + assert_eq!(s1, Size2D::new(15.0, 25.0)); + } + + #[test] + pub fn test_mul_scale() { + let s1 = Size2DMm::new(1.0, 2.0); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = s1 * cm_per_mm; + + assert_eq!(result, Size2DCm::new(0.1, 0.2)); + } + + #[test] + pub fn test_mul_assign_scale() { + let mut s1 = Size2DMm::new(1.0, 2.0); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + s1 *= scale; + + assert_eq!(s1, Size2DMm::new(0.1, 0.2)); + } + + #[test] + pub fn test_div_scalar() { + let s1: Size2D<f32> = Size2D::new(15.0, 25.0); + + let result = s1 / 5.0; + + assert_eq!(result, Size2D::new(3.0, 5.0)); + } + + #[test] + pub fn test_div_assign_scalar() { + let mut s1: Size2D<f32> = Size2D::new(15.0, 25.0); + + s1 /= 5.0; + + assert_eq!(s1, Size2D::new(3.0, 5.0)); + } + + #[test] + pub fn test_div_scale() { + let s1 = Size2DCm::new(0.1, 0.2); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = s1 / cm_per_mm; + + assert_eq!(result, Size2DMm::new(1.0, 2.0)); + } + + #[test] + pub fn test_div_assign_scale() { + let mut s1 = Size2DMm::new(0.1, 0.2); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + s1 /= scale; + + assert_eq!(s1, Size2DMm::new(1.0, 2.0)); + } + + #[test] + pub fn test_nan_empty() { + use std::f32::NAN; + assert!(Size2D::new(NAN, 2.0).is_empty()); + assert!(Size2D::new(0.0, NAN).is_empty()); + assert!(Size2D::new(NAN, -2.0).is_empty()); + } + } +} + +/// A 3d size tagged with a unit. +#[repr(C)] +pub struct Size3D<T, U> { + /// The extent of the element in the `U` units along the `x` axis. + pub width: T, + /// The extent of the element in the `U` units along the `y` axis. + pub height: T, + /// The extent of the element in the `U` units along the `z` axis. + pub depth: T, + #[doc(hidden)] + pub _unit: PhantomData<U>, +} + +impl<T: Copy, U> Copy for Size3D<T, U> {} + +impl<T: Clone, U> Clone for Size3D<T, U> { + fn clone(&self) -> Self { + Size3D { + width: self.width.clone(), + height: self.height.clone(), + depth: self.depth.clone(), + _unit: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T, U> serde::Deserialize<'de> for Size3D<T, U> +where + T: serde::Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let (width, height, depth) = serde::Deserialize::deserialize(deserializer)?; + Ok(Size3D { + width, + height, + depth, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl<T, U> serde::Serialize for Size3D<T, U> +where + T: serde::Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + (&self.width, &self.height, &self.depth).serialize(serializer) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Size3D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Size3D<T, U> {} + +impl<T, U> Eq for Size3D<T, U> where T: Eq {} + +impl<T, U> PartialEq for Size3D<T, U> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.width == other.width && self.height == other.height && self.depth == other.depth + } +} + +impl<T, U> Hash for Size3D<T, U> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.width.hash(h); + self.height.hash(h); + self.depth.hash(h); + } +} + +impl<T: fmt::Debug, U> fmt::Debug for Size3D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.width, f)?; + write!(f, "x")?; + fmt::Debug::fmt(&self.height, f)?; + write!(f, "x")?; + fmt::Debug::fmt(&self.depth, f) + } +} + +impl<T: Default, U> Default for Size3D<T, U> { + fn default() -> Self { + Size3D::new(Default::default(), Default::default(), Default::default()) + } +} + +impl<T, U> Size3D<T, U> { + /// The same as [`Zero::zero()`] but available without importing trait. + /// + /// [`Zero::zero()`]: ./num/trait.Zero.html#tymethod.zero + pub fn zero() -> Self + where + T: Zero, + { + Size3D::new(Zero::zero(), Zero::zero(), Zero::zero()) + } + + /// Constructor taking scalar values. + #[inline] + pub const fn new(width: T, height: T, depth: T) -> Self { + Size3D { + width, + height, + depth, + _unit: PhantomData, + } + } + /// Constructor taking scalar strongly typed lengths. + #[inline] + pub fn from_lengths(width: Length<T, U>, height: Length<T, U>, depth: Length<T, U>) -> Self { + Size3D::new(width.0, height.0, depth.0) + } + + /// Constructor setting all components to the same value. + #[inline] + pub fn splat(v: T) -> Self + where + T: Clone, + { + Size3D { + width: v.clone(), + height: v.clone(), + depth: v, + _unit: PhantomData, + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Size3D<T, UnknownUnit>) -> Self { + Size3D::new(p.width, p.height, p.depth) + } +} + +impl<T: Copy, U> Size3D<T, U> { + /// Return this size as an array of three elements (width, then height, then depth). + #[inline] + pub fn to_array(self) -> [T; 3] { + [self.width, self.height, self.depth] + } + + /// Return this size as an array of three elements (width, then height, then depth). + #[inline] + pub fn to_tuple(self) -> (T, T, T) { + (self.width, self.height, self.depth) + } + + /// Return this size as a vector with width, height and depth. + #[inline] + pub fn to_vector(self) -> Vector3D<T, U> { + vec3(self.width, self.height, self.depth) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(self) -> Size3D<T, UnknownUnit> { + self.cast_unit() + } + + /// Cast the unit + #[inline] + pub fn cast_unit<V>(self) -> Size3D<T, V> { + Size3D::new(self.width, self.height, self.depth) + } + + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::size3; + /// enum Mm {} + /// + /// assert_eq!(size3::<_, Mm>(-0.1, -0.8, 0.4).round(), size3::<_, Mm>(0.0, -1.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn round(self) -> Self + where + T: Round, + { + Size3D::new(self.width.round(), self.height.round(), self.depth.round()) + } + + /// Rounds each component to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::size3; + /// enum Mm {} + /// + /// assert_eq!(size3::<_, Mm>(-0.1, -0.8, 0.4).ceil(), size3::<_, Mm>(0.0, 0.0, 1.0)) + /// ``` + #[inline] + #[must_use] + pub fn ceil(self) -> Self + where + T: Ceil, + { + Size3D::new(self.width.ceil(), self.height.ceil(), self.depth.ceil()) + } + + /// Rounds each component to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::size3; + /// enum Mm {} + /// + /// assert_eq!(size3::<_, Mm>(-0.1, -0.8, 0.4).floor(), size3::<_, Mm>(-1.0, -1.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn floor(self) -> Self + where + T: Floor, + { + Size3D::new(self.width.floor(), self.height.floor(), self.depth.floor()) + } + + /// Returns result of multiplication of all components + pub fn volume(self) -> T + where + T: Mul<Output = T>, + { + self.width * self.height * self.depth + } + + /// Linearly interpolate between this size and another size. + /// + /// # Example + /// + /// ```rust + /// use euclid::size3; + /// use euclid::default::Size3D; + /// + /// let from: Size3D<_> = size3(0.0, 10.0, -1.0); + /// let to: Size3D<_> = size3(8.0, -4.0, 0.0); + /// + /// assert_eq!(from.lerp(to, -1.0), size3(-8.0, 24.0, -2.0)); + /// assert_eq!(from.lerp(to, 0.0), size3( 0.0, 10.0, -1.0)); + /// assert_eq!(from.lerp(to, 0.5), size3( 4.0, 3.0, -0.5)); + /// assert_eq!(from.lerp(to, 1.0), size3( 8.0, -4.0, 0.0)); + /// assert_eq!(from.lerp(to, 2.0), size3(16.0, -18.0, 1.0)); + /// ``` + #[inline] + pub fn lerp(self, other: Self, t: T) -> Self + where + T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>, + { + let one_t = T::one() - t; + self * one_t + other * t + } +} + +impl<T: NumCast + Copy, U> Size3D<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + #[inline] + pub fn cast<NewT: NumCast>(self) -> Size3D<NewT, U> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + pub fn try_cast<NewT: NumCast>(self) -> Option<Size3D<NewT, U>> { + match ( + NumCast::from(self.width), + NumCast::from(self.height), + NumCast::from(self.depth), + ) { + (Some(w), Some(h), Some(d)) => Some(Size3D::new(w, h, d)), + _ => None, + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` size. + #[inline] + pub fn to_f32(self) -> Size3D<f32, U> { + self.cast() + } + + /// Cast into an `f64` size. + #[inline] + pub fn to_f64(self) -> Size3D<f64, U> { + self.cast() + } + + /// Cast into an `uint` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_usize(self) -> Size3D<usize, U> { + self.cast() + } + + /// Cast into an `u32` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_u32(self) -> Size3D<u32, U> { + self.cast() + } + + /// Cast into an `i32` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i32(self) -> Size3D<i32, U> { + self.cast() + } + + /// Cast into an `i64` size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i64(self) -> Size3D<i64, U> { + self.cast() + } +} + +impl<T: Float, U> Size3D<T, U> { + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.width.is_finite() && self.height.is_finite() && self.depth.is_finite() + } +} + +impl<T: Signed, U> Size3D<T, U> { + /// Computes the absolute value of each component. + /// + /// For `f32` and `f64`, `NaN` will be returned for component if the component is `NaN`. + /// + /// For signed integers, `::MIN` will be returned for component if the component is `::MIN`. + pub fn abs(self) -> Self { + size3(self.width.abs(), self.height.abs(), self.depth.abs()) + } + + /// Returns `true` if all components is positive and `false` any component is zero or negative. + pub fn is_positive(self) -> bool { + self.width.is_positive() && self.height.is_positive() && self.depth.is_positive() + } +} + +impl<T: PartialOrd, U> Size3D<T, U> { + /// Returns the size each component of which are minimum of this size and another. + #[inline] + pub fn min(self, other: Self) -> Self { + size3( + min(self.width, other.width), + min(self.height, other.height), + min(self.depth, other.depth), + ) + } + + /// Returns the size each component of which are maximum of this size and another. + #[inline] + pub fn max(self, other: Self) -> Self { + size3( + max(self.width, other.width), + max(self.height, other.height), + max(self.depth, other.depth), + ) + } + + /// Returns the size each component of which clamped by corresponding + /// components of `start` and `end`. + /// + /// Shortcut for `self.max(start).min(end)`. + #[inline] + pub fn clamp(self, start: Self, end: Self) -> Self + where + T: Copy, + { + self.max(start).min(end) + } + + // Returns true if this size is larger or equal to the other size in all dimensions. + #[inline] + pub fn contains(self, other: Self) -> bool { + self.width >= other.width && self.height >= other.height && self.depth >= other.depth + } + + + /// Returns vector with results of "greater than" operation on each component. + pub fn greater_than(self, other: Self) -> BoolVector3D { + BoolVector3D { + x: self.width > other.width, + y: self.height > other.height, + z: self.depth > other.depth, + } + } + + /// Returns vector with results of "lower than" operation on each component. + pub fn lower_than(self, other: Self) -> BoolVector3D { + BoolVector3D { + x: self.width < other.width, + y: self.height < other.height, + z: self.depth < other.depth, + } + } + + /// Returns `true` if any component of size is zero, negative or NaN. + pub fn is_empty(self) -> bool + where + T: Zero, + { + let zero = T::zero(); + !(self.width > zero && self.height > zero && self.depth <= zero) + } +} + +impl<T: PartialEq, U> Size3D<T, U> { + /// Returns vector with results of "equal" operation on each component. + pub fn equal(self, other: Self) -> BoolVector3D { + BoolVector3D { + x: self.width == other.width, + y: self.height == other.height, + z: self.depth == other.depth, + } + } + + /// Returns vector with results of "not equal" operation on each component. + pub fn not_equal(self, other: Self) -> BoolVector3D { + BoolVector3D { + x: self.width != other.width, + y: self.height != other.height, + z: self.depth != other.depth, + } + } +} + +impl<T: Round, U> Round for Size3D<T, U> { + /// See [`Size3D::round()`](#method.round). + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl<T: Ceil, U> Ceil for Size3D<T, U> { + /// See [`Size3D::ceil()`](#method.ceil). + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl<T: Floor, U> Floor for Size3D<T, U> { + /// See [`Size3D::floor()`](#method.floor). + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl<T: Zero, U> Zero for Size3D<T, U> { + #[inline] + fn zero() -> Self { + Size3D::new(Zero::zero(), Zero::zero(), Zero::zero()) + } +} + +impl<T: Neg, U> Neg for Size3D<T, U> { + type Output = Size3D<T::Output, U>; + + #[inline] + fn neg(self) -> Self::Output { + Size3D::new(-self.width, -self.height, -self.depth) + } +} + +impl<T: Add, U> Add for Size3D<T, U> { + type Output = Size3D<T::Output, U>; + + #[inline] + fn add(self, other: Self) -> Self::Output { + Size3D::new( + self.width + other.width, + self.height + other.height, + self.depth + other.depth, + ) + } +} + +impl<T: Copy + Add<T, Output = T>, U> Add<&Self> for Size3D<T, U> { + type Output = Self; + fn add(self, other: &Self) -> Self { + Size3D::new( + self.width + other.width, + self.height + other.height, + self.depth + other.depth, + ) + } +} + +impl<T: Add<Output = T> + Zero, U> Sum for Size3D<T, U> { + fn sum<I: Iterator<Item=Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Size3D<T, U> { + fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<T: AddAssign, U> AddAssign for Size3D<T, U> { + #[inline] + fn add_assign(&mut self, other: Self) { + self.width += other.width; + self.height += other.height; + self.depth += other.depth; + } +} + +impl<T: Sub, U> Sub for Size3D<T, U> { + type Output = Size3D<T::Output, U>; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + Size3D::new( + self.width - other.width, + self.height - other.height, + self.depth - other.depth, + ) + } +} + +impl<T: SubAssign, U> SubAssign for Size3D<T, U> { + #[inline] + fn sub_assign(&mut self, other: Self) { + self.width -= other.width; + self.height -= other.height; + self.depth -= other.depth; + } +} + +impl<T: Copy + Mul, U> Mul<T> for Size3D<T, U> { + type Output = Size3D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Size3D::new( + self.width * scale, + self.height * scale, + self.depth * scale, + ) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<T> for Size3D<T, U> { + #[inline] + fn mul_assign(&mut self, other: T) { + self.width *= other; + self.height *= other; + self.depth *= other; + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Size3D<T, U1> { + type Output = Size3D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + Size3D::new( + self.width * scale.0, + self.height * scale.0, + self.depth * scale.0, + ) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Size3D<T, U> { + #[inline] + fn mul_assign(&mut self, other: Scale<T, U, U>) { + *self *= other.0; + } +} + +impl<T: Copy + Div, U> Div<T> for Size3D<T, U> { + type Output = Size3D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Size3D::new( + self.width / scale, + self.height / scale, + self.depth / scale, + ) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<T> for Size3D<T, U> { + #[inline] + fn div_assign(&mut self, other: T) { + self.width /= other; + self.height /= other; + self.depth /= other; + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Size3D<T, U2> { + type Output = Size3D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + Size3D::new( + self.width / scale.0, + self.height / scale.0, + self.depth / scale.0, + ) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Size3D<T, U> { + #[inline] + fn div_assign(&mut self, other: Scale<T, U, U>) { + *self /= other.0; + } +} + +#[cfg(feature = "mint")] +impl<T, U> From<mint::Vector3<T>> for Size3D<T, U> { + #[inline] + fn from(v: mint::Vector3<T>) -> Self { + size3(v.x, v.y, v.z) + } +} +#[cfg(feature = "mint")] +impl<T, U> Into<mint::Vector3<T>> for Size3D<T, U> { + #[inline] + fn into(self) -> mint::Vector3<T> { + mint::Vector3 { + x: self.width, + y: self.height, + z: self.depth, + } + } +} + +impl<T, U> From<Vector3D<T, U>> for Size3D<T, U> { + #[inline] + fn from(v: Vector3D<T, U>) -> Self { + size3(v.x, v.y, v.z) + } +} + +impl<T, U> Into<[T; 3]> for Size3D<T, U> { + #[inline] + fn into(self) -> [T; 3] { + [self.width, self.height, self.depth] + } +} + +impl<T, U> From<[T; 3]> for Size3D<T, U> { + #[inline] + fn from([w, h, d]: [T; 3]) -> Self { + size3(w, h, d) + } +} + +impl<T, U> Into<(T, T, T)> for Size3D<T, U> { + #[inline] + fn into(self) -> (T, T, T) { + (self.width, self.height, self.depth) + } +} + +impl<T, U> From<(T, T, T)> for Size3D<T, U> { + #[inline] + fn from(tuple: (T, T, T)) -> Self { + size3(tuple.0, tuple.1, tuple.2) + } +} + +/// Shorthand for `Size3D::new(w, h, d)`. +#[inline] +pub const fn size3<T, U>(w: T, h: T, d: T) -> Size3D<T, U> { + Size3D::new(w, h, d) +} + +#[cfg(test)] +mod size3d { + mod ops { + use crate::default::Size3D; + use crate::scale::Scale; + + pub enum Mm {} + pub enum Cm {} + + pub type Size3DMm<T> = crate::Size3D<T, Mm>; + pub type Size3DCm<T> = crate::Size3D<T, Cm>; + + #[test] + pub fn test_neg() { + assert_eq!(-Size3D::new(1.0, 2.0, 3.0), Size3D::new(-1.0, -2.0, -3.0)); + assert_eq!(-Size3D::new(0.0, 0.0, 0.0), Size3D::new(-0.0, -0.0, -0.0)); + assert_eq!(-Size3D::new(-1.0, -2.0, -3.0), Size3D::new(1.0, 2.0, 3.0)); + } + + #[test] + pub fn test_add() { + let s1 = Size3D::new(1.0, 2.0, 3.0); + let s2 = Size3D::new(4.0, 5.0, 6.0); + assert_eq!(s1 + s2, Size3D::new(5.0, 7.0, 9.0)); + assert_eq!(s1 + &s2, Size3D::new(5.0, 7.0, 9.0)); + + let s1 = Size3D::new(1.0, 2.0, 3.0); + let s2 = Size3D::new(0.0, 0.0, 0.0); + assert_eq!(s1 + s2, Size3D::new(1.0, 2.0, 3.0)); + assert_eq!(s1 + &s2, Size3D::new(1.0, 2.0, 3.0)); + + let s1 = Size3D::new(1.0, 2.0, 3.0); + let s2 = Size3D::new(-4.0, -5.0, -6.0); + assert_eq!(s1 + s2, Size3D::new(-3.0, -3.0, -3.0)); + assert_eq!(s1 + &s2, Size3D::new(-3.0, -3.0, -3.0)); + + let s1 = Size3D::new(0.0, 0.0, 0.0); + let s2 = Size3D::new(0.0, 0.0, 0.0); + assert_eq!(s1 + s2, Size3D::new(0.0, 0.0, 0.0)); + assert_eq!(s1 + &s2, Size3D::new(0.0, 0.0, 0.0)); + } + + #[test] + pub fn test_sum() { + let sizes = [ + Size3D::new(0.0, 1.0, 2.0), + Size3D::new(1.0, 2.0, 3.0), + Size3D::new(2.0, 3.0, 4.0) + ]; + let sum = Size3D::new(3.0, 6.0, 9.0); + assert_eq!(sizes.iter().sum::<Size3D<_>>(), sum); + } + + #[test] + pub fn test_add_assign() { + let mut s = Size3D::new(1.0, 2.0, 3.0); + s += Size3D::new(4.0, 5.0, 6.0); + assert_eq!(s, Size3D::new(5.0, 7.0, 9.0)); + + let mut s = Size3D::new(1.0, 2.0, 3.0); + s += Size3D::new(0.0, 0.0, 0.0); + assert_eq!(s, Size3D::new(1.0, 2.0, 3.0)); + + let mut s = Size3D::new(1.0, 2.0, 3.0); + s += Size3D::new(-4.0, -5.0, -6.0); + assert_eq!(s, Size3D::new(-3.0, -3.0, -3.0)); + + let mut s = Size3D::new(0.0, 0.0, 0.0); + s += Size3D::new(0.0, 0.0, 0.0); + assert_eq!(s, Size3D::new(0.0, 0.0, 0.0)); + } + + #[test] + pub fn test_sub() { + let s1 = Size3D::new(1.0, 2.0, 3.0); + let s2 = Size3D::new(4.0, 5.0, 6.0); + assert_eq!(s1 - s2, Size3D::new(-3.0, -3.0, -3.0)); + + let s1 = Size3D::new(1.0, 2.0, 3.0); + let s2 = Size3D::new(0.0, 0.0, 0.0); + assert_eq!(s1 - s2, Size3D::new(1.0, 2.0, 3.0)); + + let s1 = Size3D::new(1.0, 2.0, 3.0); + let s2 = Size3D::new(-4.0, -5.0, -6.0); + assert_eq!(s1 - s2, Size3D::new(5.0, 7.0, 9.0)); + + let s1 = Size3D::new(0.0, 0.0, 0.0); + let s2 = Size3D::new(0.0, 0.0, 0.0); + assert_eq!(s1 - s2, Size3D::new(0.0, 0.0, 0.0)); + } + + #[test] + pub fn test_sub_assign() { + let mut s = Size3D::new(1.0, 2.0, 3.0); + s -= Size3D::new(4.0, 5.0, 6.0); + assert_eq!(s, Size3D::new(-3.0, -3.0, -3.0)); + + let mut s = Size3D::new(1.0, 2.0, 3.0); + s -= Size3D::new(0.0, 0.0, 0.0); + assert_eq!(s, Size3D::new(1.0, 2.0, 3.0)); + + let mut s = Size3D::new(1.0, 2.0, 3.0); + s -= Size3D::new(-4.0, -5.0, -6.0); + assert_eq!(s, Size3D::new(5.0, 7.0, 9.0)); + + let mut s = Size3D::new(0.0, 0.0, 0.0); + s -= Size3D::new(0.0, 0.0, 0.0); + assert_eq!(s, Size3D::new(0.0, 0.0, 0.0)); + } + + #[test] + pub fn test_mul_scalar() { + let s1: Size3D<f32> = Size3D::new(3.0, 5.0, 7.0); + + let result = s1 * 5.0; + + assert_eq!(result, Size3D::new(15.0, 25.0, 35.0)); + } + + #[test] + pub fn test_mul_assign_scalar() { + let mut s1: Size3D<f32> = Size3D::new(3.0, 5.0, 7.0); + + s1 *= 5.0; + + assert_eq!(s1, Size3D::new(15.0, 25.0, 35.0)); + } + + #[test] + pub fn test_mul_scale() { + let s1 = Size3DMm::new(1.0, 2.0, 3.0); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = s1 * cm_per_mm; + + assert_eq!(result, Size3DCm::new(0.1, 0.2, 0.3)); + } + + #[test] + pub fn test_mul_assign_scale() { + let mut s1 = Size3DMm::new(1.0, 2.0, 3.0); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + s1 *= scale; + + assert_eq!(s1, Size3DMm::new(0.1, 0.2, 0.3)); + } + + #[test] + pub fn test_div_scalar() { + let s1: Size3D<f32> = Size3D::new(15.0, 25.0, 35.0); + + let result = s1 / 5.0; + + assert_eq!(result, Size3D::new(3.0, 5.0, 7.0)); + } + + #[test] + pub fn test_div_assign_scalar() { + let mut s1: Size3D<f32> = Size3D::new(15.0, 25.0, 35.0); + + s1 /= 5.0; + + assert_eq!(s1, Size3D::new(3.0, 5.0, 7.0)); + } + + #[test] + pub fn test_div_scale() { + let s1 = Size3DCm::new(0.1, 0.2, 0.3); + let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1); + + let result = s1 / cm_per_mm; + + assert_eq!(result, Size3DMm::new(1.0, 2.0, 3.0)); + } + + #[test] + pub fn test_div_assign_scale() { + let mut s1 = Size3DMm::new(0.1, 0.2, 0.3); + let scale: Scale<f32, Mm, Mm> = Scale::new(0.1); + + s1 /= scale; + + assert_eq!(s1, Size3DMm::new(1.0, 2.0, 3.0)); + } + + #[test] + pub fn test_nan_empty() { + use std::f32::NAN; + assert!(Size3D::new(NAN, 2.0, 3.0).is_empty()); + assert!(Size3D::new(0.0, NAN, 0.0).is_empty()); + assert!(Size3D::new(1.0, 2.0, NAN).is_empty()); + } + } +} diff --git a/third_party/rust/euclid/src/transform2d.rs b/third_party/rust/euclid/src/transform2d.rs new file mode 100644 index 0000000000..85eb426b6c --- /dev/null +++ b/third_party/rust/euclid/src/transform2d.rs @@ -0,0 +1,809 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![cfg_attr(feature = "cargo-clippy", allow(just_underscores_and_digits))] + +use super::{UnknownUnit, Angle}; +#[cfg(feature = "mint")] +use mint; +use crate::num::{One, Zero}; +use crate::point::{Point2D, point2}; +use crate::vector::{Vector2D, vec2}; +use crate::rect::Rect; +use crate::box2d::Box2D; +use crate::transform3d::Transform3D; +use core::ops::{Add, Mul, Div, Sub}; +use core::marker::PhantomData; +use core::cmp::{Eq, PartialEq}; +use core::hash::{Hash}; +use crate::approxeq::ApproxEq; +use crate::trig::Trig; +use core::fmt; +use num_traits::NumCast; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A 2d transform represented by a column-major 3 by 3 matrix, compressed down to 3 by 2. +/// +/// Transforms can be parametrized over the source and destination units, to describe a +/// transformation from a space to another. +/// For example, `Transform2D<f32, WorldSpace, ScreenSpace>::transform_point4d` +/// takes a `Point2D<f32, WorldSpace>` and returns a `Point2D<f32, ScreenSpace>`. +/// +/// Transforms expose a set of convenience methods for pre- and post-transformations. +/// Pre-transformations (`pre_*` methods) correspond to adding an operation that is +/// applied before the rest of the transformation, while post-transformations (`then_*` +/// methods) add an operation that is applied after. +/// +/// The matrix representation is conceptually equivalent to a 3 by 3 matrix transformation +/// compressed to 3 by 2 with the components that aren't needed to describe the set of 2d +/// transformations we are interested in implicitly defined: +/// +/// ```text +/// | m11 m12 0 | |x| |x'| +/// | m21 m22 0 | x |y| = |y'| +/// | m31 m32 1 | |1| |w | +/// ``` +/// +/// When translating Transform2D into general matrix representations, consider that the +/// representation follows the column-major notation with column vectors. +/// +/// The translation terms are m31 and m32. +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) +)] +pub struct Transform2D<T, Src, Dst> { + pub m11: T, pub m12: T, + pub m21: T, pub m22: T, + pub m31: T, pub m32: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +#[cfg(feature = "arbitrary")] +impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Transform2D<T, Src, Dst> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> + { + let (m11, m12, m21, m22, m31, m32) = arbitrary::Arbitrary::arbitrary(u)?; + Ok(Transform2D { + m11, m12, m21, m22, m31, m32, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, Src, Dst> Zeroable for Transform2D<T, Src, Dst> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Transform2D<T, Src, Dst> {} + +impl<T: Copy, Src, Dst> Copy for Transform2D<T, Src, Dst> {} + +impl<T: Clone, Src, Dst> Clone for Transform2D<T, Src, Dst> { + fn clone(&self) -> Self { + Transform2D { + m11: self.m11.clone(), + m12: self.m12.clone(), + m21: self.m21.clone(), + m22: self.m22.clone(), + m31: self.m31.clone(), + m32: self.m32.clone(), + _unit: PhantomData, + } + } +} + +impl<T, Src, Dst> Eq for Transform2D<T, Src, Dst> where T: Eq {} + +impl<T, Src, Dst> PartialEq for Transform2D<T, Src, Dst> + where T: PartialEq +{ + fn eq(&self, other: &Self) -> bool { + self.m11 == other.m11 && + self.m12 == other.m12 && + self.m21 == other.m21 && + self.m22 == other.m22 && + self.m31 == other.m31 && + self.m32 == other.m32 + } +} + +impl<T, Src, Dst> Hash for Transform2D<T, Src, Dst> + where T: Hash +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.m11.hash(h); + self.m12.hash(h); + self.m21.hash(h); + self.m22.hash(h); + self.m31.hash(h); + self.m32.hash(h); + } +} + + +impl<T, Src, Dst> Transform2D<T, Src, Dst> { + /// Create a transform specifying its components in using the column-major-column-vector + /// matrix notation. + /// + /// For example, the translation terms m31 and m32 are the last two parameters parameters. + /// + /// ``` + /// use euclid::default::Transform2D; + /// let tx = 1.0; + /// let ty = 2.0; + /// let translation = Transform2D::new( + /// 1.0, 0.0, + /// 0.0, 1.0, + /// tx, ty, + /// ); + /// ``` + pub const fn new(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> Self { + Transform2D { + m11, m12, + m21, m22, + m31, m32, + _unit: PhantomData, + } + } + + /// Returns true is this transform is approximately equal to the other one, using + /// T's default epsilon value. + /// + /// The same as [`ApproxEq::approx_eq()`] but available without importing trait. + /// + /// [`ApproxEq::approx_eq()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq + #[inline] + pub fn approx_eq(&self, other: &Self) -> bool + where T : ApproxEq<T> { + <Self as ApproxEq<T>>::approx_eq(&self, &other) + } + + /// Returns true is this transform is approximately equal to the other one, using + /// a provided epsilon value. + /// + /// The same as [`ApproxEq::approx_eq_eps()`] but available without importing trait. + /// + /// [`ApproxEq::approx_eq_eps()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq_eps + #[inline] + pub fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool + where T : ApproxEq<T> { + <Self as ApproxEq<T>>::approx_eq_eps(&self, &other, &eps) + } +} + +impl<T: Copy, Src, Dst> Transform2D<T, Src, Dst> { + /// Returns an array containing this transform's terms. + /// + /// The terms are laid out in the same order as they are + /// specified in `Transform2D::new`, that is following the + /// column-major-column-vector matrix notation. + /// + /// For example the translation terms are found in the + /// last two slots of the array. + #[inline] + pub fn to_array(&self) -> [T; 6] { + [ + self.m11, self.m12, + self.m21, self.m22, + self.m31, self.m32 + ] + } + + /// Returns an array containing this transform's terms transposed. + /// + /// The terms are laid out in transposed order from the same order of + /// `Transform3D::new` and `Transform3D::to_array`, that is following + /// the row-major-column-vector matrix notation. + /// + /// For example the translation terms are found at indices 2 and 5 + /// in the array. + #[inline] + pub fn to_array_transposed(&self) -> [T; 6] { + [ + self.m11, self.m21, self.m31, + self.m12, self.m22, self.m32 + ] + } + + /// Equivalent to `to_array` with elements packed two at a time + /// in an array of arrays. + #[inline] + pub fn to_arrays(&self) -> [[T; 2]; 3] { + [ + [self.m11, self.m12], + [self.m21, self.m22], + [self.m31, self.m32], + ] + } + + /// Create a transform providing its components via an array + /// of 6 elements instead of as individual parameters. + /// + /// The order of the components corresponds to the + /// column-major-column-vector matrix notation (the same order + /// as `Transform2D::new`). + #[inline] + pub fn from_array(array: [T; 6]) -> Self { + Self::new( + array[0], array[1], + array[2], array[3], + array[4], array[5], + ) + } + + /// Equivalent to `from_array` with elements packed two at a time + /// in an array of arrays. + /// + /// The order of the components corresponds to the + /// column-major-column-vector matrix notation (the same order + /// as `Transform3D::new`). + #[inline] + pub fn from_arrays(array: [[T; 2]; 3]) -> Self { + Self::new( + array[0][0], array[0][1], + array[1][0], array[1][1], + array[2][0], array[2][1], + ) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Transform2D<T, UnknownUnit, UnknownUnit> { + Transform2D::new( + self.m11, self.m12, + self.m21, self.m22, + self.m31, self.m32 + ) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: &Transform2D<T, UnknownUnit, UnknownUnit>) -> Self { + Transform2D::new( + p.m11, p.m12, + p.m21, p.m22, + p.m31, p.m32 + ) + } + + /// Returns the same transform with a different source unit. + #[inline] + pub fn with_source<NewSrc>(&self) -> Transform2D<T, NewSrc, Dst> { + Transform2D::new( + self.m11, self.m12, + self.m21, self.m22, + self.m31, self.m32, + ) + } + + /// Returns the same transform with a different destination unit. + #[inline] + pub fn with_destination<NewDst>(&self) -> Transform2D<T, Src, NewDst> { + Transform2D::new( + self.m11, self.m12, + self.m21, self.m22, + self.m31, self.m32, + ) + } + + /// Create a 3D transform from the current transform + pub fn to_3d(&self) -> Transform3D<T, Src, Dst> + where + T: Zero + One, + { + Transform3D::new_2d(self.m11, self.m12, self.m21, self.m22, self.m31, self.m32) + } +} + +impl<T: NumCast + Copy, Src, Dst> Transform2D<T, Src, Dst> { + /// Cast from one numeric representation to another, preserving the units. + #[inline] + pub fn cast<NewT: NumCast>(&self) -> Transform2D<NewT, Src, Dst> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + pub fn try_cast<NewT: NumCast>(&self) -> Option<Transform2D<NewT, Src, Dst>> { + match (NumCast::from(self.m11), NumCast::from(self.m12), + NumCast::from(self.m21), NumCast::from(self.m22), + NumCast::from(self.m31), NumCast::from(self.m32)) { + (Some(m11), Some(m12), + Some(m21), Some(m22), + Some(m31), Some(m32)) => { + Some(Transform2D::new( + m11, m12, + m21, m22, + m31, m32 + )) + }, + _ => None + } + } +} + +impl<T, Src, Dst> Transform2D<T, Src, Dst> +where + T: Zero + One, +{ + /// Create an identity matrix: + /// + /// ```text + /// 1 0 + /// 0 1 + /// 0 0 + /// ``` + #[inline] + pub fn identity() -> Self { + Self::translation(T::zero(), T::zero()) + } + + /// Intentional not public, because it checks for exact equivalence + /// while most consumers will probably want some sort of approximate + /// equivalence to deal with floating-point errors. + fn is_identity(&self) -> bool + where + T: PartialEq, + { + *self == Self::identity() + } +} + + +/// Methods for combining generic transformations +impl<T, Src, Dst> Transform2D<T, Src, Dst> +where + T: Copy + Add<Output = T> + Mul<Output = T>, +{ + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies after self's transformation. + #[must_use] + pub fn then<NewDst>(&self, mat: &Transform2D<T, Dst, NewDst>) -> Transform2D<T, Src, NewDst> { + Transform2D::new( + self.m11 * mat.m11 + self.m12 * mat.m21, + self.m11 * mat.m12 + self.m12 * mat.m22, + + self.m21 * mat.m11 + self.m22 * mat.m21, + self.m21 * mat.m12 + self.m22 * mat.m22, + + self.m31 * mat.m11 + self.m32 * mat.m21 + mat.m31, + self.m31 * mat.m12 + self.m32 * mat.m22 + mat.m32, + ) + } +} + +/// Methods for creating and combining translation transformations +impl<T, Src, Dst> Transform2D<T, Src, Dst> +where + T: Zero + One, +{ + /// Create a 2d translation transform: + /// + /// ```text + /// 1 0 + /// 0 1 + /// x y + /// ``` + #[inline] + pub fn translation(x: T, y: T) -> Self { + let _0 = || T::zero(); + let _1 = || T::one(); + + Self::new( + _1(), _0(), + _0(), _1(), + x, y, + ) + } + + /// Applies a translation after self's transformation and returns the resulting transform. + #[inline] + #[must_use] + pub fn then_translate(&self, v: Vector2D<T, Dst>) -> Self + where + T: Copy + Add<Output = T> + Mul<Output = T>, + { + self.then(&Transform2D::translation(v.x, v.y)) + } + + /// Applies a translation before self's transformation and returns the resulting transform. + #[inline] + #[must_use] + pub fn pre_translate(&self, v: Vector2D<T, Src>) -> Self + where + T: Copy + Add<Output = T> + Mul<Output = T>, + { + Transform2D::translation(v.x, v.y).then(self) + } +} + +/// Methods for creating and combining rotation transformations +impl<T, Src, Dst> Transform2D<T, Src, Dst> +where + T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Zero + Trig, +{ + /// Returns a rotation transform. + #[inline] + pub fn rotation(theta: Angle<T>) -> Self { + let _0 = Zero::zero(); + let cos = theta.get().cos(); + let sin = theta.get().sin(); + Transform2D::new( + cos, sin, + _0 - sin, cos, + _0, _0 + ) + } + + /// Applies a rotation after self's transformation and returns the resulting transform. + #[inline] + #[must_use] + pub fn then_rotate(&self, theta: Angle<T>) -> Self { + self.then(&Transform2D::rotation(theta)) + } + + /// Applies a rotation before self's transformation and returns the resulting transform. + #[inline] + #[must_use] + pub fn pre_rotate(&self, theta: Angle<T>) -> Self { + Transform2D::rotation(theta).then(self) + } +} + +/// Methods for creating and combining scale transformations +impl<T, Src, Dst> Transform2D<T, Src, Dst> { + /// Create a 2d scale transform: + /// + /// ```text + /// x 0 + /// 0 y + /// 0 0 + /// ``` + #[inline] + pub fn scale(x: T, y: T) -> Self + where + T: Zero, + { + let _0 = || Zero::zero(); + + Self::new( + x, _0(), + _0(), y, + _0(), _0(), + ) + } + + /// Applies a scale after self's transformation and returns the resulting transform. + #[inline] + #[must_use] + pub fn then_scale(&self, x: T, y: T) -> Self + where + T: Copy + Add<Output = T> + Mul<Output = T> + Zero, + { + self.then(&Transform2D::scale(x, y)) + } + + /// Applies a scale before self's transformation and returns the resulting transform. + #[inline] + #[must_use] + pub fn pre_scale(&self, x: T, y: T) -> Self + where + T: Copy + Mul<Output = T>, + { + Transform2D::new( + self.m11 * x, self.m12 * x, + self.m21 * y, self.m22 * y, + self.m31, self.m32 + ) + } +} + +/// Methods for apply transformations to objects +impl<T, Src, Dst> Transform2D<T, Src, Dst> +where + T: Copy + Add<Output = T> + Mul<Output = T>, +{ + /// Returns the given point transformed by this transform. + #[inline] + #[must_use] + pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T, Dst> { + Point2D::new( + point.x * self.m11 + point.y * self.m21 + self.m31, + point.x * self.m12 + point.y * self.m22 + self.m32 + ) + } + + /// Returns the given vector transformed by this matrix. + #[inline] + #[must_use] + pub fn transform_vector(&self, vec: Vector2D<T, Src>) -> Vector2D<T, Dst> { + vec2(vec.x * self.m11 + vec.y * self.m21, + vec.x * self.m12 + vec.y * self.m22) + } + + /// Returns a rectangle that encompasses the result of transforming the given rectangle by this + /// transform. + #[inline] + #[must_use] + pub fn outer_transformed_rect(&self, rect: &Rect<T, Src>) -> Rect<T, Dst> + where + T: Sub<Output = T> + Zero + PartialOrd, + { + let min = rect.min(); + let max = rect.max(); + Rect::from_points(&[ + self.transform_point(min), + self.transform_point(max), + self.transform_point(point2(max.x, min.y)), + self.transform_point(point2(min.x, max.y)), + ]) + } + + + /// Returns a box that encompasses the result of transforming the given box by this + /// transform. + #[inline] + #[must_use] + pub fn outer_transformed_box(&self, b: &Box2D<T, Src>) -> Box2D<T, Dst> + where + T: Sub<Output = T> + Zero + PartialOrd, + { + Box2D::from_points(&[ + self.transform_point(b.min), + self.transform_point(b.max), + self.transform_point(point2(b.max.x, b.min.y)), + self.transform_point(point2(b.min.x, b.max.y)), + ]) + } +} + + +impl<T, Src, Dst> Transform2D<T, Src, Dst> +where + T: Copy + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + PartialEq + Zero + One, +{ + /// Computes and returns the determinant of this transform. + pub fn determinant(&self) -> T { + self.m11 * self.m22 - self.m12 * self.m21 + } + + /// Returns whether it is possible to compute the inverse transform. + #[inline] + pub fn is_invertible(&self) -> bool { + self.determinant() != Zero::zero() + } + + /// Returns the inverse transform if possible. + #[must_use] + pub fn inverse(&self) -> Option<Transform2D<T, Dst, Src>> { + let det = self.determinant(); + + let _0: T = Zero::zero(); + let _1: T = One::one(); + + if det == _0 { + return None; + } + + let inv_det = _1 / det; + Some(Transform2D::new( + inv_det * self.m22, + inv_det * (_0 - self.m12), + inv_det * (_0 - self.m21), + inv_det * self.m11, + inv_det * (self.m21 * self.m32 - self.m22 * self.m31), + inv_det * (self.m31 * self.m12 - self.m11 * self.m32), + )) + } +} + +impl <T, Src, Dst> Default for Transform2D<T, Src, Dst> + where T: Zero + One +{ + /// Returns the [identity transform](#method.identity). + fn default() -> Self { + Self::identity() + } +} + +impl<T: ApproxEq<T>, Src, Dst> ApproxEq<T> for Transform2D<T, Src, Dst> { + #[inline] + fn approx_epsilon() -> T { T::approx_epsilon() } + + /// Returns true is this transform is approximately equal to the other one, using + /// a provided epsilon value. + fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool { + self.m11.approx_eq_eps(&other.m11, eps) && self.m12.approx_eq_eps(&other.m12, eps) && + self.m21.approx_eq_eps(&other.m21, eps) && self.m22.approx_eq_eps(&other.m22, eps) && + self.m31.approx_eq_eps(&other.m31, eps) && self.m32.approx_eq_eps(&other.m32, eps) + } +} + +impl<T, Src, Dst> fmt::Debug for Transform2D<T, Src, Dst> +where T: Copy + fmt::Debug + + PartialEq + + One + Zero { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_identity() { + write!(f, "[I]") + } else { + self.to_array().fmt(f) + } + } +} + +#[cfg(feature = "mint")] +impl<T, Src, Dst> From<mint::RowMatrix3x2<T>> for Transform2D<T, Src, Dst> { + fn from(m: mint::RowMatrix3x2<T>) -> Self { + Transform2D { + m11: m.x.x, m12: m.x.y, + m21: m.y.x, m22: m.y.y, + m31: m.z.x, m32: m.z.y, + _unit: PhantomData, + } + } +} +#[cfg(feature = "mint")] +impl<T, Src, Dst> Into<mint::RowMatrix3x2<T>> for Transform2D<T, Src, Dst> { + fn into(self) -> mint::RowMatrix3x2<T> { + mint::RowMatrix3x2 { + x: mint::Vector2 { x: self.m11, y: self.m12 }, + y: mint::Vector2 { x: self.m21, y: self.m22 }, + z: mint::Vector2 { x: self.m31, y: self.m32 }, + } + } +} + + +#[cfg(test)] +mod test { + use super::*; + use crate::default; + use crate::approxeq::ApproxEq; + #[cfg(feature = "mint")] + use mint; + + use core::f32::consts::FRAC_PI_2; + + type Mat = default::Transform2D<f32>; + + fn rad(v: f32) -> Angle<f32> { Angle::radians(v) } + + #[test] + pub fn test_translation() { + let t1 = Mat::translation(1.0, 2.0); + let t2 = Mat::identity().pre_translate(vec2(1.0, 2.0)); + let t3 = Mat::identity().then_translate(vec2(1.0, 2.0)); + assert_eq!(t1, t2); + assert_eq!(t1, t3); + + assert_eq!(t1.transform_point(Point2D::new(1.0, 1.0)), Point2D::new(2.0, 3.0)); + + assert_eq!(t1.then(&t1), Mat::translation(2.0, 4.0)); + } + + #[test] + pub fn test_rotation() { + let r1 = Mat::rotation(rad(FRAC_PI_2)); + let r2 = Mat::identity().pre_rotate(rad(FRAC_PI_2)); + let r3 = Mat::identity().then_rotate(rad(FRAC_PI_2)); + assert_eq!(r1, r2); + assert_eq!(r1, r3); + + assert!(r1.transform_point(Point2D::new(1.0, 2.0)).approx_eq(&Point2D::new(-2.0, 1.0))); + + assert!(r1.then(&r1).approx_eq(&Mat::rotation(rad(FRAC_PI_2*2.0)))); + } + + #[test] + pub fn test_scale() { + let s1 = Mat::scale(2.0, 3.0); + let s2 = Mat::identity().pre_scale(2.0, 3.0); + let s3 = Mat::identity().then_scale(2.0, 3.0); + assert_eq!(s1, s2); + assert_eq!(s1, s3); + + assert!(s1.transform_point(Point2D::new(2.0, 2.0)).approx_eq(&Point2D::new(4.0, 6.0))); + } + + + #[test] + pub fn test_pre_then_scale() { + let m = Mat::rotation(rad(FRAC_PI_2)).then_translate(vec2(6.0, 7.0)); + let s = Mat::scale(2.0, 3.0); + assert_eq!(m.then(&s), m.then_scale(2.0, 3.0)); + } + + #[test] + pub fn test_inverse_simple() { + let m1 = Mat::identity(); + let m2 = m1.inverse().unwrap(); + assert!(m1.approx_eq(&m2)); + } + + #[test] + pub fn test_inverse_scale() { + let m1 = Mat::scale(1.5, 0.3); + let m2 = m1.inverse().unwrap(); + assert!(m1.then(&m2).approx_eq(&Mat::identity())); + assert!(m2.then(&m1).approx_eq(&Mat::identity())); + } + + #[test] + pub fn test_inverse_translate() { + let m1 = Mat::translation(-132.0, 0.3); + let m2 = m1.inverse().unwrap(); + assert!(m1.then(&m2).approx_eq(&Mat::identity())); + assert!(m2.then(&m1).approx_eq(&Mat::identity())); + } + + #[test] + fn test_inverse_none() { + assert!(Mat::scale(2.0, 0.0).inverse().is_none()); + assert!(Mat::scale(2.0, 2.0).inverse().is_some()); + } + + #[test] + pub fn test_pre_post() { + let m1 = default::Transform2D::identity().then_scale(1.0, 2.0).then_translate(vec2(1.0, 2.0)); + let m2 = default::Transform2D::identity().pre_translate(vec2(1.0, 2.0)).pre_scale(1.0, 2.0); + assert!(m1.approx_eq(&m2)); + + let r = Mat::rotation(rad(FRAC_PI_2)); + let t = Mat::translation(2.0, 3.0); + + let a = Point2D::new(1.0, 1.0); + + assert!(r.then(&t).transform_point(a).approx_eq(&Point2D::new(1.0, 4.0))); + assert!(t.then(&r).transform_point(a).approx_eq(&Point2D::new(-4.0, 3.0))); + assert!(t.then(&r).transform_point(a).approx_eq(&r.transform_point(t.transform_point(a)))); + } + + #[test] + fn test_size_of() { + use core::mem::size_of; + assert_eq!(size_of::<default::Transform2D<f32>>(), 6*size_of::<f32>()); + assert_eq!(size_of::<default::Transform2D<f64>>(), 6*size_of::<f64>()); + } + + #[test] + pub fn test_is_identity() { + let m1 = default::Transform2D::identity(); + assert!(m1.is_identity()); + let m2 = m1.then_translate(vec2(0.1, 0.0)); + assert!(!m2.is_identity()); + } + + #[test] + pub fn test_transform_vector() { + // Translation does not apply to vectors. + let m1 = Mat::translation(1.0, 1.0); + let v1 = vec2(10.0, -10.0); + assert_eq!(v1, m1.transform_vector(v1)); + } + + #[cfg(feature = "mint")] + #[test] + pub fn test_mint() { + let m1 = Mat::rotation(rad(FRAC_PI_2)); + let mm: mint::RowMatrix3x2<_> = m1.into(); + let m2 = Mat::from(mm); + + assert_eq!(m1, m2); + } +} diff --git a/third_party/rust/euclid/src/transform3d.rs b/third_party/rust/euclid/src/transform3d.rs new file mode 100644 index 0000000000..2ea4730ad2 --- /dev/null +++ b/third_party/rust/euclid/src/transform3d.rs @@ -0,0 +1,1436 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![cfg_attr(feature = "cargo-clippy", allow(just_underscores_and_digits))] + +use super::{UnknownUnit, Angle}; +use crate::approxeq::ApproxEq; +use crate::homogen::HomogeneousVector; +#[cfg(feature = "mint")] +use mint; +use crate::trig::Trig; +use crate::point::{Point2D, point2, Point3D, point3}; +use crate::vector::{Vector2D, Vector3D, vec2, vec3}; +use crate::rect::Rect; +use crate::box2d::Box2D; +use crate::box3d::Box3D; +use crate::transform2d::Transform2D; +use crate::scale::Scale; +use crate::num::{One, Zero}; +use core::ops::{Add, Mul, Sub, Div, Neg}; +use core::marker::PhantomData; +use core::fmt; +use core::cmp::{Eq, PartialEq}; +use core::hash::{Hash}; +use num_traits::NumCast; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A 3d transform stored as a column-major 4 by 4 matrix. +/// +/// Transforms can be parametrized over the source and destination units, to describe a +/// transformation from a space to another. +/// For example, `Transform3D<f32, WorldSpace, ScreenSpace>::transform_point3d` +/// takes a `Point3D<f32, WorldSpace>` and returns a `Point3D<f32, ScreenSpace>`. +/// +/// Transforms expose a set of convenience methods for pre- and post-transformations. +/// Pre-transformations (`pre_*` methods) correspond to adding an operation that is +/// applied before the rest of the transformation, while post-transformations (`then_*` +/// methods) add an operation that is applied after. +/// +/// When translating Transform3D into general matrix representations, consider that the +/// representation follows the column major notation with column vectors. +/// +/// ```text +/// |x'| | m11 m12 m13 m14 | |x| +/// |y'| | m21 m22 m23 m24 | |y| +/// |z'| = | m31 m32 m33 m34 | x |y| +/// |w | | m41 m42 m43 m44 | |1| +/// ``` +/// +/// The translation terms are m41, m42 and m43. +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) +)] +pub struct Transform3D<T, Src, Dst> { + pub m11: T, pub m12: T, pub m13: T, pub m14: T, + pub m21: T, pub m22: T, pub m23: T, pub m24: T, + pub m31: T, pub m32: T, pub m33: T, pub m34: T, + pub m41: T, pub m42: T, pub m43: T, pub m44: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + + +#[cfg(feature = "arbitrary")] +impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Transform3D<T, Src, Dst> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> + { + let (m11, m12, m13, m14) = arbitrary::Arbitrary::arbitrary(u)?; + let (m21, m22, m23, m24) = arbitrary::Arbitrary::arbitrary(u)?; + let (m31, m32, m33, m34) = arbitrary::Arbitrary::arbitrary(u)?; + let (m41, m42, m43, m44) = arbitrary::Arbitrary::arbitrary(u)?; + + Ok(Transform3D { + m11, + m12, + m13, + m14, + m21, + m22, + m23, + m24, + m31, + m32, + m33, + m34, + m41, + m42, + m43, + m44, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, Src, Dst> Zeroable for Transform3D<T, Src, Dst> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Transform3D<T, Src, Dst> {} + +impl<T: Copy, Src, Dst> Copy for Transform3D<T, Src, Dst> {} + +impl<T: Clone, Src, Dst> Clone for Transform3D<T, Src, Dst> { + fn clone(&self) -> Self { + Transform3D { + m11: self.m11.clone(), + m12: self.m12.clone(), + m13: self.m13.clone(), + m14: self.m14.clone(), + m21: self.m21.clone(), + m22: self.m22.clone(), + m23: self.m23.clone(), + m24: self.m24.clone(), + m31: self.m31.clone(), + m32: self.m32.clone(), + m33: self.m33.clone(), + m34: self.m34.clone(), + m41: self.m41.clone(), + m42: self.m42.clone(), + m43: self.m43.clone(), + m44: self.m44.clone(), + _unit: PhantomData, + } + } +} + +impl<T, Src, Dst> Eq for Transform3D<T, Src, Dst> where T: Eq {} + +impl<T, Src, Dst> PartialEq for Transform3D<T, Src, Dst> + where T: PartialEq +{ + fn eq(&self, other: &Self) -> bool { + self.m11 == other.m11 && + self.m12 == other.m12 && + self.m13 == other.m13 && + self.m14 == other.m14 && + self.m21 == other.m21 && + self.m22 == other.m22 && + self.m23 == other.m23 && + self.m24 == other.m24 && + self.m31 == other.m31 && + self.m32 == other.m32 && + self.m33 == other.m33 && + self.m34 == other.m34 && + self.m41 == other.m41 && + self.m42 == other.m42 && + self.m43 == other.m43 && + self.m44 == other.m44 + } +} + +impl<T, Src, Dst> Hash for Transform3D<T, Src, Dst> + where T: Hash +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.m11.hash(h); + self.m12.hash(h); + self.m13.hash(h); + self.m14.hash(h); + self.m21.hash(h); + self.m22.hash(h); + self.m23.hash(h); + self.m24.hash(h); + self.m31.hash(h); + self.m32.hash(h); + self.m33.hash(h); + self.m34.hash(h); + self.m41.hash(h); + self.m42.hash(h); + self.m43.hash(h); + self.m44.hash(h); + } +} + + +impl<T, Src, Dst> Transform3D<T, Src, Dst> { + /// Create a transform specifying all of it's component as a 4 by 4 matrix. + /// + /// Components are specified following column-major-column-vector matrix notation. + /// For example, the translation terms m41, m42, m43 are the 13rd, 14th and 15th parameters. + /// + /// ``` + /// use euclid::default::Transform3D; + /// let tx = 1.0; + /// let ty = 2.0; + /// let tz = 3.0; + /// let translation = Transform3D::new( + /// 1.0, 0.0, 0.0, 0.0, + /// 0.0, 1.0, 0.0, 0.0, + /// 0.0, 0.0, 1.0, 0.0, + /// tx, ty, tz, 1.0, + /// ); + /// ``` + #[inline] + #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] + pub const fn new( + m11: T, m12: T, m13: T, m14: T, + m21: T, m22: T, m23: T, m24: T, + m31: T, m32: T, m33: T, m34: T, + m41: T, m42: T, m43: T, m44: T, + ) -> Self { + Transform3D { + m11, m12, m13, m14, + m21, m22, m23, m24, + m31, m32, m33, m34, + m41, m42, m43, m44, + _unit: PhantomData, + } + } + + /// Create a transform representing a 2d transformation from the components + /// of a 2 by 3 matrix transformation. + /// + /// Components follow the column-major-column-vector notation (m41 and m42 + /// representating the translation terms). + /// + /// ```text + /// m11 m12 0 0 + /// m21 m22 0 0 + /// 0 0 1 0 + /// m41 m42 0 1 + /// ``` + #[inline] + pub fn new_2d(m11: T, m12: T, m21: T, m22: T, m41: T, m42: T) -> Self + where + T: Zero + One, + { + let _0 = || T::zero(); + let _1 = || T::one(); + + Self::new( + m11, m12, _0(), _0(), + m21, m22, _0(), _0(), + _0(), _0(), _1(), _0(), + m41, m42, _0(), _1() + ) + } + + + /// Returns `true` if this transform can be represented with a `Transform2D`. + /// + /// See <https://drafts.csswg.org/css-transforms/#2d-transform> + #[inline] + pub fn is_2d(&self) -> bool + where + T: Zero + One + PartialEq, + { + let (_0, _1): (T, T) = (Zero::zero(), One::one()); + self.m31 == _0 && self.m32 == _0 && + self.m13 == _0 && self.m23 == _0 && + self.m43 == _0 && self.m14 == _0 && + self.m24 == _0 && self.m34 == _0 && + self.m33 == _1 && self.m44 == _1 + } +} + +impl<T: Copy, Src, Dst> Transform3D<T, Src, Dst> { + /// Returns an array containing this transform's terms. + /// + /// The terms are laid out in the same order as they are + /// specified in `Transform3D::new`, that is following the + /// column-major-column-vector matrix notation. + /// + /// For example the translation terms are found on the + /// 13th, 14th and 15th slots of the array. + #[inline] + pub fn to_array(&self) -> [T; 16] { + [ + self.m11, self.m12, self.m13, self.m14, + self.m21, self.m22, self.m23, self.m24, + self.m31, self.m32, self.m33, self.m34, + self.m41, self.m42, self.m43, self.m44 + ] + } + + /// Returns an array containing this transform's terms transposed. + /// + /// The terms are laid out in transposed order from the same order of + /// `Transform3D::new` and `Transform3D::to_array`, that is following + /// the row-major-column-vector matrix notation. + /// + /// For example the translation terms are found at indices 3, 7 and 11 + /// of the array. + #[inline] + pub fn to_array_transposed(&self) -> [T; 16] { + [ + self.m11, self.m21, self.m31, self.m41, + self.m12, self.m22, self.m32, self.m42, + self.m13, self.m23, self.m33, self.m43, + self.m14, self.m24, self.m34, self.m44 + ] + } + + /// Equivalent to `to_array` with elements packed four at a time + /// in an array of arrays. + #[inline] + pub fn to_arrays(&self) -> [[T; 4]; 4] { + [ + [self.m11, self.m12, self.m13, self.m14], + [self.m21, self.m22, self.m23, self.m24], + [self.m31, self.m32, self.m33, self.m34], + [self.m41, self.m42, self.m43, self.m44] + ] + } + + /// Equivalent to `to_array_transposed` with elements packed + /// four at a time in an array of arrays. + #[inline] + pub fn to_arrays_transposed(&self) -> [[T; 4]; 4] { + [ + [self.m11, self.m21, self.m31, self.m41], + [self.m12, self.m22, self.m32, self.m42], + [self.m13, self.m23, self.m33, self.m43], + [self.m14, self.m24, self.m34, self.m44] + ] + } + + /// Create a transform providing its components via an array + /// of 16 elements instead of as individual parameters. + /// + /// The order of the components corresponds to the + /// column-major-column-vector matrix notation (the same order + /// as `Transform3D::new`). + #[inline] + pub fn from_array(array: [T; 16]) -> Self { + Self::new( + array[0], array[1], array[2], array[3], + array[4], array[5], array[6], array[7], + array[8], array[9], array[10], array[11], + array[12], array[13], array[14], array[15], + ) + } + + /// Equivalent to `from_array` with elements packed four at a time + /// in an array of arrays. + /// + /// The order of the components corresponds to the + /// column-major-column-vector matrix notation (the same order + /// as `Transform3D::new`). + #[inline] + pub fn from_arrays(array: [[T; 4]; 4]) -> Self { + Self::new( + array[0][0], array[0][1], array[0][2], array[0][3], + array[1][0], array[1][1], array[1][2], array[1][3], + array[2][0], array[2][1], array[2][2], array[2][3], + array[3][0], array[3][1], array[3][2], array[3][3], + ) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(m: &Transform3D<T, UnknownUnit, UnknownUnit>) -> Self { + Transform3D::new( + m.m11, m.m12, m.m13, m.m14, + m.m21, m.m22, m.m23, m.m24, + m.m31, m.m32, m.m33, m.m34, + m.m41, m.m42, m.m43, m.m44, + ) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Transform3D<T, UnknownUnit, UnknownUnit> { + Transform3D::new( + self.m11, self.m12, self.m13, self.m14, + self.m21, self.m22, self.m23, self.m24, + self.m31, self.m32, self.m33, self.m34, + self.m41, self.m42, self.m43, self.m44, + ) + } + + /// Returns the same transform with a different source unit. + #[inline] + pub fn with_source<NewSrc>(&self) -> Transform3D<T, NewSrc, Dst> { + Transform3D::new( + self.m11, self.m12, self.m13, self.m14, + self.m21, self.m22, self.m23, self.m24, + self.m31, self.m32, self.m33, self.m34, + self.m41, self.m42, self.m43, self.m44, + ) + } + + /// Returns the same transform with a different destination unit. + #[inline] + pub fn with_destination<NewDst>(&self) -> Transform3D<T, Src, NewDst> { + Transform3D::new( + self.m11, self.m12, self.m13, self.m14, + self.m21, self.m22, self.m23, self.m24, + self.m31, self.m32, self.m33, self.m34, + self.m41, self.m42, self.m43, self.m44, + ) + } + + /// Create a 2D transform picking the relevant terms from this transform. + /// + /// This method assumes that self represents a 2d transformation, callers + /// should check that [`self.is_2d()`] returns `true` beforehand. + /// + /// [`self.is_2d()`]: #method.is_2d + pub fn to_2d(&self) -> Transform2D<T, Src, Dst> { + Transform2D::new( + self.m11, self.m12, + self.m21, self.m22, + self.m41, self.m42 + ) + } +} + +impl <T, Src, Dst> Transform3D<T, Src, Dst> +where + T: Zero + One, +{ + /// Creates an identity matrix: + /// + /// ```text + /// 1 0 0 0 + /// 0 1 0 0 + /// 0 0 1 0 + /// 0 0 0 1 + /// ``` + #[inline] + pub fn identity() -> Self { + Self::translation(T::zero(), T::zero(), T::zero()) + } + + /// Intentional not public, because it checks for exact equivalence + /// while most consumers will probably want some sort of approximate + /// equivalence to deal with floating-point errors. + #[inline] + fn is_identity(&self) -> bool + where + T: PartialEq, + { + *self == Self::identity() + } + + /// Create a 2d skew transform. + /// + /// See <https://drafts.csswg.org/css-transforms/#funcdef-skew> + pub fn skew(alpha: Angle<T>, beta: Angle<T>) -> Self + where + T: Trig, + { + let _0 = || T::zero(); + let _1 = || T::one(); + let (sx, sy) = (beta.radians.tan(), alpha.radians.tan()); + + Self::new( + _1(), sx, _0(), _0(), + sy, _1(), _0(), _0(), + _0(), _0(), _1(), _0(), + _0(), _0(), _0(), _1(), + ) + } + + /// Create a simple perspective transform, projecting to the plane `z = -d`. + /// + /// ```text + /// 1 0 0 0 + /// 0 1 0 0 + /// 0 0 1 -1/d + /// 0 0 0 1 + /// ``` + /// + /// See <https://drafts.csswg.org/css-transforms-2/#PerspectiveDefined>. + pub fn perspective(d: T) -> Self + where + T: Neg<Output = T> + Div<Output = T>, + { + let _0 = || T::zero(); + let _1 = || T::one(); + + Self::new( + _1(), _0(), _0(), _0(), + _0(), _1(), _0(), _0(), + _0(), _0(), _1(), -_1() / d, + _0(), _0(), _0(), _1(), + ) + } +} + + +/// Methods for combining generic transformations +impl <T, Src, Dst> Transform3D<T, Src, Dst> +where + T: Copy + Add<Output = T> + Mul<Output = T>, +{ + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies after self's transformation. + /// + /// Assuming row vectors, this is equivalent to self * mat + #[must_use] + pub fn then<NewDst>(&self, other: &Transform3D<T, Dst, NewDst>) -> Transform3D<T, Src, NewDst> { + Transform3D::new( + self.m11 * other.m11 + self.m12 * other.m21 + self.m13 * other.m31 + self.m14 * other.m41, + self.m11 * other.m12 + self.m12 * other.m22 + self.m13 * other.m32 + self.m14 * other.m42, + self.m11 * other.m13 + self.m12 * other.m23 + self.m13 * other.m33 + self.m14 * other.m43, + self.m11 * other.m14 + self.m12 * other.m24 + self.m13 * other.m34 + self.m14 * other.m44, + + self.m21 * other.m11 + self.m22 * other.m21 + self.m23 * other.m31 + self.m24 * other.m41, + self.m21 * other.m12 + self.m22 * other.m22 + self.m23 * other.m32 + self.m24 * other.m42, + self.m21 * other.m13 + self.m22 * other.m23 + self.m23 * other.m33 + self.m24 * other.m43, + self.m21 * other.m14 + self.m22 * other.m24 + self.m23 * other.m34 + self.m24 * other.m44, + + self.m31 * other.m11 + self.m32 * other.m21 + self.m33 * other.m31 + self.m34 * other.m41, + self.m31 * other.m12 + self.m32 * other.m22 + self.m33 * other.m32 + self.m34 * other.m42, + self.m31 * other.m13 + self.m32 * other.m23 + self.m33 * other.m33 + self.m34 * other.m43, + self.m31 * other.m14 + self.m32 * other.m24 + self.m33 * other.m34 + self.m34 * other.m44, + + self.m41 * other.m11 + self.m42 * other.m21 + self.m43 * other.m31 + self.m44 * other.m41, + self.m41 * other.m12 + self.m42 * other.m22 + self.m43 * other.m32 + self.m44 * other.m42, + self.m41 * other.m13 + self.m42 * other.m23 + self.m43 * other.m33 + self.m44 * other.m43, + self.m41 * other.m14 + self.m42 * other.m24 + self.m43 * other.m34 + self.m44 * other.m44, + ) + } +} + +/// Methods for creating and combining translation transformations +impl <T, Src, Dst> Transform3D<T, Src, Dst> +where + T: Zero + One, +{ + /// Create a 3d translation transform: + /// + /// ```text + /// 1 0 0 0 + /// 0 1 0 0 + /// 0 0 1 0 + /// x y z 1 + /// ``` + #[inline] + pub fn translation(x: T, y: T, z: T) -> Self { + let _0 = || T::zero(); + let _1 = || T::one(); + + Self::new( + _1(), _0(), _0(), _0(), + _0(), _1(), _0(), _0(), + _0(), _0(), _1(), _0(), + x, y, z, _1(), + ) + } + + /// Returns a transform with a translation applied before self's transformation. + #[must_use] + pub fn pre_translate(&self, v: Vector3D<T, Src>) -> Self + where + T: Copy + Add<Output = T> + Mul<Output = T>, + { + Transform3D::translation(v.x, v.y, v.z).then(self) + } + + /// Returns a transform with a translation applied after self's transformation. + #[must_use] + pub fn then_translate(&self, v: Vector3D<T, Dst>) -> Self + where + T: Copy + Add<Output = T> + Mul<Output = T>, + { + self.then(&Transform3D::translation(v.x, v.y, v.z)) + } +} + +/// Methods for creating and combining rotation transformations +impl<T, Src, Dst> Transform3D<T, Src, Dst> +where + T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Zero + One + Trig, +{ + /// Create a 3d rotation transform from an angle / axis. + /// The supplied axis must be normalized. + pub fn rotation(x: T, y: T, z: T, theta: Angle<T>) -> Self { + let (_0, _1): (T, T) = (Zero::zero(), One::one()); + let _2 = _1 + _1; + + let xx = x * x; + let yy = y * y; + let zz = z * z; + + let half_theta = theta.get() / _2; + let sc = half_theta.sin() * half_theta.cos(); + let sq = half_theta.sin() * half_theta.sin(); + + Transform3D::new( + _1 - _2 * (yy + zz) * sq, + _2 * (x * y * sq + z * sc), + _2 * (x * z * sq - y * sc), + _0, + + + _2 * (x * y * sq - z * sc), + _1 - _2 * (xx + zz) * sq, + _2 * (y * z * sq + x * sc), + _0, + + _2 * (x * z * sq + y * sc), + _2 * (y * z * sq - x * sc), + _1 - _2 * (xx + yy) * sq, + _0, + + _0, + _0, + _0, + _1 + ) + } + + /// Returns a transform with a rotation applied after self's transformation. + #[must_use] + pub fn then_rotate(&self, x: T, y: T, z: T, theta: Angle<T>) -> Self { + self.then(&Transform3D::rotation(x, y, z, theta)) + } + + /// Returns a transform with a rotation applied before self's transformation. + #[must_use] + pub fn pre_rotate(&self, x: T, y: T, z: T, theta: Angle<T>) -> Self { + Transform3D::rotation(x, y, z, theta).then(self) + } +} + +/// Methods for creating and combining scale transformations +impl<T, Src, Dst> Transform3D<T, Src, Dst> +where + T: Zero + One, +{ + /// Create a 3d scale transform: + /// + /// ```text + /// x 0 0 0 + /// 0 y 0 0 + /// 0 0 z 0 + /// 0 0 0 1 + /// ``` + #[inline] + pub fn scale(x: T, y: T, z: T) -> Self { + let _0 = || T::zero(); + let _1 = || T::one(); + + Self::new( + x, _0(), _0(), _0(), + _0(), y, _0(), _0(), + _0(), _0(), z, _0(), + _0(), _0(), _0(), _1(), + ) + } + + /// Returns a transform with a scale applied before self's transformation. + #[must_use] + pub fn pre_scale(&self, x: T, y: T, z: T) -> Self + where + T: Copy + Add<Output = T> + Mul<Output = T>, + { + Transform3D::new( + self.m11 * x, self.m12 * x, self.m13 * x, self.m14 * x, + self.m21 * y, self.m22 * y, self.m23 * y, self.m24 * y, + self.m31 * z, self.m32 * z, self.m33 * z, self.m34 * z, + self.m41 , self.m42, self.m43, self.m44 + ) + } + + /// Returns a transform with a scale applied after self's transformation. + #[must_use] + pub fn then_scale(&self, x: T, y: T, z: T) -> Self + where + T: Copy + Add<Output = T> + Mul<Output = T>, + { + self.then(&Transform3D::scale(x, y, z)) + } +} + +/// Methods for apply transformations to objects +impl<T, Src, Dst> Transform3D<T, Src, Dst> +where + T: Copy + Add<Output = T> + Mul<Output = T>, +{ + /// Returns the homogeneous vector corresponding to the transformed 2d point. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_point2d_homogeneous( + &self, p: Point2D<T, Src> + ) -> HomogeneousVector<T, Dst> { + let x = p.x * self.m11 + p.y * self.m21 + self.m41; + let y = p.x * self.m12 + p.y * self.m22 + self.m42; + let z = p.x * self.m13 + p.y * self.m23 + self.m43; + let w = p.x * self.m14 + p.y * self.m24 + self.m44; + + HomogeneousVector::new(x, y, z, w) + } + + /// Returns the given 2d point transformed by this transform, if the transform makes sense, + /// or `None` otherwise. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_point2d(&self, p: Point2D<T, Src>) -> Option<Point2D<T, Dst>> + where + T: Div<Output = T> + Zero + PartialOrd, + { + //Note: could use `transform_point2d_homogeneous()` but it would waste the calculus of `z` + let w = p.x * self.m14 + p.y * self.m24 + self.m44; + if w > T::zero() { + let x = p.x * self.m11 + p.y * self.m21 + self.m41; + let y = p.x * self.m12 + p.y * self.m22 + self.m42; + + Some(Point2D::new(x / w, y / w)) + } else { + None + } + } + + /// Returns the given 2d vector transformed by this matrix. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_vector2d(&self, v: Vector2D<T, Src>) -> Vector2D<T, Dst> { + vec2( + v.x * self.m11 + v.y * self.m21, + v.x * self.m12 + v.y * self.m22, + ) + } + + /// Returns the homogeneous vector corresponding to the transformed 3d point. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_point3d_homogeneous( + &self, p: Point3D<T, Src> + ) -> HomogeneousVector<T, Dst> { + let x = p.x * self.m11 + p.y * self.m21 + p.z * self.m31 + self.m41; + let y = p.x * self.m12 + p.y * self.m22 + p.z * self.m32 + self.m42; + let z = p.x * self.m13 + p.y * self.m23 + p.z * self.m33 + self.m43; + let w = p.x * self.m14 + p.y * self.m24 + p.z * self.m34 + self.m44; + + HomogeneousVector::new(x, y, z, w) + } + + /// Returns the given 3d point transformed by this transform, if the transform makes sense, + /// or `None` otherwise. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_point3d(&self, p: Point3D<T, Src>) -> Option<Point3D<T, Dst>> + where + T: Div<Output = T> + Zero + PartialOrd, + { + self.transform_point3d_homogeneous(p).to_point3d() + } + + /// Returns the given 3d vector transformed by this matrix. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_vector3d(&self, v: Vector3D<T, Src>) -> Vector3D<T, Dst> { + vec3( + v.x * self.m11 + v.y * self.m21 + v.z * self.m31, + v.x * self.m12 + v.y * self.m22 + v.z * self.m32, + v.x * self.m13 + v.y * self.m23 + v.z * self.m33, + ) + } + + /// Returns a rectangle that encompasses the result of transforming the given rectangle by this + /// transform, if the transform makes sense for it, or `None` otherwise. + pub fn outer_transformed_rect(&self, rect: &Rect<T, Src>) -> Option<Rect<T, Dst>> + where + T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd, + { + let min = rect.min(); + let max = rect.max(); + Some(Rect::from_points(&[ + self.transform_point2d(min)?, + self.transform_point2d(max)?, + self.transform_point2d(point2(max.x, min.y))?, + self.transform_point2d(point2(min.x, max.y))?, + ])) + } + + /// Returns a 2d box that encompasses the result of transforming the given box by this + /// transform, if the transform makes sense for it, or `None` otherwise. + pub fn outer_transformed_box2d(&self, b: &Box2D<T, Src>) -> Option<Box2D<T, Dst>> + where + T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd, + { + Some(Box2D::from_points(&[ + self.transform_point2d(b.min)?, + self.transform_point2d(b.max)?, + self.transform_point2d(point2(b.max.x, b.min.y))?, + self.transform_point2d(point2(b.min.x, b.max.y))?, + ])) + } + + /// Returns a 3d box that encompasses the result of transforming the given box by this + /// transform, if the transform makes sense for it, or `None` otherwise. + pub fn outer_transformed_box3d(&self, b: &Box3D<T, Src>) -> Option<Box3D<T, Dst>> + where + T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd, + { + Some(Box3D::from_points(&[ + self.transform_point3d(point3(b.min.x, b.min.y, b.min.z))?, + self.transform_point3d(point3(b.min.x, b.min.y, b.max.z))?, + self.transform_point3d(point3(b.min.x, b.max.y, b.min.z))?, + self.transform_point3d(point3(b.min.x, b.max.y, b.max.z))?, + self.transform_point3d(point3(b.max.x, b.min.y, b.min.z))?, + self.transform_point3d(point3(b.max.x, b.min.y, b.max.z))?, + self.transform_point3d(point3(b.max.x, b.max.y, b.min.z))?, + self.transform_point3d(point3(b.max.x, b.max.y, b.max.z))?, + ])) + } +} + + +impl <T, Src, Dst> Transform3D<T, Src, Dst> +where T: Copy + + Add<T, Output=T> + + Sub<T, Output=T> + + Mul<T, Output=T> + + Div<T, Output=T> + + Neg<Output=T> + + PartialOrd + + One + Zero { + + /// Create an orthogonal projection transform. + pub fn ortho(left: T, right: T, + bottom: T, top: T, + near: T, far: T) -> Self { + let tx = -((right + left) / (right - left)); + let ty = -((top + bottom) / (top - bottom)); + let tz = -((far + near) / (far - near)); + + let (_0, _1): (T, T) = (Zero::zero(), One::one()); + let _2 = _1 + _1; + Transform3D::new( + _2 / (right - left), _0 , _0 , _0, + _0 , _2 / (top - bottom), _0 , _0, + _0 , _0 , -_2 / (far - near), _0, + tx , ty , tz , _1 + ) + } + + /// Check whether shapes on the XY plane with Z pointing towards the + /// screen transformed by this matrix would be facing back. + pub fn is_backface_visible(&self) -> bool { + // inverse().m33 < 0; + let det = self.determinant(); + let m33 = self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 + + self.m14 * self.m21 * self.m42 - self.m11 * self.m24 * self.m42 - + self.m12 * self.m21 * self.m44 + self.m11 * self.m22 * self.m44; + let _0: T = Zero::zero(); + (m33 * det) < _0 + } + + /// Returns whether it is possible to compute the inverse transform. + #[inline] + pub fn is_invertible(&self) -> bool { + self.determinant() != Zero::zero() + } + + /// Returns the inverse transform if possible. + pub fn inverse(&self) -> Option<Transform3D<T, Dst, Src>> { + let det = self.determinant(); + + if det == Zero::zero() { + return None; + } + + // todo(gw): this could be made faster by special casing + // for simpler transform types. + let m = Transform3D::new( + self.m23*self.m34*self.m42 - self.m24*self.m33*self.m42 + + self.m24*self.m32*self.m43 - self.m22*self.m34*self.m43 - + self.m23*self.m32*self.m44 + self.m22*self.m33*self.m44, + + self.m14*self.m33*self.m42 - self.m13*self.m34*self.m42 - + self.m14*self.m32*self.m43 + self.m12*self.m34*self.m43 + + self.m13*self.m32*self.m44 - self.m12*self.m33*self.m44, + + self.m13*self.m24*self.m42 - self.m14*self.m23*self.m42 + + self.m14*self.m22*self.m43 - self.m12*self.m24*self.m43 - + self.m13*self.m22*self.m44 + self.m12*self.m23*self.m44, + + self.m14*self.m23*self.m32 - self.m13*self.m24*self.m32 - + self.m14*self.m22*self.m33 + self.m12*self.m24*self.m33 + + self.m13*self.m22*self.m34 - self.m12*self.m23*self.m34, + + self.m24*self.m33*self.m41 - self.m23*self.m34*self.m41 - + self.m24*self.m31*self.m43 + self.m21*self.m34*self.m43 + + self.m23*self.m31*self.m44 - self.m21*self.m33*self.m44, + + self.m13*self.m34*self.m41 - self.m14*self.m33*self.m41 + + self.m14*self.m31*self.m43 - self.m11*self.m34*self.m43 - + self.m13*self.m31*self.m44 + self.m11*self.m33*self.m44, + + self.m14*self.m23*self.m41 - self.m13*self.m24*self.m41 - + self.m14*self.m21*self.m43 + self.m11*self.m24*self.m43 + + self.m13*self.m21*self.m44 - self.m11*self.m23*self.m44, + + self.m13*self.m24*self.m31 - self.m14*self.m23*self.m31 + + self.m14*self.m21*self.m33 - self.m11*self.m24*self.m33 - + self.m13*self.m21*self.m34 + self.m11*self.m23*self.m34, + + self.m22*self.m34*self.m41 - self.m24*self.m32*self.m41 + + self.m24*self.m31*self.m42 - self.m21*self.m34*self.m42 - + self.m22*self.m31*self.m44 + self.m21*self.m32*self.m44, + + self.m14*self.m32*self.m41 - self.m12*self.m34*self.m41 - + self.m14*self.m31*self.m42 + self.m11*self.m34*self.m42 + + self.m12*self.m31*self.m44 - self.m11*self.m32*self.m44, + + self.m12*self.m24*self.m41 - self.m14*self.m22*self.m41 + + self.m14*self.m21*self.m42 - self.m11*self.m24*self.m42 - + self.m12*self.m21*self.m44 + self.m11*self.m22*self.m44, + + self.m14*self.m22*self.m31 - self.m12*self.m24*self.m31 - + self.m14*self.m21*self.m32 + self.m11*self.m24*self.m32 + + self.m12*self.m21*self.m34 - self.m11*self.m22*self.m34, + + self.m23*self.m32*self.m41 - self.m22*self.m33*self.m41 - + self.m23*self.m31*self.m42 + self.m21*self.m33*self.m42 + + self.m22*self.m31*self.m43 - self.m21*self.m32*self.m43, + + self.m12*self.m33*self.m41 - self.m13*self.m32*self.m41 + + self.m13*self.m31*self.m42 - self.m11*self.m33*self.m42 - + self.m12*self.m31*self.m43 + self.m11*self.m32*self.m43, + + self.m13*self.m22*self.m41 - self.m12*self.m23*self.m41 - + self.m13*self.m21*self.m42 + self.m11*self.m23*self.m42 + + self.m12*self.m21*self.m43 - self.m11*self.m22*self.m43, + + self.m12*self.m23*self.m31 - self.m13*self.m22*self.m31 + + self.m13*self.m21*self.m32 - self.m11*self.m23*self.m32 - + self.m12*self.m21*self.m33 + self.m11*self.m22*self.m33 + ); + + let _1: T = One::one(); + Some(m.mul_s(_1 / det)) + } + + /// Compute the determinant of the transform. + pub fn determinant(&self) -> T { + self.m14 * self.m23 * self.m32 * self.m41 - + self.m13 * self.m24 * self.m32 * self.m41 - + self.m14 * self.m22 * self.m33 * self.m41 + + self.m12 * self.m24 * self.m33 * self.m41 + + self.m13 * self.m22 * self.m34 * self.m41 - + self.m12 * self.m23 * self.m34 * self.m41 - + self.m14 * self.m23 * self.m31 * self.m42 + + self.m13 * self.m24 * self.m31 * self.m42 + + self.m14 * self.m21 * self.m33 * self.m42 - + self.m11 * self.m24 * self.m33 * self.m42 - + self.m13 * self.m21 * self.m34 * self.m42 + + self.m11 * self.m23 * self.m34 * self.m42 + + self.m14 * self.m22 * self.m31 * self.m43 - + self.m12 * self.m24 * self.m31 * self.m43 - + self.m14 * self.m21 * self.m32 * self.m43 + + self.m11 * self.m24 * self.m32 * self.m43 + + self.m12 * self.m21 * self.m34 * self.m43 - + self.m11 * self.m22 * self.m34 * self.m43 - + self.m13 * self.m22 * self.m31 * self.m44 + + self.m12 * self.m23 * self.m31 * self.m44 + + self.m13 * self.m21 * self.m32 * self.m44 - + self.m11 * self.m23 * self.m32 * self.m44 - + self.m12 * self.m21 * self.m33 * self.m44 + + self.m11 * self.m22 * self.m33 * self.m44 + } + + /// Multiplies all of the transform's component by a scalar and returns the result. + #[must_use] + pub fn mul_s(&self, x: T) -> Self { + Transform3D::new( + self.m11 * x, self.m12 * x, self.m13 * x, self.m14 * x, + self.m21 * x, self.m22 * x, self.m23 * x, self.m24 * x, + self.m31 * x, self.m32 * x, self.m33 * x, self.m34 * x, + self.m41 * x, self.m42 * x, self.m43 * x, self.m44 * x + ) + } + + /// Convenience function to create a scale transform from a `Scale`. + pub fn from_scale(scale: Scale<T, Src, Dst>) -> Self { + Transform3D::scale(scale.get(), scale.get(), scale.get()) + } +} + +impl <T, Src, Dst> Transform3D<T, Src, Dst> +where + T: Copy + Mul<Output = T> + Div<Output = T> + Zero + One + PartialEq, +{ + /// Returns a projection of this transform in 2d space. + pub fn project_to_2d(&self) -> Self { + let (_0, _1): (T, T) = (Zero::zero(), One::one()); + + let mut result = self.clone(); + + result.m31 = _0; + result.m32 = _0; + result.m13 = _0; + result.m23 = _0; + result.m33 = _1; + result.m43 = _0; + result.m34 = _0; + + // Try to normalize perspective when possible to convert to a 2d matrix. + // Some matrices, such as those derived from perspective transforms, can + // modify m44 from 1, while leaving the rest of the fourth column + // (m14, m24) at 0. In this case, after resetting the third row and + // third column above, the value of m44 functions only to scale the + // coordinate transform divide by W. The matrix can be converted to + // a true 2D matrix by normalizing out the scaling effect of m44 on + // the remaining components ahead of time. + if self.m14 == _0 && self.m24 == _0 && self.m44 != _0 && self.m44 != _1 { + let scale = _1 / self.m44; + result.m11 = result.m11 * scale; + result.m12 = result.m12 * scale; + result.m21 = result.m21 * scale; + result.m22 = result.m22 * scale; + result.m41 = result.m41 * scale; + result.m42 = result.m42 * scale; + result.m44 = _1; + } + + result + } +} + +impl<T: NumCast + Copy, Src, Dst> Transform3D<T, Src, Dst> { + /// Cast from one numeric representation to another, preserving the units. + #[inline] + pub fn cast<NewT: NumCast>(&self) -> Transform3D<NewT, Src, Dst> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + pub fn try_cast<NewT: NumCast>(&self) -> Option<Transform3D<NewT, Src, Dst>> { + match (NumCast::from(self.m11), NumCast::from(self.m12), + NumCast::from(self.m13), NumCast::from(self.m14), + NumCast::from(self.m21), NumCast::from(self.m22), + NumCast::from(self.m23), NumCast::from(self.m24), + NumCast::from(self.m31), NumCast::from(self.m32), + NumCast::from(self.m33), NumCast::from(self.m34), + NumCast::from(self.m41), NumCast::from(self.m42), + NumCast::from(self.m43), NumCast::from(self.m44)) { + (Some(m11), Some(m12), Some(m13), Some(m14), + Some(m21), Some(m22), Some(m23), Some(m24), + Some(m31), Some(m32), Some(m33), Some(m34), + Some(m41), Some(m42), Some(m43), Some(m44)) => { + Some(Transform3D::new(m11, m12, m13, m14, + m21, m22, m23, m24, + m31, m32, m33, m34, + m41, m42, m43, m44)) + }, + _ => None + } + } +} + +impl<T: ApproxEq<T>, Src, Dst> Transform3D<T, Src, Dst> { + /// Returns true is this transform is approximately equal to the other one, using + /// T's default epsilon value. + /// + /// The same as [`ApproxEq::approx_eq()`] but available without importing trait. + /// + /// [`ApproxEq::approx_eq()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq + #[inline] + pub fn approx_eq(&self, other: &Self) -> bool { + <Self as ApproxEq<T>>::approx_eq(&self, &other) + } + + /// Returns true is this transform is approximately equal to the other one, using + /// a provided epsilon value. + /// + /// The same as [`ApproxEq::approx_eq_eps()`] but available without importing trait. + /// + /// [`ApproxEq::approx_eq_eps()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq_eps + #[inline] + pub fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool { + <Self as ApproxEq<T>>::approx_eq_eps(&self, &other, &eps) + } +} + + +impl<T: ApproxEq<T>, Src, Dst> ApproxEq<T> for Transform3D<T, Src, Dst> { + #[inline] + fn approx_epsilon() -> T { T::approx_epsilon() } + + fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool { + self.m11.approx_eq_eps(&other.m11, eps) && self.m12.approx_eq_eps(&other.m12, eps) && + self.m13.approx_eq_eps(&other.m13, eps) && self.m14.approx_eq_eps(&other.m14, eps) && + self.m21.approx_eq_eps(&other.m21, eps) && self.m22.approx_eq_eps(&other.m22, eps) && + self.m23.approx_eq_eps(&other.m23, eps) && self.m24.approx_eq_eps(&other.m24, eps) && + self.m31.approx_eq_eps(&other.m31, eps) && self.m32.approx_eq_eps(&other.m32, eps) && + self.m33.approx_eq_eps(&other.m33, eps) && self.m34.approx_eq_eps(&other.m34, eps) && + self.m41.approx_eq_eps(&other.m41, eps) && self.m42.approx_eq_eps(&other.m42, eps) && + self.m43.approx_eq_eps(&other.m43, eps) && self.m44.approx_eq_eps(&other.m44, eps) + } +} + +impl <T, Src, Dst> Default for Transform3D<T, Src, Dst> + where T: Zero + One +{ + /// Returns the [identity transform](#method.identity). + fn default() -> Self { + Self::identity() + } +} + +impl<T, Src, Dst> fmt::Debug for Transform3D<T, Src, Dst> +where T: Copy + fmt::Debug + + PartialEq + + One + Zero { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_identity() { + write!(f, "[I]") + } else { + self.to_array().fmt(f) + } + } +} + +#[cfg(feature = "mint")] +impl<T, Src, Dst> From<mint::RowMatrix4<T>> for Transform3D<T, Src, Dst> { + fn from(m: mint::RowMatrix4<T>) -> Self { + Transform3D { + m11: m.x.x, m12: m.x.y, m13: m.x.z, m14: m.x.w, + m21: m.y.x, m22: m.y.y, m23: m.y.z, m24: m.y.w, + m31: m.z.x, m32: m.z.y, m33: m.z.z, m34: m.z.w, + m41: m.w.x, m42: m.w.y, m43: m.w.z, m44: m.w.w, + _unit: PhantomData, + } + } +} +#[cfg(feature = "mint")] +impl<T, Src, Dst> Into<mint::RowMatrix4<T>> for Transform3D<T, Src, Dst> { + fn into(self) -> mint::RowMatrix4<T> { + mint::RowMatrix4 { + x: mint::Vector4 { x: self.m11, y: self.m12, z: self.m13, w: self.m14 }, + y: mint::Vector4 { x: self.m21, y: self.m22, z: self.m23, w: self.m24 }, + z: mint::Vector4 { x: self.m31, y: self.m32, z: self.m33, w: self.m34 }, + w: mint::Vector4 { x: self.m41, y: self.m42, z: self.m43, w: self.m44 }, + } + } +} + + +#[cfg(test)] +mod tests { + use crate::approxeq::ApproxEq; + use super::*; + use crate::{point2, point3}; + use crate::default; + + use core::f32::consts::{FRAC_PI_2, PI}; + + type Mf32 = default::Transform3D<f32>; + + // For convenience. + fn rad(v: f32) -> Angle<f32> { Angle::radians(v) } + + #[test] + pub fn test_translation() { + let t1 = Mf32::translation(1.0, 2.0, 3.0); + let t2 = Mf32::identity().pre_translate(vec3(1.0, 2.0, 3.0)); + let t3 = Mf32::identity().then_translate(vec3(1.0, 2.0, 3.0)); + assert_eq!(t1, t2); + assert_eq!(t1, t3); + + assert_eq!(t1.transform_point3d(point3(1.0, 1.0, 1.0)), Some(point3(2.0, 3.0, 4.0))); + assert_eq!(t1.transform_point2d(point2(1.0, 1.0)), Some(point2(2.0, 3.0))); + + assert_eq!(t1.then(&t1), Mf32::translation(2.0, 4.0, 6.0)); + + assert!(!t1.is_2d()); + assert_eq!(Mf32::translation(1.0, 2.0, 3.0).to_2d(), Transform2D::translation(1.0, 2.0)); + } + + #[test] + pub fn test_rotation() { + let r1 = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2)); + let r2 = Mf32::identity().pre_rotate(0.0, 0.0, 1.0, rad(FRAC_PI_2)); + let r3 = Mf32::identity().then_rotate(0.0, 0.0, 1.0, rad(FRAC_PI_2)); + assert_eq!(r1, r2); + assert_eq!(r1, r3); + + assert!(r1.transform_point3d(point3(1.0, 2.0, 3.0)).unwrap().approx_eq(&point3(-2.0, 1.0, 3.0))); + assert!(r1.transform_point2d(point2(1.0, 2.0)).unwrap().approx_eq(&point2(-2.0, 1.0))); + + assert!(r1.then(&r1).approx_eq(&Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2*2.0)))); + + assert!(r1.is_2d()); + assert!(r1.to_2d().approx_eq(&Transform2D::rotation(rad(FRAC_PI_2)))); + } + + #[test] + pub fn test_scale() { + let s1 = Mf32::scale(2.0, 3.0, 4.0); + let s2 = Mf32::identity().pre_scale(2.0, 3.0, 4.0); + let s3 = Mf32::identity().then_scale(2.0, 3.0, 4.0); + assert_eq!(s1, s2); + assert_eq!(s1, s3); + + assert!(s1.transform_point3d(point3(2.0, 2.0, 2.0)).unwrap().approx_eq(&point3(4.0, 6.0, 8.0))); + assert!(s1.transform_point2d(point2(2.0, 2.0)).unwrap().approx_eq(&point2(4.0, 6.0))); + + assert_eq!(s1.then(&s1), Mf32::scale(4.0, 9.0, 16.0)); + + assert!(!s1.is_2d()); + assert_eq!(Mf32::scale(2.0, 3.0, 0.0).to_2d(), Transform2D::scale(2.0, 3.0)); + } + + + #[test] + pub fn test_pre_then_scale() { + let m = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2)).then_translate(vec3(6.0, 7.0, 8.0)); + let s = Mf32::scale(2.0, 3.0, 4.0); + assert_eq!(m.then(&s), m.then_scale(2.0, 3.0, 4.0)); + } + + + #[test] + pub fn test_ortho() { + let (left, right, bottom, top) = (0.0f32, 1.0f32, 0.1f32, 1.0f32); + let (near, far) = (-1.0f32, 1.0f32); + let result = Mf32::ortho(left, right, bottom, top, near, far); + let expected = Mf32::new( + 2.0, 0.0, 0.0, 0.0, + 0.0, 2.22222222, 0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + -1.0, -1.22222222, -0.0, 1.0 + ); + assert!(result.approx_eq(&expected)); + } + + #[test] + pub fn test_is_2d() { + assert!(Mf32::identity().is_2d()); + assert!(Mf32::rotation(0.0, 0.0, 1.0, rad(0.7854)).is_2d()); + assert!(!Mf32::rotation(0.0, 1.0, 0.0, rad(0.7854)).is_2d()); + } + + #[test] + pub fn test_new_2d() { + let m1 = Mf32::new_2d(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + let m2 = Mf32::new( + 1.0, 2.0, 0.0, 0.0, + 3.0, 4.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 5.0, 6.0, 0.0, 1.0 + ); + assert_eq!(m1, m2); + } + + #[test] + pub fn test_inverse_simple() { + let m1 = Mf32::identity(); + let m2 = m1.inverse().unwrap(); + assert!(m1.approx_eq(&m2)); + } + + #[test] + pub fn test_inverse_scale() { + let m1 = Mf32::scale(1.5, 0.3, 2.1); + let m2 = m1.inverse().unwrap(); + assert!(m1.then(&m2).approx_eq(&Mf32::identity())); + assert!(m2.then(&m1).approx_eq(&Mf32::identity())); + } + + #[test] + pub fn test_inverse_translate() { + let m1 = Mf32::translation(-132.0, 0.3, 493.0); + let m2 = m1.inverse().unwrap(); + assert!(m1.then(&m2).approx_eq(&Mf32::identity())); + assert!(m2.then(&m1).approx_eq(&Mf32::identity())); + } + + #[test] + pub fn test_inverse_rotate() { + let m1 = Mf32::rotation(0.0, 1.0, 0.0, rad(1.57)); + let m2 = m1.inverse().unwrap(); + assert!(m1.then(&m2).approx_eq(&Mf32::identity())); + assert!(m2.then(&m1).approx_eq(&Mf32::identity())); + } + + #[test] + pub fn test_inverse_transform_point_2d() { + let m1 = Mf32::translation(100.0, 200.0, 0.0); + let m2 = m1.inverse().unwrap(); + assert!(m1.then(&m2).approx_eq(&Mf32::identity())); + assert!(m2.then(&m1).approx_eq(&Mf32::identity())); + + let p1 = point2(1000.0, 2000.0); + let p2 = m1.transform_point2d(p1); + assert_eq!(p2, Some(point2(1100.0, 2200.0))); + + let p3 = m2.transform_point2d(p2.unwrap()); + assert_eq!(p3, Some(p1)); + } + + #[test] + fn test_inverse_none() { + assert!(Mf32::scale(2.0, 0.0, 2.0).inverse().is_none()); + assert!(Mf32::scale(2.0, 2.0, 2.0).inverse().is_some()); + } + + #[test] + pub fn test_pre_post() { + let m1 = default::Transform3D::identity().then_scale(1.0, 2.0, 3.0).then_translate(vec3(1.0, 2.0, 3.0)); + let m2 = default::Transform3D::identity().pre_translate(vec3(1.0, 2.0, 3.0)).pre_scale(1.0, 2.0, 3.0); + assert!(m1.approx_eq(&m2)); + + let r = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2)); + let t = Mf32::translation(2.0, 3.0, 0.0); + + let a = point3(1.0, 1.0, 1.0); + + assert!(r.then(&t).transform_point3d(a).unwrap().approx_eq(&point3(1.0, 4.0, 1.0))); + assert!(t.then(&r).transform_point3d(a).unwrap().approx_eq(&point3(-4.0, 3.0, 1.0))); + assert!(t.then(&r).transform_point3d(a).unwrap().approx_eq(&r.transform_point3d(t.transform_point3d(a).unwrap()).unwrap())); + } + + #[test] + fn test_size_of() { + use core::mem::size_of; + assert_eq!(size_of::<default::Transform3D<f32>>(), 16*size_of::<f32>()); + assert_eq!(size_of::<default::Transform3D<f64>>(), 16*size_of::<f64>()); + } + + #[test] + pub fn test_transform_associativity() { + let m1 = Mf32::new(3.0, 2.0, 1.5, 1.0, + 0.0, 4.5, -1.0, -4.0, + 0.0, 3.5, 2.5, 40.0, + 0.0, 3.0, 0.0, 1.0); + let m2 = Mf32::new(1.0, -1.0, 3.0, 0.0, + -1.0, 0.5, 0.0, 2.0, + 1.5, -2.0, 6.0, 0.0, + -2.5, 6.0, 1.0, 1.0); + + let p = point3(1.0, 3.0, 5.0); + let p1 = m1.then(&m2).transform_point3d(p).unwrap(); + let p2 = m2.transform_point3d(m1.transform_point3d(p).unwrap()).unwrap(); + assert!(p1.approx_eq(&p2)); + } + + #[test] + pub fn test_is_identity() { + let m1 = default::Transform3D::identity(); + assert!(m1.is_identity()); + let m2 = m1.then_translate(vec3(0.1, 0.0, 0.0)); + assert!(!m2.is_identity()); + } + + #[test] + pub fn test_transform_vector() { + // Translation does not apply to vectors. + let m = Mf32::translation(1.0, 2.0, 3.0); + let v1 = vec3(10.0, -10.0, 3.0); + assert_eq!(v1, m.transform_vector3d(v1)); + // While it does apply to points. + assert_ne!(Some(v1.to_point()), m.transform_point3d(v1.to_point())); + + // same thing with 2d vectors/points + let v2 = vec2(10.0, -5.0); + assert_eq!(v2, m.transform_vector2d(v2)); + assert_ne!(Some(v2.to_point()), m.transform_point2d(v2.to_point())); + } + + #[test] + pub fn test_is_backface_visible() { + // backface is not visible for rotate-x 0 degree. + let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(0.0)); + assert!(!r1.is_backface_visible()); + // backface is not visible for rotate-x 45 degree. + let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI * 0.25)); + assert!(!r1.is_backface_visible()); + // backface is visible for rotate-x 180 degree. + let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI)); + assert!(r1.is_backface_visible()); + // backface is visible for rotate-x 225 degree. + let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI * 1.25)); + assert!(r1.is_backface_visible()); + // backface is not visible for non-inverseable matrix + let r1 = Mf32::scale(2.0, 0.0, 2.0); + assert!(!r1.is_backface_visible()); + } + + #[test] + pub fn test_homogeneous() { + let m = Mf32::new( + 1.0, 2.0, 0.5, 5.0, + 3.0, 4.0, 0.25, 6.0, + 0.5, -1.0, 1.0, -1.0, + -1.0, 1.0, -1.0, 2.0, + ); + assert_eq!( + m.transform_point2d_homogeneous(point2(1.0, 2.0)), + HomogeneousVector::new(6.0, 11.0, 0.0, 19.0), + ); + assert_eq!( + m.transform_point3d_homogeneous(point3(1.0, 2.0, 4.0)), + HomogeneousVector::new(8.0, 7.0, 4.0, 15.0), + ); + } + + #[test] + pub fn test_perspective_division() { + let p = point2(1.0, 2.0); + let mut m = Mf32::identity(); + assert!(m.transform_point2d(p).is_some()); + m.m44 = 0.0; + assert_eq!(None, m.transform_point2d(p)); + m.m44 = 1.0; + m.m24 = -1.0; + assert_eq!(None, m.transform_point2d(p)); + } + + #[cfg(feature = "mint")] + #[test] + pub fn test_mint() { + let m1 = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2)); + let mm: mint::RowMatrix4<_> = m1.into(); + let m2 = Mf32::from(mm); + + assert_eq!(m1, m2); + } +} diff --git a/third_party/rust/euclid/src/translation.rs b/third_party/rust/euclid/src/translation.rs new file mode 100644 index 0000000000..45126e8d44 --- /dev/null +++ b/third_party/rust/euclid/src/translation.rs @@ -0,0 +1,867 @@ +// 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<i32, ParentSpace, ChildSpace>; +/// type ParentPoint = Point2D<i32, ParentSpace>; +/// type ChildPoint = Point2D<i32, ChildSpace>; +/// +/// 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<T, Src, Dst> { + 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<T, Src, Dst> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> + { + let (x, y) = arbitrary::Arbitrary::arbitrary(u)?; + Ok(Translation2D { + x, + y, + _unit: PhantomData, + }) + } +} + +impl<T: Copy, Src, Dst> Copy for Translation2D<T, Src, Dst> {} + +impl<T: Clone, Src, Dst> Clone for Translation2D<T, Src, Dst> { + fn clone(&self) -> Self { + Translation2D { + x: self.x.clone(), + y: self.y.clone(), + _unit: PhantomData, + } + } +} + +impl<T, Src, Dst> Eq for Translation2D<T, Src, Dst> where T: Eq {} + +impl<T, Src, Dst> PartialEq for Translation2D<T, Src, Dst> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +impl<T, Src, Dst> Hash for Translation2D<T, Src, Dst> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + } +} + +impl<T, Src, Dst> Translation2D<T, Src, Dst> { + #[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::<f32>::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<T, Src>) -> Size2D<T, Dst> { + Size2D::new(s.width, s.height) + } +} + +impl<T: Copy, Src, Dst> Translation2D<T, Src, Dst> { + /// Cast into a 2D vector. + #[inline] + pub fn to_vector(&self) -> Vector2D<T, Src> { + 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<T, UnknownUnit, UnknownUnit> { + Translation2D { + x: self.x, + y: self.y, + _unit: PhantomData, + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(t: &Translation2D<T, UnknownUnit, UnknownUnit>) -> Self { + Translation2D { + x: t.x, + y: t.y, + _unit: PhantomData, + } + } + + /// Returns the matrix representation of this translation. + #[inline] + pub fn to_transform(&self) -> Transform2D<T, Src, Dst> + where + T: Zero + One, + { + (*self).into() + } + + /// Translate a point and cast its unit. + #[inline] + pub fn transform_point(&self, p: Point2D<T, Src>) -> Point2D<T::Output, Dst> + 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<T, Src>) -> Rect<T::Output, Dst> + where + T: Add<Output = T>, + { + 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<T, Src>) -> Box2D<T::Output, Dst> + 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<T::Output, Dst, Src> + where + T: Neg, + { + Translation2D::new(-self.x, -self.y) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, Src, Dst> Zeroable for Translation2D<T, Src, Dst> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Translation2D<T, Src, Dst> {} + +impl<T: Add, Src, Dst1, Dst2> Add<Translation2D<T, Dst1, Dst2>> for Translation2D<T, Src, Dst1> { + type Output = Translation2D<T::Output, Src, Dst2>; + + fn add(self, other: Translation2D<T, Dst1, Dst2>) -> Self::Output { + Translation2D::new(self.x + other.x, self.y + other.y) + } +} + +impl<T: AddAssign, Src, Dst> AddAssign<Translation2D<T, Dst, Dst>> for Translation2D<T, Src, Dst> { + fn add_assign(&mut self, other: Translation2D<T, Dst, Dst>) { + self.x += other.x; + self.y += other.y; + } +} + +impl<T: Sub, Src, Dst1, Dst2> Sub<Translation2D<T, Dst1, Dst2>> for Translation2D<T, Src, Dst2> { + type Output = Translation2D<T::Output, Src, Dst1>; + + fn sub(self, other: Translation2D<T, Dst1, Dst2>) -> Self::Output { + Translation2D::new(self.x - other.x, self.y - other.y) + } +} + +impl<T: SubAssign, Src, Dst> SubAssign<Translation2D<T, Dst, Dst>> for Translation2D<T, Src, Dst> { + fn sub_assign(&mut self, other: Translation2D<T, Dst, Dst>) { + self.x -= other.x; + self.y -= other.y; + } +} + +impl<T, Src, Dst> From<Vector2D<T, Src>> for Translation2D<T, Src, Dst> { + fn from(v: Vector2D<T, Src>) -> Self { + Translation2D::new(v.x, v.y) + } +} + +impl<T, Src, Dst> Into<Vector2D<T, Src>> for Translation2D<T, Src, Dst> { + fn into(self) -> Vector2D<T, Src> { + vec2(self.x, self.y) + } +} + +impl<T, Src, Dst> Into<Transform2D<T, Src, Dst>> for Translation2D<T, Src, Dst> +where + T: Zero + One, +{ + fn into(self) -> Transform2D<T, Src, Dst> { + Transform2D::translation(self.x, self.y) + } +} + +impl<T, Src, Dst> Default for Translation2D<T, Src, Dst> +where + T: Zero, +{ + fn default() -> Self { + Self::identity() + } +} + +impl<T: fmt::Debug, Src, Dst> fmt::Debug for Translation2D<T, Src, Dst> { + 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<T, Src, Dst> { + pub x: T, + pub y: T, + pub z: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +impl<T: Copy, Src, Dst> Copy for Translation3D<T, Src, Dst> {} + +impl<T: Clone, Src, Dst> Clone for Translation3D<T, Src, Dst> { + 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<T, Src, Dst> +where + T: serde::Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let (x, y, z) = serde::Deserialize::deserialize(deserializer)?; + Ok(Translation3D { + x, + y, + z, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl<T, Src, Dst> serde::Serialize for Translation3D<T, Src, Dst> +where + T: serde::Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + (&self.x, &self.y, &self.z).serialize(serializer) + } +} + +impl<T, Src, Dst> Eq for Translation3D<T, Src, Dst> where T: Eq {} + +impl<T, Src, Dst> PartialEq for Translation3D<T, Src, Dst> +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z + } +} + +impl<T, Src, Dst> Hash for Translation3D<T, Src, Dst> +where + T: Hash, +{ + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + self.z.hash(h); + } +} + +impl<T, Src, Dst> Translation3D<T, Src, Dst> { + #[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::<f32>::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<T, Src>) -> Size2D<T, Dst> { + Size2D::new(s.width, s.height) + } +} + +impl<T: Copy, Src, Dst> Translation3D<T, Src, Dst> { + /// Cast into a 3D vector. + #[inline] + pub fn to_vector(&self) -> Vector3D<T, Src> { + 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<T, UnknownUnit, UnknownUnit> { + 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<T, UnknownUnit, UnknownUnit>) -> 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<T, Src, Dst> + where + T: Zero + One, + { + (*self).into() + } + + /// Translate a point and cast its unit. + #[inline] + pub fn transform_point3d(&self, p: &Point3D<T, Src>) -> Point3D<T::Output, Dst> + 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<T, Src>) -> Point2D<T::Output, Dst> + 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<T, Src>) -> Box2D<T::Output, Dst> + 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<T, Src>) -> Box3D<T::Output, Dst> + 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<T, Src>) -> Rect<T, Dst> + where + T: Add<Output = T>, + { + Rect { + origin: self.transform_point2d(&r.origin), + size: self.transform_size(r.size), + } + } + + /// Return the inverse transformation. + #[inline] + pub fn inverse(&self) -> Translation3D<T::Output, Dst, Src> + where + T: Neg, + { + Translation3D::new(-self.x, -self.y, -self.z) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, Src, Dst> Zeroable for Translation3D<T, Src, Dst> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Translation3D<T, Src, Dst> {} + +impl<T: Add, Src, Dst1, Dst2> Add<Translation3D<T, Dst1, Dst2>> for Translation3D<T, Src, Dst1> { + type Output = Translation3D<T::Output, Src, Dst2>; + + fn add(self, other: Translation3D<T, Dst1, Dst2>) -> Self::Output { + Translation3D::new(self.x + other.x, self.y + other.y, self.z + other.z) + } +} + +impl<T: AddAssign, Src, Dst> AddAssign<Translation3D<T, Dst, Dst>> for Translation3D<T, Src, Dst> { + fn add_assign(&mut self, other: Translation3D<T, Dst, Dst>) { + self.x += other.x; + self.y += other.y; + self.z += other.z; + } +} + +impl<T: Sub, Src, Dst1, Dst2> Sub<Translation3D<T, Dst1, Dst2>> for Translation3D<T, Src, Dst2> { + type Output = Translation3D<T::Output, Src, Dst1>; + + fn sub(self, other: Translation3D<T, Dst1, Dst2>) -> Self::Output { + Translation3D::new(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl<T: SubAssign, Src, Dst> SubAssign<Translation3D<T, Dst, Dst>> for Translation3D<T, Src, Dst> { + fn sub_assign(&mut self, other: Translation3D<T, Dst, Dst>) { + self.x -= other.x; + self.y -= other.y; + self.z -= other.z; + } +} + +impl<T, Src, Dst> From<Vector3D<T, Src>> for Translation3D<T, Src, Dst> { + fn from(v: Vector3D<T, Src>) -> Self { + Translation3D::new(v.x, v.y, v.z) + } +} + +impl<T, Src, Dst> Into<Vector3D<T, Src>> for Translation3D<T, Src, Dst> { + fn into(self) -> Vector3D<T, Src> { + vec3(self.x, self.y, self.z) + } +} + +impl<T, Src, Dst> Into<Transform3D<T, Src, Dst>> for Translation3D<T, Src, Dst> +where + T: Zero + One, +{ + fn into(self) -> Transform3D<T, Src, Dst> { + Transform3D::translation(self.x, self.y, self.z) + } +} + +impl<T, Src, Dst> Default for Translation3D<T, Src, Dst> +where + T: Zero, +{ + fn default() -> Self { + Self::identity() + } +} + +impl<T: fmt::Debug, Src, Dst> fmt::Debug for Translation3D<T, Src, Dst> { + 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<i32, A, B>; + type SrcRect = Rect<i32, A>; + type DstRect = Rect<i32, B>; + + 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<i32, A, B>; + type SrcPoint = Point3D<i32, A>; + type DstPoint = Point3D<i32, B>; + + 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)); + } + } +} diff --git a/third_party/rust/euclid/src/trig.rs b/third_party/rust/euclid/src/trig.rs new file mode 100644 index 0000000000..907ffb0baa --- /dev/null +++ b/third_party/rust/euclid/src/trig.rs @@ -0,0 +1,80 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// Trait for basic trigonometry functions, so they can be used on generic numeric types +pub trait Trig { + fn sin(self) -> Self; + fn cos(self) -> Self; + fn tan(self) -> Self; + fn fast_atan2(y: Self, x: Self) -> Self; + fn degrees_to_radians(deg: Self) -> Self; + fn radians_to_degrees(rad: Self) -> Self; +} + +macro_rules! trig { + ($ty:ident) => { + impl Trig for $ty { + #[inline] + fn sin(self) -> $ty { + num_traits::Float::sin(self) + } + #[inline] + fn cos(self) -> $ty { + num_traits::Float::cos(self) + } + #[inline] + fn tan(self) -> $ty { + num_traits::Float::tan(self) + } + + /// A slightly faster approximation of `atan2`. + /// + /// Note that it does not deal with the case where both x and y are 0. + #[inline] + fn fast_atan2(y: $ty, x: $ty) -> $ty { + // This macro is used with f32 and f64 and clippy warns about the extra + // precision with f32. + #![cfg_attr(feature = "cargo-clippy", allow(excessive_precision))] + + // See https://math.stackexchange.com/questions/1098487/atan2-faster-approximation#1105038 + use core::$ty::consts; + let x_abs = num_traits::Float::abs(x); + let y_abs = num_traits::Float::abs(y); + let a = x_abs.min(y_abs) / x_abs.max(y_abs); + let s = a * a; + let mut result = + ((-0.046_496_474_9 * s + 0.159_314_22) * s - 0.327_622_764) * s * a + a; + if y_abs > x_abs { + result = consts::FRAC_PI_2 - result; + } + if x < 0.0 { + result = consts::PI - result + } + if y < 0.0 { + result = -result + } + + result + } + + #[inline] + fn degrees_to_radians(deg: Self) -> Self { + deg.to_radians() + } + + #[inline] + fn radians_to_degrees(rad: Self) -> Self { + rad.to_degrees() + } + } + }; +} + +trig!(f32); +trig!(f64); diff --git a/third_party/rust/euclid/src/vector.rs b/third_party/rust/euclid/src/vector.rs new file mode 100644 index 0000000000..1a23bbed31 --- /dev/null +++ b/third_party/rust/euclid/src/vector.rs @@ -0,0 +1,2596 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::approxeq::ApproxEq; +use crate::approxord::{max, min}; +use crate::length::Length; +use crate::num::*; +use crate::point::{point2, point3, Point2D, Point3D}; +use crate::scale::Scale; +use crate::size::{size2, size3, Size2D, Size3D}; +use crate::transform2d::Transform2D; +use crate::transform3d::Transform3D; +use crate::trig::Trig; +use crate::Angle; +use core::cmp::{Eq, PartialEq}; +use core::fmt; +use core::hash::Hash; +use core::iter::Sum; +use core::marker::PhantomData; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +#[cfg(feature = "mint")] +use mint; +use num_traits::real::Real; +use num_traits::{Float, NumCast, Signed}; +#[cfg(feature = "serde")] +use serde; + +#[cfg(feature = "bytemuck")] +use bytemuck::{Zeroable, Pod}; + +/// A 2d Vector tagged with a unit. +#[repr(C)] +pub struct Vector2D<T, U> { + /// The `x` (traditionally, horizontal) coordinate. + pub x: T, + /// The `y` (traditionally, vertical) coordinate. + pub y: T, + #[doc(hidden)] + pub _unit: PhantomData<U>, +} + +mint_vec!(Vector2D[x, y] = Vector2); + +impl<T: Copy, U> Copy for Vector2D<T, U> {} + +impl<T: Clone, U> Clone for Vector2D<T, U> { + fn clone(&self) -> Self { + Vector2D { + x: self.x.clone(), + y: self.y.clone(), + _unit: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T, U> serde::Deserialize<'de> for Vector2D<T, U> +where + T: serde::Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let (x, y) = serde::Deserialize::deserialize(deserializer)?; + Ok(Vector2D { + x, + y, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl<T, U> serde::Serialize for Vector2D<T, U> +where + T: serde::Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + (&self.x, &self.y).serialize(serializer) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a, T, U> arbitrary::Arbitrary<'a> for Vector2D<T, U> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> + { + let (x, y) = arbitrary::Arbitrary::arbitrary(u)?; + Ok(Vector2D { + x, + y, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Vector2D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Vector2D<T, U> {} + +impl<T: Eq, U> Eq for Vector2D<T, U> {} + +impl<T: PartialEq, U> PartialEq for Vector2D<T, U> { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +impl<T: Hash, U> Hash for Vector2D<T, U> { + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + } +} + +impl<T: Zero, U> Zero for Vector2D<T, U> { + /// Constructor, setting all components to zero. + #[inline] + fn zero() -> Self { + Vector2D::new(Zero::zero(), Zero::zero()) + } +} + +impl<T: fmt::Debug, U> fmt::Debug for Vector2D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("").field(&self.x).field(&self.y).finish() + } +} + +impl<T: Default, U> Default for Vector2D<T, U> { + fn default() -> Self { + Vector2D::new(Default::default(), Default::default()) + } +} + +impl<T, U> Vector2D<T, U> { + /// Constructor, setting all components to zero. + #[inline] + pub fn zero() -> Self + where + T: Zero, + { + Vector2D::new(Zero::zero(), Zero::zero()) + } + + /// Constructor, setting all components to one. + #[inline] + pub fn one() -> Self + where + T: One, + { + Vector2D::new(One::one(), One::one()) + } + + /// Constructor taking scalar values directly. + #[inline] + pub const fn new(x: T, y: T) -> Self { + Vector2D { + x, + y, + _unit: PhantomData, + } + } + + /// Constructor setting all components to the same value. + #[inline] + pub fn splat(v: T) -> Self + where + T: Clone, + { + Vector2D { + x: v.clone(), + y: v, + _unit: PhantomData, + } + } + + /// Constructor taking angle and length + pub fn from_angle_and_length(angle: Angle<T>, length: T) -> Self + where + T: Trig + Mul<Output = T> + Copy, + { + vec2(length * angle.radians.cos(), length * angle.radians.sin()) + } + + /// Constructor taking properly Lengths instead of scalar values. + #[inline] + pub fn from_lengths(x: Length<T, U>, y: Length<T, U>) -> Self { + vec2(x.0, y.0) + } + + /// Tag a unit-less value with units. + #[inline] + pub fn from_untyped(p: Vector2D<T, UnknownUnit>) -> Self { + vec2(p.x, p.y) + } + + /// Computes the vector with absolute values of each component. + /// + /// # Example + /// + /// ```rust + /// # use std::{i32, f32}; + /// # use euclid::vec2; + /// enum U {} + /// + /// assert_eq!(vec2::<_, U>(-1, 2).abs(), vec2(1, 2)); + /// + /// let vec = vec2::<_, U>(f32::NAN, -f32::MAX).abs(); + /// assert!(vec.x.is_nan()); + /// assert_eq!(vec.y, f32::MAX); + /// ``` + /// + /// # Panics + /// + /// The behavior for each component follows the scalar type's implementation of + /// `num_traits::Signed::abs`. + pub fn abs(self) -> Self + where + T: Signed, + { + vec2(self.x.abs(), self.y.abs()) + } + + /// Dot product. + #[inline] + pub fn dot(self, other: Self) -> T + where + T: Add<Output = T> + Mul<Output = T>, + { + self.x * other.x + self.y * other.y + } + + /// Returns the norm of the cross product [self.x, self.y, 0] x [other.x, other.y, 0]. + #[inline] + pub fn cross(self, other: Self) -> T + where + T: Sub<Output = T> + Mul<Output = T>, + { + self.x * other.y - self.y * other.x + } + + /// Returns the component-wise multiplication of the two vectors. + #[inline] + pub fn component_mul(self, other: Self) -> Self + where + T: Mul<Output = T>, + { + vec2(self.x * other.x, self.y * other.y) + } + + /// Returns the component-wise division of the two vectors. + #[inline] + pub fn component_div(self, other: Self) -> Self + where + T: Div<Output = T>, + { + vec2(self.x / other.x, self.y / other.y) + } +} + +impl<T: Copy, U> Vector2D<T, U> { + /// Create a 3d vector from this one, using the specified z value. + #[inline] + pub fn extend(self, z: T) -> Vector3D<T, U> { + vec3(self.x, self.y, z) + } + + /// Cast this vector into a point. + /// + /// Equivalent to adding this vector to the origin. + #[inline] + pub fn to_point(self) -> Point2D<T, U> { + Point2D { + x: self.x, + y: self.y, + _unit: PhantomData, + } + } + + /// Swap x and y. + #[inline] + pub fn yx(self) -> Self { + vec2(self.y, self.x) + } + + /// Cast this vector into a size. + #[inline] + pub fn to_size(self) -> Size2D<T, U> { + size2(self.x, self.y) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(self) -> Vector2D<T, UnknownUnit> { + vec2(self.x, self.y) + } + + /// Cast the unit. + #[inline] + pub fn cast_unit<V>(self) -> Vector2D<T, V> { + 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) + } + + /// Convert into a 3d vector with `z` coordinate equals to `T::zero()`. + #[inline] + pub fn to_3d(self) -> Vector3D<T, U> + where + T: Zero, + { + vec3(self.x, self.y, Zero::zero()) + } + + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::vec2; + /// enum Mm {} + /// + /// assert_eq!(vec2::<_, Mm>(-0.1, -0.8).round(), vec2::<_, Mm>(0.0, -1.0)) + /// ``` + #[inline] + #[must_use] + pub fn round(self) -> Self + where + T: Round, + { + vec2(self.x.round(), self.y.round()) + } + + /// Rounds each component to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::vec2; + /// enum Mm {} + /// + /// assert_eq!(vec2::<_, Mm>(-0.1, -0.8).ceil(), vec2::<_, Mm>(0.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn ceil(self) -> Self + where + T: Ceil, + { + vec2(self.x.ceil(), self.y.ceil()) + } + + /// Rounds each component to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::vec2; + /// enum Mm {} + /// + /// assert_eq!(vec2::<_, Mm>(-0.1, -0.8).floor(), vec2::<_, Mm>(-1.0, -1.0)) + /// ``` + #[inline] + #[must_use] + pub fn floor(self) -> Self + where + T: Floor, + { + vec2(self.x.floor(), self.y.floor()) + } + + /// Returns the signed angle between this vector and the x axis. + /// Positive values counted counterclockwise, where 0 is `+x` axis, `PI/2` + /// is `+y` axis. + /// + /// The returned angle is between -PI and PI. + pub fn angle_from_x_axis(self) -> Angle<T> + where + T: Trig, + { + Angle::radians(Trig::fast_atan2(self.y, self.x)) + } + + /// Creates translation by this vector in vector units. + #[inline] + pub fn to_transform(self) -> Transform2D<T, U, U> + where + T: Zero + One, + { + Transform2D::translation(self.x, self.y) + } +} + +impl<T, U> Vector2D<T, U> +where + T: Copy + Mul<T, Output = T> + Add<T, Output = T>, +{ + /// Returns the vector's length squared. + #[inline] + pub fn square_length(self) -> T { + self.x * self.x + self.y * self.y + } + + /// Returns this vector projected onto another one. + /// + /// Projecting onto a nil vector will cause a division by zero. + #[inline] + pub fn project_onto_vector(self, onto: Self) -> Self + where + T: Sub<T, Output = T> + Div<T, Output = T>, + { + onto * (self.dot(onto) / onto.square_length()) + } + + /// Returns the signed angle between this vector and another vector. + /// + /// The returned angle is between -PI and PI. + pub fn angle_to(self, other: Self) -> Angle<T> + where + T: Sub<Output = T> + Trig, + { + Angle::radians(Trig::fast_atan2(self.cross(other), self.dot(other))) + } +} + +impl<T: Float, U> Vector2D<T, U> { + /// Return the normalized vector even if the length is larger than the max value of Float. + #[inline] + #[must_use] + pub fn robust_normalize(self) -> Self { + let length = self.length(); + if length.is_infinite() { + let scaled = self / T::max_value(); + scaled / scaled.length() + } else { + self / length + } + } + + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.x.is_finite() && self.y.is_finite() + } +} + +impl<T: Real, U> Vector2D<T, U> { + /// Returns the vector length. + #[inline] + pub fn length(self) -> T { + self.square_length().sqrt() + } + + /// Returns the vector with length of one unit. + #[inline] + #[must_use] + pub fn normalize(self) -> Self { + self / self.length() + } + + /// Returns the vector with length of one unit. + /// + /// Unlike [`Vector2D::normalize`](#method.normalize), this returns None in the case that the + /// length of the vector is zero. + #[inline] + #[must_use] + pub fn try_normalize(self) -> Option<Self> { + let len = self.length(); + if len == T::zero() { + None + } else { + Some(self / len) + } + } + + /// Return this vector scaled to fit the provided length. + #[inline] + pub fn with_length(self, length: T) -> Self { + self.normalize() * length + } + + /// Return this vector capped to a maximum length. + #[inline] + pub fn with_max_length(self, max_length: T) -> Self { + let square_length = self.square_length(); + if square_length > max_length * max_length { + return self * (max_length / square_length.sqrt()); + } + + self + } + + /// Return this vector with a minimum length applied. + #[inline] + pub fn with_min_length(self, min_length: T) -> Self { + let square_length = self.square_length(); + if square_length < min_length * min_length { + return self * (min_length / square_length.sqrt()); + } + + self + } + + /// Return this vector with minimum and maximum lengths applied. + #[inline] + pub fn clamp_length(self, min: T, max: T) -> Self { + debug_assert!(min <= max); + self.with_min_length(min).with_max_length(max) + } +} + +impl<T, U> Vector2D<T, U> +where + T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>, +{ + /// Linearly interpolate each component between this vector and another vector. + /// + /// # Example + /// + /// ```rust + /// use euclid::vec2; + /// use euclid::default::Vector2D; + /// + /// let from: Vector2D<_> = vec2(0.0, 10.0); + /// let to: Vector2D<_> = vec2(8.0, -4.0); + /// + /// assert_eq!(from.lerp(to, -1.0), vec2(-8.0, 24.0)); + /// assert_eq!(from.lerp(to, 0.0), vec2( 0.0, 10.0)); + /// assert_eq!(from.lerp(to, 0.5), vec2( 4.0, 3.0)); + /// assert_eq!(from.lerp(to, 1.0), vec2( 8.0, -4.0)); + /// assert_eq!(from.lerp(to, 2.0), vec2(16.0, -18.0)); + /// ``` + #[inline] + pub fn lerp(self, other: Self, t: T) -> Self { + let one_t = T::one() - t; + self * one_t + other * t + } + + /// Returns a reflection vector using an incident ray and a surface normal. + #[inline] + pub fn reflect(self, normal: Self) -> Self { + let two = T::one() + T::one(); + self - normal * two * self.dot(normal) + } +} + +impl<T: PartialOrd, U> Vector2D<T, U> { + /// Returns the vector each component of which are minimum of this vector and another. + #[inline] + pub fn min(self, other: Self) -> Self { + vec2(min(self.x, other.x), min(self.y, other.y)) + } + + /// Returns the vector each component of which are maximum of this vector and another. + #[inline] + pub fn max(self, other: Self) -> Self { + vec2(max(self.x, other.x), max(self.y, other.y)) + } + + /// Returns the vector each component of which is clamped by corresponding + /// components of `start` and `end`. + /// + /// Shortcut for `self.max(start).min(end)`. + #[inline] + pub fn clamp(self, start: Self, end: Self) -> Self + where + T: Copy, + { + self.max(start).min(end) + } + + /// Returns vector with results of "greater than" operation on each component. + #[inline] + pub fn greater_than(self, other: Self) -> BoolVector2D { + BoolVector2D { + x: self.x > other.x, + y: self.y > other.y, + } + } + + /// Returns vector with results of "lower than" operation on each component. + #[inline] + pub fn lower_than(self, other: Self) -> BoolVector2D { + BoolVector2D { + x: self.x < other.x, + y: self.y < other.y, + } + } +} + +impl<T: PartialEq, U> Vector2D<T, U> { + /// Returns vector with results of "equal" operation on each component. + #[inline] + pub fn equal(self, other: Self) -> BoolVector2D { + BoolVector2D { + x: self.x == other.x, + y: self.y == other.y, + } + } + + /// Returns vector with results of "not equal" operation on each component. + #[inline] + pub fn not_equal(self, other: Self) -> BoolVector2D { + BoolVector2D { + x: self.x != other.x, + y: self.y != other.y, + } + } +} + +impl<T: NumCast + Copy, U> Vector2D<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating vector to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + #[inline] + pub fn cast<NewT: NumCast>(self) -> Vector2D<NewT, U> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating vector to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + pub fn try_cast<NewT: NumCast>(self) -> Option<Vector2D<NewT, U>> { + match (NumCast::from(self.x), NumCast::from(self.y)) { + (Some(x), Some(y)) => Some(Vector2D::new(x, y)), + _ => None, + } + } + + // Convenience functions for common casts. + + /// Cast into an `f32` vector. + #[inline] + pub fn to_f32(self) -> Vector2D<f32, U> { + self.cast() + } + + /// Cast into an `f64` vector. + #[inline] + pub fn to_f64(self) -> Vector2D<f64, U> { + self.cast() + } + + /// Cast into an `usize` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_usize(self) -> Vector2D<usize, U> { + self.cast() + } + + /// Cast into an `u32` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_u32(self) -> Vector2D<u32, U> { + self.cast() + } + + /// Cast into an i32 vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i32(self) -> Vector2D<i32, U> { + self.cast() + } + + /// Cast into an i64 vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i64(self) -> Vector2D<i64, U> { + self.cast() + } +} + +impl<T: Neg, U> Neg for Vector2D<T, U> { + type Output = Vector2D<T::Output, U>; + + #[inline] + fn neg(self) -> Self::Output { + vec2(-self.x, -self.y) + } +} + +impl<T: Add, U> Add for Vector2D<T, U> { + type Output = Vector2D<T::Output, U>; + + #[inline] + fn add(self, other: Self) -> Self::Output { + Vector2D::new(self.x + other.x, self.y + other.y) + } +} + +impl<T: Add + Copy, U> Add<&Self> for Vector2D<T, U> { + type Output = Vector2D<T::Output, U>; + + #[inline] + fn add(self, other: &Self) -> Self::Output { + Vector2D::new(self.x + other.x, self.y + other.y) + } +} + +impl<T: Add<Output = T> + Zero, U> Sum for Vector2D<T, U> { + fn sum<I: Iterator<Item=Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Vector2D<T, U> { + fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<T: Copy + Add<T, Output = T>, U> AddAssign for Vector2D<T, U> { + #[inline] + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl<T: Sub, U> Sub for Vector2D<T, U> { + type Output = Vector2D<T::Output, U>; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + vec2(self.x - other.x, self.y - other.y) + } +} + +impl<T: Copy + Sub<T, Output = T>, U> SubAssign<Vector2D<T, U>> for Vector2D<T, U> { + #[inline] + fn sub_assign(&mut self, other: Self) { + *self = *self - other + } +} + +impl<T: Copy + Mul, U> Mul<T> for Vector2D<T, U> { + type Output = Vector2D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + vec2(self.x * scale, self.y * scale) + } +} + +impl<T: Copy + Mul<T, Output = T>, U> MulAssign<T> for Vector2D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Vector2D<T, U1> { + type Output = Vector2D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + vec2(self.x * scale.0, self.y * scale.0) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Vector2D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: Scale<T, U, U>) { + self.x *= scale.0; + self.y *= scale.0; + } +} + +impl<T: Copy + Div, U> Div<T> for Vector2D<T, U> { + type Output = Vector2D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + vec2(self.x / scale, self.y / scale) + } +} + +impl<T: Copy + Div<T, Output = T>, U> DivAssign<T> for Vector2D<T, U> { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Vector2D<T, U2> { + type Output = Vector2D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + vec2(self.x / scale.0, self.y / scale.0) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Vector2D<T, U> { + #[inline] + fn div_assign(&mut self, scale: Scale<T, U, U>) { + self.x /= scale.0; + self.y /= scale.0; + } +} + +impl<T: Round, U> Round for Vector2D<T, U> { + /// See [`Vector2D::round()`](#method.round) + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl<T: Ceil, U> Ceil for Vector2D<T, U> { + /// See [`Vector2D::ceil()`](#method.ceil) + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl<T: Floor, U> Floor for Vector2D<T, U> { + /// See [`Vector2D::floor()`](#method.floor) + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl<T: ApproxEq<T>, U> ApproxEq<Vector2D<T, U>> for Vector2D<T, U> { + #[inline] + fn approx_epsilon() -> Self { + vec2(T::approx_epsilon(), T::approx_epsilon()) + } + + #[inline] + fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool { + self.x.approx_eq_eps(&other.x, &eps.x) && self.y.approx_eq_eps(&other.y, &eps.y) + } +} + +impl<T, U> Into<[T; 2]> for Vector2D<T, U> { + fn into(self) -> [T; 2] { + [self.x, self.y] + } +} + +impl<T, U> From<[T; 2]> for Vector2D<T, U> { + fn from([x, y]: [T; 2]) -> Self { + vec2(x, y) + } +} + +impl<T, U> Into<(T, T)> for Vector2D<T, U> { + fn into(self) -> (T, T) { + (self.x, self.y) + } +} + +impl<T, U> From<(T, T)> for Vector2D<T, U> { + fn from(tuple: (T, T)) -> Self { + vec2(tuple.0, tuple.1) + } +} + +impl<T, U> From<Size2D<T, U>> for Vector2D<T, U> { + fn from(size: Size2D<T, U>) -> Self { + vec2(size.width, size.height) + } +} + +/// A 3d Vector tagged with a unit. +#[repr(C)] +pub struct Vector3D<T, U> { + /// The `x` (traditionally, horizontal) coordinate. + pub x: T, + /// The `y` (traditionally, vertical) coordinate. + pub y: T, + /// The `z` (traditionally, depth) coordinate. + pub z: T, + #[doc(hidden)] + pub _unit: PhantomData<U>, +} + +mint_vec!(Vector3D[x, y, z] = Vector3); + +impl<T: Copy, U> Copy for Vector3D<T, U> {} + +impl<T: Clone, U> Clone for Vector3D<T, U> { + fn clone(&self) -> Self { + Vector3D { + x: self.x.clone(), + y: self.y.clone(), + z: self.z.clone(), + _unit: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T, U> serde::Deserialize<'de> for Vector3D<T, U> +where + T: serde::Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let (x, y, z) = serde::Deserialize::deserialize(deserializer)?; + Ok(Vector3D { + x, + y, + z, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl<T, U> serde::Serialize for Vector3D<T, U> +where + T: serde::Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + (&self.x, &self.y, &self.z).serialize(serializer) + } +} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Zeroable, U> Zeroable for Vector3D<T, U> {} + +#[cfg(feature = "bytemuck")] +unsafe impl<T: Pod, U: 'static> Pod for Vector3D<T, U> {} + +impl<T: Eq, U> Eq for Vector3D<T, U> {} + +impl<T: PartialEq, U> PartialEq for Vector3D<T, U> { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z + } +} + +impl<T: Hash, U> Hash for Vector3D<T, U> { + fn hash<H: core::hash::Hasher>(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + self.z.hash(h); + } +} + +impl<T: Zero, U> Zero for Vector3D<T, U> { + /// Constructor, setting all components to zero. + #[inline] + fn zero() -> Self { + vec3(Zero::zero(), Zero::zero(), Zero::zero()) + } +} + +impl<T: fmt::Debug, U> fmt::Debug for Vector3D<T, U> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("") + .field(&self.x) + .field(&self.y) + .field(&self.z) + .finish() + } +} + +impl<T: Default, U> Default for Vector3D<T, U> { + fn default() -> Self { + Vector3D::new(Default::default(), Default::default(), Default::default()) + } +} + +impl<T, U> Vector3D<T, U> { + /// Constructor, setting all components to zero. + #[inline] + pub fn zero() -> Self + where + T: Zero, + { + vec3(Zero::zero(), Zero::zero(), Zero::zero()) + } + + /// Constructor, setting all components to one. + #[inline] + pub fn one() -> Self + where + T: One, + { + vec3(One::one(), One::one(), One::one()) + } + + /// Constructor taking scalar values directly. + #[inline] + pub const fn new(x: T, y: T, z: T) -> Self { + Vector3D { + x, + y, + z, + _unit: PhantomData, + } + } + /// Constructor setting all components to the same value. + #[inline] + pub fn splat(v: T) -> Self + where + T: Clone, + { + Vector3D { + x: v.clone(), + y: v.clone(), + z: v, + _unit: PhantomData, + } + } + + /// Constructor taking properly Lengths instead of scalar values. + #[inline] + pub fn from_lengths(x: Length<T, U>, y: Length<T, U>, z: Length<T, U>) -> Vector3D<T, U> { + vec3(x.0, y.0, z.0) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Vector3D<T, UnknownUnit>) -> Self { + vec3(p.x, p.y, p.z) + } + + /// Computes the vector with absolute values of each component. + /// + /// # Example + /// + /// ```rust + /// # use std::{i32, f32}; + /// # use euclid::vec3; + /// enum U {} + /// + /// assert_eq!(vec3::<_, U>(-1, 0, 2).abs(), vec3(1, 0, 2)); + /// + /// let vec = vec3::<_, U>(f32::NAN, 0.0, -f32::MAX).abs(); + /// assert!(vec.x.is_nan()); + /// assert_eq!(vec.y, 0.0); + /// assert_eq!(vec.z, f32::MAX); + /// ``` + /// + /// # Panics + /// + /// The behavior for each component follows the scalar type's implementation of + /// `num_traits::Signed::abs`. + pub fn abs(self) -> Self + where + T: Signed, + { + vec3(self.x.abs(), self.y.abs(), self.z.abs()) + } + + /// Dot product. + #[inline] + pub fn dot(self, other: Self) -> T + where + T: Add<Output = T> + Mul<Output = T>, + { + self.x * other.x + self.y * other.y + self.z * other.z + } +} + +impl<T: Copy, U> Vector3D<T, U> { + /// Cross product. + #[inline] + pub fn cross(self, other: Self) -> Self + where + T: Sub<Output = T> + Mul<Output = T>, + { + vec3( + self.y * other.z - self.z * other.y, + self.z * other.x - self.x * other.z, + self.x * other.y - self.y * other.x, + ) + } + + /// Returns the component-wise multiplication of the two vectors. + #[inline] + pub fn component_mul(self, other: Self) -> Self + where + T: Mul<Output = T>, + { + vec3(self.x * other.x, self.y * other.y, self.z * other.z) + } + + /// Returns the component-wise division of the two vectors. + #[inline] + pub fn component_div(self, other: Self) -> Self + where + T: Div<Output = T>, + { + vec3(self.x / other.x, self.y / other.y, self.z / other.z) + } + + /// Cast this vector into a point. + /// + /// Equivalent to adding this vector to the origin. + #[inline] + pub fn to_point(self) -> Point3D<T, U> { + point3(self.x, self.y, self.z) + } + + /// Returns a 2d vector using this vector's x and y coordinates + #[inline] + pub fn xy(self) -> Vector2D<T, U> { + vec2(self.x, self.y) + } + + /// Returns a 2d vector using this vector's x and z coordinates + #[inline] + pub fn xz(self) -> Vector2D<T, U> { + vec2(self.x, self.z) + } + + /// Returns a 2d vector using this vector's x and z coordinates + #[inline] + pub fn yz(self) -> Vector2D<T, U> { + vec2(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 an array with x, y, z and 0. + #[inline] + pub fn to_array_4d(self) -> [T; 4] + where + T: Zero, + { + [self.x, self.y, self.z, Zero::zero()] + } + + /// Cast into a tuple with x, y and z. + #[inline] + pub fn to_tuple(self) -> (T, T, T) { + (self.x, self.y, self.z) + } + + /// Cast into a tuple with x, y, z and 0. + #[inline] + pub fn to_tuple_4d(self) -> (T, T, T, T) + where + T: Zero, + { + (self.x, self.y, self.z, Zero::zero()) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(self) -> Vector3D<T, UnknownUnit> { + vec3(self.x, self.y, self.z) + } + + /// Cast the unit. + #[inline] + pub fn cast_unit<V>(self) -> Vector3D<T, V> { + vec3(self.x, self.y, self.z) + } + + /// Convert into a 2d vector. + #[inline] + pub fn to_2d(self) -> Vector2D<T, U> { + self.xy() + } + + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::vec3; + /// enum Mm {} + /// + /// assert_eq!(vec3::<_, Mm>(-0.1, -0.8, 0.4).round(), vec3::<_, Mm>(0.0, -1.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn round(self) -> Self + where + T: Round, + { + vec3(self.x.round(), self.y.round(), self.z.round()) + } + + /// Rounds each component to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::vec3; + /// enum Mm {} + /// + /// assert_eq!(vec3::<_, Mm>(-0.1, -0.8, 0.4).ceil(), vec3::<_, Mm>(0.0, 0.0, 1.0)) + /// ``` + #[inline] + #[must_use] + pub fn ceil(self) -> Self + where + T: Ceil, + { + vec3(self.x.ceil(), self.y.ceil(), self.z.ceil()) + } + + /// Rounds each component to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use euclid::vec3; + /// enum Mm {} + /// + /// assert_eq!(vec3::<_, Mm>(-0.1, -0.8, 0.4).floor(), vec3::<_, Mm>(-1.0, -1.0, 0.0)) + /// ``` + #[inline] + #[must_use] + pub fn floor(self) -> Self + where + T: Floor, + { + vec3(self.x.floor(), self.y.floor(), self.z.floor()) + } + + /// Creates translation by this vector in vector units + #[inline] + pub fn to_transform(self) -> Transform3D<T, U, U> + where + T: Zero + One, + { + Transform3D::translation(self.x, self.y, self.z) + } +} + +impl<T, U> Vector3D<T, U> +where + T: Copy + Mul<T, Output = T> + Add<T, Output = T>, +{ + /// Returns the vector's length squared. + #[inline] + pub fn square_length(self) -> T { + self.x * self.x + self.y * self.y + self.z * self.z + } + + /// Returns this vector projected onto another one. + /// + /// Projecting onto a nil vector will cause a division by zero. + #[inline] + pub fn project_onto_vector(self, onto: Self) -> Self + where + T: Sub<T, Output = T> + Div<T, Output = T>, + { + onto * (self.dot(onto) / onto.square_length()) + } +} + +impl<T: Float, U> Vector3D<T, U> { + /// Return the normalized vector even if the length is larger than the max value of Float. + #[inline] + #[must_use] + pub fn robust_normalize(self) -> Self { + let length = self.length(); + if length.is_infinite() { + let scaled = self / T::max_value(); + scaled / scaled.length() + } else { + self / length + } + } + + /// Returns true if all members are finite. + #[inline] + pub fn is_finite(self) -> bool { + self.x.is_finite() && self.y.is_finite() && self.z.is_finite() + } +} + +impl<T: Real, U> Vector3D<T, U> { + /// Returns the positive angle between this vector and another vector. + /// + /// The returned angle is between 0 and PI. + pub fn angle_to(self, other: Self) -> Angle<T> + where + T: Trig, + { + Angle::radians(Trig::fast_atan2( + self.cross(other).length(), + self.dot(other), + )) + } + + /// Returns the vector length. + #[inline] + pub fn length(self) -> T { + self.square_length().sqrt() + } + + /// Returns the vector with length of one unit + #[inline] + #[must_use] + pub fn normalize(self) -> Self { + self / self.length() + } + + /// Returns the vector with length of one unit. + /// + /// Unlike [`Vector2D::normalize`](#method.normalize), this returns None in the case that the + /// length of the vector is zero. + #[inline] + #[must_use] + pub fn try_normalize(self) -> Option<Self> { + let len = self.length(); + if len == T::zero() { + None + } else { + Some(self / len) + } + } + + /// Return this vector capped to a maximum length. + #[inline] + pub fn with_max_length(self, max_length: T) -> Self { + let square_length = self.square_length(); + if square_length > max_length * max_length { + return self * (max_length / square_length.sqrt()); + } + + self + } + + /// Return this vector with a minimum length applied. + #[inline] + pub fn with_min_length(self, min_length: T) -> Self { + let square_length = self.square_length(); + if square_length < min_length * min_length { + return self * (min_length / square_length.sqrt()); + } + + self + } + + /// Return this vector with minimum and maximum lengths applied. + #[inline] + pub fn clamp_length(self, min: T, max: T) -> Self { + debug_assert!(min <= max); + self.with_min_length(min).with_max_length(max) + } +} + +impl<T, U> Vector3D<T, U> +where + T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>, +{ + /// Linearly interpolate each component between this vector and another vector. + /// + /// # Example + /// + /// ```rust + /// use euclid::vec3; + /// use euclid::default::Vector3D; + /// + /// let from: Vector3D<_> = vec3(0.0, 10.0, -1.0); + /// let to: Vector3D<_> = vec3(8.0, -4.0, 0.0); + /// + /// assert_eq!(from.lerp(to, -1.0), vec3(-8.0, 24.0, -2.0)); + /// assert_eq!(from.lerp(to, 0.0), vec3( 0.0, 10.0, -1.0)); + /// assert_eq!(from.lerp(to, 0.5), vec3( 4.0, 3.0, -0.5)); + /// assert_eq!(from.lerp(to, 1.0), vec3( 8.0, -4.0, 0.0)); + /// assert_eq!(from.lerp(to, 2.0), vec3(16.0, -18.0, 1.0)); + /// ``` + #[inline] + pub fn lerp(self, other: Self, t: T) -> Self { + let one_t = T::one() - t; + self * one_t + other * t + } + + /// Returns a reflection vector using an incident ray and a surface normal. + #[inline] + pub fn reflect(self, normal: Self) -> Self { + let two = T::one() + T::one(); + self - normal * two * self.dot(normal) + } +} + +impl<T: PartialOrd, U> Vector3D<T, U> { + /// Returns the vector each component of which are minimum of this vector and another. + #[inline] + pub fn min(self, other: Self) -> Self { + vec3( + min(self.x, other.x), + min(self.y, other.y), + min(self.z, other.z), + ) + } + + /// Returns the vector each component of which are maximum of this vector and another. + #[inline] + pub fn max(self, other: Self) -> Self { + vec3( + max(self.x, other.x), + max(self.y, other.y), + max(self.z, other.z), + ) + } + + /// Returns the vector each component of which is clamped by corresponding + /// components of `start` and `end`. + /// + /// Shortcut for `self.max(start).min(end)`. + #[inline] + pub fn clamp(self, start: Self, end: Self) -> Self + where + T: Copy, + { + self.max(start).min(end) + } + + /// Returns vector with results of "greater than" operation on each component. + #[inline] + pub fn greater_than(self, other: Self) -> BoolVector3D { + BoolVector3D { + x: self.x > other.x, + y: self.y > other.y, + z: self.z > other.z, + } + } + + /// Returns vector with results of "lower than" operation on each component. + #[inline] + pub fn lower_than(self, other: Self) -> BoolVector3D { + BoolVector3D { + x: self.x < other.x, + y: self.y < other.y, + z: self.z < other.z, + } + } +} + +impl<T: PartialEq, U> Vector3D<T, U> { + /// Returns vector with results of "equal" operation on each component. + #[inline] + pub fn equal(self, other: Self) -> BoolVector3D { + BoolVector3D { + x: self.x == other.x, + y: self.y == other.y, + z: self.z == other.z, + } + } + + /// Returns vector with results of "not equal" operation on each component. + #[inline] + pub fn not_equal(self, other: Self) -> BoolVector3D { + BoolVector3D { + x: self.x != other.x, + y: self.y != other.y, + z: self.z != other.z, + } + } +} + +impl<T: NumCast + Copy, U> Vector3D<T, U> { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating vector to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + #[inline] + pub fn cast<NewT: NumCast>(self) -> Vector3D<NewT, U> { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating vector to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting. + pub fn try_cast<NewT: NumCast>(self) -> Option<Vector3D<NewT, U>> { + match ( + NumCast::from(self.x), + NumCast::from(self.y), + NumCast::from(self.z), + ) { + (Some(x), Some(y), Some(z)) => Some(vec3(x, y, z)), + _ => None, + } + } + + // Convenience functions for common casts. + + /// Cast into an `f32` vector. + #[inline] + pub fn to_f32(self) -> Vector3D<f32, U> { + self.cast() + } + + /// Cast into an `f64` vector. + #[inline] + pub fn to_f64(self) -> Vector3D<f64, U> { + self.cast() + } + + /// Cast into an `usize` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_usize(self) -> Vector3D<usize, U> { + self.cast() + } + + /// Cast into an `u32` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_u32(self) -> Vector3D<u32, U> { + self.cast() + } + + /// Cast into an `i32` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i32(self) -> Vector3D<i32, U> { + self.cast() + } + + /// Cast into an `i64` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i64(self) -> Vector3D<i64, U> { + self.cast() + } +} + +impl<T: Neg, U> Neg for Vector3D<T, U> { + type Output = Vector3D<T::Output, U>; + + #[inline] + fn neg(self) -> Self::Output { + vec3(-self.x, -self.y, -self.z) + } +} + +impl<T: Add, U> Add for Vector3D<T, U> { + type Output = Vector3D<T::Output, U>; + + #[inline] + fn add(self, other: Self) -> Self::Output { + vec3(self.x + other.x, self.y + other.y, self.z + other.z) + } +} + +impl<'a, T: 'a + Add + Copy, U: 'a> Add<&Self> for Vector3D<T, U> { + type Output = Vector3D<T::Output, U>; + + #[inline] + fn add(self, other: &Self) -> Self::Output { + vec3(self.x + other.x, self.y + other.y, self.z + other.z) + } +} + +impl<T: Add<Output = T> + Zero, U> Sum for Vector3D<T, U> { + fn sum<I: Iterator<Item=Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Vector3D<T, U> { + fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +impl<T: Copy + Add<T, Output = T>, U> AddAssign for Vector3D<T, U> { + #[inline] + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl<T: Sub, U> Sub for Vector3D<T, U> { + type Output = Vector3D<T::Output, U>; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + vec3(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl<T: Copy + Sub<T, Output = T>, U> SubAssign<Vector3D<T, U>> for Vector3D<T, U> { + #[inline] + fn sub_assign(&mut self, other: Self) { + *self = *self - other + } +} + +impl<T: Copy + Mul, U> Mul<T> for Vector3D<T, U> { + type Output = Vector3D<T::Output, U>; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + vec3( + self.x * scale, + self.y * scale, + self.z * scale, + ) + } +} + +impl<T: Copy + Mul<T, Output = T>, U> MulAssign<T> for Vector3D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Vector3D<T, U1> { + type Output = Vector3D<T::Output, U2>; + + #[inline] + fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { + vec3( + self.x * scale.0, + self.y * scale.0, + self.z * scale.0, + ) + } +} + +impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Vector3D<T, U> { + #[inline] + fn mul_assign(&mut self, scale: Scale<T, U, U>) { + self.x *= scale.0; + self.y *= scale.0; + self.z *= scale.0; + } +} + +impl<T: Copy + Div, U> Div<T> for Vector3D<T, U> { + type Output = Vector3D<T::Output, U>; + + #[inline] + fn div(self, scale: T) -> Self::Output { + vec3( + self.x / scale, + self.y / scale, + self.z / scale, + ) + } +} + +impl<T: Copy + Div<T, Output = T>, U> DivAssign<T> for Vector3D<T, U> { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Vector3D<T, U2> { + type Output = Vector3D<T::Output, U1>; + + #[inline] + fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { + vec3( + self.x / scale.0, + self.y / scale.0, + self.z / scale.0, + ) + } +} + +impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Vector3D<T, U> { + #[inline] + fn div_assign(&mut self, scale: Scale<T, U, U>) { + self.x /= scale.0; + self.y /= scale.0; + self.z /= scale.0; + } +} + +impl<T: Round, U> Round for Vector3D<T, U> { + /// See [`Vector3D::round()`](#method.round) + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl<T: Ceil, U> Ceil for Vector3D<T, U> { + /// See [`Vector3D::ceil()`](#method.ceil) + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl<T: Floor, U> Floor for Vector3D<T, U> { + /// See [`Vector3D::floor()`](#method.floor) + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl<T: ApproxEq<T>, U> ApproxEq<Vector3D<T, U>> for Vector3D<T, U> { + #[inline] + fn approx_epsilon() -> Self { + vec3( + T::approx_epsilon(), + T::approx_epsilon(), + T::approx_epsilon(), + ) + } + + #[inline] + fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool { + self.x.approx_eq_eps(&other.x, &eps.x) + && self.y.approx_eq_eps(&other.y, &eps.y) + && self.z.approx_eq_eps(&other.z, &eps.z) + } +} + +impl<T, U> Into<[T; 3]> for Vector3D<T, U> { + fn into(self) -> [T; 3] { + [self.x, self.y, self.z] + } +} + +impl<T, U> From<[T; 3]> for Vector3D<T, U> { + fn from([x, y, z]: [T; 3]) -> Self { + vec3(x, y, z) + } +} + +impl<T, U> Into<(T, T, T)> for Vector3D<T, U> { + fn into(self) -> (T, T, T) { + (self.x, self.y, self.z) + } +} + +impl<T, U> From<(T, T, T)> for Vector3D<T, U> { + fn from(tuple: (T, T, T)) -> Self { + vec3(tuple.0, tuple.1, tuple.2) + } +} + +/// A 2d vector of booleans, useful for component-wise logic operations. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct BoolVector2D { + pub x: bool, + pub y: bool, +} + +/// A 3d vector of booleans, useful for component-wise logic operations. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct BoolVector3D { + pub x: bool, + pub y: bool, + pub z: bool, +} + +impl BoolVector2D { + /// Returns `true` if all components are `true` and `false` otherwise. + #[inline] + pub fn all(self) -> bool { + self.x && self.y + } + + /// Returns `true` if any component are `true` and `false` otherwise. + #[inline] + pub fn any(self) -> bool { + self.x || self.y + } + + /// Returns `true` if all components are `false` and `false` otherwise. Negation of `any()`. + #[inline] + pub fn none(self) -> bool { + !self.any() + } + + /// Returns new vector with by-component AND operation applied. + #[inline] + pub fn and(self, other: Self) -> Self { + BoolVector2D { + x: self.x && other.x, + y: self.y && other.y, + } + } + + /// Returns new vector with by-component OR operation applied. + #[inline] + pub fn or(self, other: Self) -> Self { + BoolVector2D { + x: self.x || other.x, + y: self.y || other.y, + } + } + + /// Returns new vector with results of negation operation on each component. + #[inline] + pub fn not(self) -> Self { + BoolVector2D { + x: !self.x, + y: !self.y, + } + } + + /// Returns point, each component of which or from `a`, or from `b` depending on truly value + /// of corresponding vector component. `true` selects value from `a` and `false` from `b`. + #[inline] + pub fn select_point<T, U>(self, a: Point2D<T, U>, b: Point2D<T, U>) -> Point2D<T, U> { + point2( + if self.x { a.x } else { b.x }, + if self.y { a.y } else { b.y }, + ) + } + + /// Returns vector, each component of which or from `a`, or from `b` depending on truly value + /// of corresponding vector component. `true` selects value from `a` and `false` from `b`. + #[inline] + pub fn select_vector<T, U>(self, a: Vector2D<T, U>, b: Vector2D<T, U>) -> Vector2D<T, U> { + vec2( + if self.x { a.x } else { b.x }, + if self.y { a.y } else { b.y }, + ) + } + + /// Returns size, each component of which or from `a`, or from `b` depending on truly value + /// of corresponding vector component. `true` selects value from `a` and `false` from `b`. + #[inline] + pub fn select_size<T, U>(self, a: Size2D<T, U>, b: Size2D<T, U>) -> Size2D<T, U> { + size2( + if self.x { a.width } else { b.width }, + if self.y { a.height } else { b.height }, + ) + } +} + +impl BoolVector3D { + /// Returns `true` if all components are `true` and `false` otherwise. + #[inline] + pub fn all(self) -> bool { + self.x && self.y && self.z + } + + /// Returns `true` if any component are `true` and `false` otherwise. + #[inline] + pub fn any(self) -> bool { + self.x || self.y || self.z + } + + /// Returns `true` if all components are `false` and `false` otherwise. Negation of `any()`. + #[inline] + pub fn none(self) -> bool { + !self.any() + } + + /// Returns new vector with by-component AND operation applied. + #[inline] + pub fn and(self, other: Self) -> Self { + BoolVector3D { + x: self.x && other.x, + y: self.y && other.y, + z: self.z && other.z, + } + } + + /// Returns new vector with by-component OR operation applied. + #[inline] + pub fn or(self, other: Self) -> Self { + BoolVector3D { + x: self.x || other.x, + y: self.y || other.y, + z: self.z || other.z, + } + } + + /// Returns new vector with results of negation operation on each component. + #[inline] + pub fn not(self) -> Self { + BoolVector3D { + x: !self.x, + y: !self.y, + z: !self.z, + } + } + + /// Returns point, each component of which or from `a`, or from `b` depending on truly value + /// of corresponding vector component. `true` selects value from `a` and `false` from `b`. + #[inline] + pub fn select_point<T, U>(self, a: Point3D<T, U>, b: Point3D<T, U>) -> Point3D<T, U> { + point3( + if self.x { a.x } else { b.x }, + if self.y { a.y } else { b.y }, + if self.z { a.z } else { b.z }, + ) + } + + /// Returns vector, each component of which or from `a`, or from `b` depending on truly value + /// of corresponding vector component. `true` selects value from `a` and `false` from `b`. + #[inline] + pub fn select_vector<T, U>(self, a: Vector3D<T, U>, b: Vector3D<T, U>) -> Vector3D<T, U> { + vec3( + if self.x { a.x } else { b.x }, + if self.y { a.y } else { b.y }, + if self.z { a.z } else { b.z }, + ) + } + + /// Returns size, each component of which or from `a`, or from `b` depending on truly value + /// of corresponding vector component. `true` selects value from `a` and `false` from `b`. + #[inline] + #[must_use] + pub fn select_size<T, U>(self, a: Size3D<T, U>, b: Size3D<T, U>) -> Size3D<T, U> { + size3( + if self.x { a.width } else { b.width }, + if self.y { a.height } else { b.height }, + if self.z { a.depth } else { b.depth }, + ) + } + + /// Returns a 2d vector using this vector's x and y coordinates. + #[inline] + pub fn xy(self) -> BoolVector2D { + BoolVector2D { + x: self.x, + y: self.y, + } + } + + /// Returns a 2d vector using this vector's x and z coordinates. + #[inline] + pub fn xz(self) -> BoolVector2D { + BoolVector2D { + x: self.x, + y: self.z, + } + } + + /// Returns a 2d vector using this vector's y and z coordinates. + #[inline] + pub fn yz(self) -> BoolVector2D { + BoolVector2D { + x: self.y, + y: self.z, + } + } +} + +/// Convenience constructor. +#[inline] +pub const fn vec2<T, U>(x: T, y: T) -> Vector2D<T, U> { + Vector2D { + x, + y, + _unit: PhantomData, + } +} + +/// Convenience constructor. +#[inline] +pub const fn vec3<T, U>(x: T, y: T, z: T) -> Vector3D<T, U> { + Vector3D { + x, + y, + z, + _unit: PhantomData, + } +} + +/// Shorthand for `BoolVector2D { x, y }`. +#[inline] +pub const fn bvec2(x: bool, y: bool) -> BoolVector2D { + BoolVector2D { x, y } +} + +/// Shorthand for `BoolVector3D { x, y, z }`. +#[inline] +pub const fn bvec3(x: bool, y: bool, z: bool) -> BoolVector3D { + BoolVector3D { x, y, z } +} + +#[cfg(test)] +mod vector2d { + use crate::scale::Scale; + use crate::{default, vec2}; + + #[cfg(feature = "mint")] + use mint; + type Vec2 = default::Vector2D<f32>; + + #[test] + pub fn test_scalar_mul() { + let p1: Vec2 = vec2(3.0, 5.0); + + let result = p1 * 5.0; + + assert_eq!(result, Vec2::new(15.0, 25.0)); + } + + #[test] + pub fn test_dot() { + let p1: Vec2 = vec2(2.0, 7.0); + let p2: Vec2 = vec2(13.0, 11.0); + assert_eq!(p1.dot(p2), 103.0); + } + + #[test] + pub fn test_cross() { + let p1: Vec2 = vec2(4.0, 7.0); + let p2: Vec2 = vec2(13.0, 8.0); + let r = p1.cross(p2); + assert_eq!(r, -59.0); + } + + #[test] + pub fn test_normalize() { + use std::f32; + + let p0: Vec2 = Vec2::zero(); + let p1: Vec2 = vec2(4.0, 0.0); + let p2: Vec2 = vec2(3.0, -4.0); + assert!(p0.normalize().x.is_nan() && p0.normalize().y.is_nan()); + assert_eq!(p1.normalize(), vec2(1.0, 0.0)); + assert_eq!(p2.normalize(), vec2(0.6, -0.8)); + + let p3: Vec2 = vec2(::std::f32::MAX, ::std::f32::MAX); + assert_ne!( + p3.normalize(), + vec2(1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt()) + ); + assert_eq!( + p3.robust_normalize(), + vec2(1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt()) + ); + + let p4: Vec2 = Vec2::zero(); + assert!(p4.try_normalize().is_none()); + let p5: Vec2 = Vec2::new(f32::MIN_POSITIVE, f32::MIN_POSITIVE); + assert!(p5.try_normalize().is_none()); + + let p6: Vec2 = vec2(4.0, 0.0); + let p7: Vec2 = vec2(3.0, -4.0); + assert_eq!(p6.try_normalize().unwrap(), vec2(1.0, 0.0)); + assert_eq!(p7.try_normalize().unwrap(), vec2(0.6, -0.8)); + } + + #[test] + pub fn test_min() { + let p1: Vec2 = vec2(1.0, 3.0); + let p2: Vec2 = vec2(2.0, 2.0); + + let result = p1.min(p2); + + assert_eq!(result, vec2(1.0, 2.0)); + } + + #[test] + pub fn test_max() { + let p1: Vec2 = vec2(1.0, 3.0); + let p2: Vec2 = vec2(2.0, 2.0); + + let result = p1.max(p2); + + assert_eq!(result, vec2(2.0, 3.0)); + } + + #[test] + pub fn test_angle_from_x_axis() { + use crate::approxeq::ApproxEq; + use core::f32::consts::FRAC_PI_2; + + let right: Vec2 = vec2(10.0, 0.0); + let down: Vec2 = vec2(0.0, 4.0); + let up: Vec2 = vec2(0.0, -1.0); + + assert!(right.angle_from_x_axis().get().approx_eq(&0.0)); + assert!(down.angle_from_x_axis().get().approx_eq(&FRAC_PI_2)); + assert!(up.angle_from_x_axis().get().approx_eq(&-FRAC_PI_2)); + } + + #[test] + pub fn test_angle_to() { + use crate::approxeq::ApproxEq; + use core::f32::consts::FRAC_PI_2; + + let right: Vec2 = vec2(10.0, 0.0); + let right2: Vec2 = vec2(1.0, 0.0); + let up: Vec2 = vec2(0.0, -1.0); + let up_left: Vec2 = vec2(-1.0, -1.0); + + assert!(right.angle_to(right2).get().approx_eq(&0.0)); + assert!(right.angle_to(up).get().approx_eq(&-FRAC_PI_2)); + assert!(up.angle_to(right).get().approx_eq(&FRAC_PI_2)); + assert!(up_left + .angle_to(up) + .get() + .approx_eq_eps(&(0.5 * FRAC_PI_2), &0.0005)); + } + + #[test] + pub fn test_with_max_length() { + use crate::approxeq::ApproxEq; + + let v1: Vec2 = vec2(0.5, 0.5); + let v2: Vec2 = vec2(1.0, 0.0); + let v3: Vec2 = vec2(0.1, 0.2); + let v4: Vec2 = vec2(2.0, -2.0); + let v5: Vec2 = vec2(1.0, 2.0); + let v6: Vec2 = vec2(-1.0, 3.0); + + assert_eq!(v1.with_max_length(1.0), v1); + assert_eq!(v2.with_max_length(1.0), v2); + assert_eq!(v3.with_max_length(1.0), v3); + assert_eq!(v4.with_max_length(10.0), v4); + assert_eq!(v5.with_max_length(10.0), v5); + assert_eq!(v6.with_max_length(10.0), v6); + + let v4_clamped = v4.with_max_length(1.0); + assert!(v4_clamped.length().approx_eq(&1.0)); + assert!(v4_clamped.normalize().approx_eq(&v4.normalize())); + + let v5_clamped = v5.with_max_length(1.5); + assert!(v5_clamped.length().approx_eq(&1.5)); + assert!(v5_clamped.normalize().approx_eq(&v5.normalize())); + + let v6_clamped = v6.with_max_length(2.5); + assert!(v6_clamped.length().approx_eq(&2.5)); + assert!(v6_clamped.normalize().approx_eq(&v6.normalize())); + } + + #[test] + pub fn test_project_onto_vector() { + use crate::approxeq::ApproxEq; + + let v1: Vec2 = vec2(1.0, 2.0); + let x: Vec2 = vec2(1.0, 0.0); + let y: Vec2 = vec2(0.0, 1.0); + + assert!(v1.project_onto_vector(x).approx_eq(&vec2(1.0, 0.0))); + assert!(v1.project_onto_vector(y).approx_eq(&vec2(0.0, 2.0))); + assert!(v1.project_onto_vector(-x).approx_eq(&vec2(1.0, 0.0))); + assert!(v1.project_onto_vector(x * 10.0).approx_eq(&vec2(1.0, 0.0))); + assert!(v1.project_onto_vector(v1 * 2.0).approx_eq(&v1)); + assert!(v1.project_onto_vector(-v1).approx_eq(&v1)); + } + + #[cfg(feature = "mint")] + #[test] + pub fn test_mint() { + let v1 = Vec2::new(1.0, 3.0); + let vm: mint::Vector2<_> = v1.into(); + let v2 = Vec2::from(vm); + + assert_eq!(v1, v2); + } + + pub enum Mm {} + pub enum Cm {} + + pub type Vector2DMm<T> = super::Vector2D<T, Mm>; + pub type Vector2DCm<T> = super::Vector2D<T, Cm>; + + #[test] + pub fn test_add() { + let p1 = Vector2DMm::new(1.0, 2.0); + let p2 = Vector2DMm::new(3.0, 4.0); + + assert_eq!(p1 + p2, vec2(4.0, 6.0)); + assert_eq!(p1 + &p2, vec2(4.0, 6.0)); + } + + #[test] + pub fn test_sum() { + let vecs = [ + Vector2DMm::new(1.0, 2.0), + Vector2DMm::new(3.0, 4.0), + Vector2DMm::new(5.0, 6.0) + ]; + let sum = Vector2DMm::new(9.0, 12.0); + assert_eq!(vecs.iter().sum::<Vector2DMm<_>>(), sum); + } + + #[test] + pub fn test_add_assign() { + let mut p1 = Vector2DMm::new(1.0, 2.0); + p1 += vec2(3.0, 4.0); + + assert_eq!(p1, vec2(4.0, 6.0)); + } + + #[test] + pub fn test_tpyed_scalar_mul() { + let p1 = Vector2DMm::new(1.0, 2.0); + let cm_per_mm = Scale::<f32, Mm, Cm>::new(0.1); + + let result: Vector2DCm<f32> = p1 * cm_per_mm; + + assert_eq!(result, vec2(0.1, 0.2)); + } + + #[test] + pub fn test_swizzling() { + let p: default::Vector2D<i32> = vec2(1, 2); + assert_eq!(p.yx(), vec2(2, 1)); + } + + #[test] + pub fn test_reflect() { + use crate::approxeq::ApproxEq; + let a: Vec2 = vec2(1.0, 3.0); + let n1: Vec2 = vec2(0.0, -1.0); + let n2: Vec2 = vec2(1.0, -1.0).normalize(); + + assert!(a.reflect(n1).approx_eq(&vec2(1.0, -3.0))); + assert!(a.reflect(n2).approx_eq(&vec2(3.0, 1.0))); + } +} + +#[cfg(test)] +mod vector3d { + use crate::scale::Scale; + use crate::{default, vec2, vec3}; + #[cfg(feature = "mint")] + use mint; + + type Vec3 = default::Vector3D<f32>; + + #[test] + pub fn test_add() { + let p1 = Vec3::new(1.0, 2.0, 3.0); + let p2 = Vec3::new(4.0, 5.0, 6.0); + + assert_eq!(p1 + p2, vec3(5.0, 7.0, 9.0)); + assert_eq!(p1 + &p2, vec3(5.0, 7.0, 9.0)); + } + + #[test] + pub fn test_sum() { + let vecs = [ + Vec3::new(1.0, 2.0, 3.0), + Vec3::new(4.0, 5.0, 6.0), + Vec3::new(7.0, 8.0, 9.0) + ]; + let sum = Vec3::new(12.0, 15.0, 18.0); + assert_eq!(vecs.iter().sum::<Vec3>(), sum); + } + + #[test] + pub fn test_dot() { + let p1: Vec3 = vec3(7.0, 21.0, 32.0); + let p2: Vec3 = vec3(43.0, 5.0, 16.0); + assert_eq!(p1.dot(p2), 918.0); + } + + #[test] + pub fn test_cross() { + let p1: Vec3 = vec3(4.0, 7.0, 9.0); + let p2: Vec3 = vec3(13.0, 8.0, 3.0); + let p3 = p1.cross(p2); + assert_eq!(p3, vec3(-51.0, 105.0, -59.0)); + } + + #[test] + pub fn test_normalize() { + use std::f32; + + let p0: Vec3 = Vec3::zero(); + let p1: Vec3 = vec3(0.0, -6.0, 0.0); + let p2: Vec3 = vec3(1.0, 2.0, -2.0); + assert!( + p0.normalize().x.is_nan() && p0.normalize().y.is_nan() && p0.normalize().z.is_nan() + ); + assert_eq!(p1.normalize(), vec3(0.0, -1.0, 0.0)); + assert_eq!(p2.normalize(), vec3(1.0 / 3.0, 2.0 / 3.0, -2.0 / 3.0)); + + let p3: Vec3 = vec3(::std::f32::MAX, ::std::f32::MAX, 0.0); + assert_ne!( + p3.normalize(), + vec3(1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt(), 0.0) + ); + assert_eq!( + p3.robust_normalize(), + vec3(1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt(), 0.0) + ); + + let p4: Vec3 = Vec3::zero(); + assert!(p4.try_normalize().is_none()); + let p5: Vec3 = Vec3::new(f32::MIN_POSITIVE, f32::MIN_POSITIVE, f32::MIN_POSITIVE); + assert!(p5.try_normalize().is_none()); + + let p6: Vec3 = vec3(4.0, 0.0, 3.0); + let p7: Vec3 = vec3(3.0, -4.0, 0.0); + assert_eq!(p6.try_normalize().unwrap(), vec3(0.8, 0.0, 0.6)); + assert_eq!(p7.try_normalize().unwrap(), vec3(0.6, -0.8, 0.0)); + } + + #[test] + pub fn test_min() { + let p1: Vec3 = vec3(1.0, 3.0, 5.0); + let p2: Vec3 = vec3(2.0, 2.0, -1.0); + + let result = p1.min(p2); + + assert_eq!(result, vec3(1.0, 2.0, -1.0)); + } + + #[test] + pub fn test_max() { + let p1: Vec3 = vec3(1.0, 3.0, 5.0); + let p2: Vec3 = vec3(2.0, 2.0, -1.0); + + let result = p1.max(p2); + + assert_eq!(result, vec3(2.0, 3.0, 5.0)); + } + + #[test] + pub fn test_clamp() { + let p1: Vec3 = vec3(1.0, -1.0, 5.0); + let p2: Vec3 = vec3(2.0, 5.0, 10.0); + let p3: Vec3 = vec3(-1.0, 2.0, 20.0); + + let result = p3.clamp(p1, p2); + + assert_eq!(result, vec3(1.0, 2.0, 10.0)); + } + + #[test] + pub fn test_typed_scalar_mul() { + enum Mm {} + enum Cm {} + + let p1 = super::Vector3D::<f32, Mm>::new(1.0, 2.0, 3.0); + let cm_per_mm = Scale::<f32, Mm, Cm>::new(0.1); + + let result: super::Vector3D<f32, Cm> = p1 * cm_per_mm; + + assert_eq!(result, vec3(0.1, 0.2, 0.3)); + } + + #[test] + pub fn test_swizzling() { + let p: Vec3 = vec3(1.0, 2.0, 3.0); + assert_eq!(p.xy(), vec2(1.0, 2.0)); + assert_eq!(p.xz(), vec2(1.0, 3.0)); + assert_eq!(p.yz(), vec2(2.0, 3.0)); + } + + #[cfg(feature = "mint")] + #[test] + pub fn test_mint() { + let v1 = Vec3::new(1.0, 3.0, 5.0); + let vm: mint::Vector3<_> = v1.into(); + let v2 = Vec3::from(vm); + + assert_eq!(v1, v2); + } + + #[test] + pub fn test_reflect() { + use crate::approxeq::ApproxEq; + let a: Vec3 = vec3(1.0, 3.0, 2.0); + let n1: Vec3 = vec3(0.0, -1.0, 0.0); + let n2: Vec3 = vec3(0.0, 1.0, 1.0).normalize(); + + assert!(a.reflect(n1).approx_eq(&vec3(1.0, -3.0, 2.0))); + assert!(a.reflect(n2).approx_eq(&vec3(1.0, -2.0, -3.0))); + } + + #[test] + pub fn test_angle_to() { + use crate::approxeq::ApproxEq; + use core::f32::consts::FRAC_PI_2; + + let right: Vec3 = vec3(10.0, 0.0, 0.0); + let right2: Vec3 = vec3(1.0, 0.0, 0.0); + let up: Vec3 = vec3(0.0, -1.0, 0.0); + let up_left: Vec3 = vec3(-1.0, -1.0, 0.0); + + assert!(right.angle_to(right2).get().approx_eq(&0.0)); + assert!(right.angle_to(up).get().approx_eq(&FRAC_PI_2)); + assert!(up.angle_to(right).get().approx_eq(&FRAC_PI_2)); + assert!(up_left + .angle_to(up) + .get() + .approx_eq_eps(&(0.5 * FRAC_PI_2), &0.0005)); + } + + #[test] + pub fn test_with_max_length() { + use crate::approxeq::ApproxEq; + + let v1: Vec3 = vec3(0.5, 0.5, 0.0); + let v2: Vec3 = vec3(1.0, 0.0, 0.0); + let v3: Vec3 = vec3(0.1, 0.2, 0.3); + let v4: Vec3 = vec3(2.0, -2.0, 2.0); + let v5: Vec3 = vec3(1.0, 2.0, -3.0); + let v6: Vec3 = vec3(-1.0, 3.0, 2.0); + + assert_eq!(v1.with_max_length(1.0), v1); + assert_eq!(v2.with_max_length(1.0), v2); + assert_eq!(v3.with_max_length(1.0), v3); + assert_eq!(v4.with_max_length(10.0), v4); + assert_eq!(v5.with_max_length(10.0), v5); + assert_eq!(v6.with_max_length(10.0), v6); + + let v4_clamped = v4.with_max_length(1.0); + assert!(v4_clamped.length().approx_eq(&1.0)); + assert!(v4_clamped.normalize().approx_eq(&v4.normalize())); + + let v5_clamped = v5.with_max_length(1.5); + assert!(v5_clamped.length().approx_eq(&1.5)); + assert!(v5_clamped.normalize().approx_eq(&v5.normalize())); + + let v6_clamped = v6.with_max_length(2.5); + assert!(v6_clamped.length().approx_eq(&2.5)); + assert!(v6_clamped.normalize().approx_eq(&v6.normalize())); + } + + #[test] + pub fn test_project_onto_vector() { + use crate::approxeq::ApproxEq; + + let v1: Vec3 = vec3(1.0, 2.0, 3.0); + let x: Vec3 = vec3(1.0, 0.0, 0.0); + let y: Vec3 = vec3(0.0, 1.0, 0.0); + let z: Vec3 = vec3(0.0, 0.0, 1.0); + + assert!(v1.project_onto_vector(x).approx_eq(&vec3(1.0, 0.0, 0.0))); + assert!(v1.project_onto_vector(y).approx_eq(&vec3(0.0, 2.0, 0.0))); + assert!(v1.project_onto_vector(z).approx_eq(&vec3(0.0, 0.0, 3.0))); + assert!(v1.project_onto_vector(-x).approx_eq(&vec3(1.0, 0.0, 0.0))); + assert!(v1 + .project_onto_vector(x * 10.0) + .approx_eq(&vec3(1.0, 0.0, 0.0))); + assert!(v1.project_onto_vector(v1 * 2.0).approx_eq(&v1)); + assert!(v1.project_onto_vector(-v1).approx_eq(&v1)); + } +} + +#[cfg(test)] +mod bool_vector { + use super::*; + use crate::default; + type Vec2 = default::Vector2D<f32>; + type Vec3 = default::Vector3D<f32>; + + #[test] + fn test_bvec2() { + assert_eq!( + Vec2::new(1.0, 2.0).greater_than(Vec2::new(2.0, 1.0)), + bvec2(false, true), + ); + + assert_eq!( + Vec2::new(1.0, 2.0).lower_than(Vec2::new(2.0, 1.0)), + bvec2(true, false), + ); + + assert_eq!( + Vec2::new(1.0, 2.0).equal(Vec2::new(1.0, 3.0)), + bvec2(true, false), + ); + + assert_eq!( + Vec2::new(1.0, 2.0).not_equal(Vec2::new(1.0, 3.0)), + bvec2(false, true), + ); + + assert!(bvec2(true, true).any()); + assert!(bvec2(false, true).any()); + assert!(bvec2(true, false).any()); + assert!(!bvec2(false, false).any()); + assert!(bvec2(false, false).none()); + assert!(bvec2(true, true).all()); + assert!(!bvec2(false, true).all()); + assert!(!bvec2(true, false).all()); + assert!(!bvec2(false, false).all()); + + assert_eq!(bvec2(true, false).not(), bvec2(false, true)); + assert_eq!( + bvec2(true, false).and(bvec2(true, true)), + bvec2(true, false) + ); + assert_eq!(bvec2(true, false).or(bvec2(true, true)), bvec2(true, true)); + + assert_eq!( + bvec2(true, false).select_vector(Vec2::new(1.0, 2.0), Vec2::new(3.0, 4.0)), + Vec2::new(1.0, 4.0), + ); + } + + #[test] + fn test_bvec3() { + assert_eq!( + Vec3::new(1.0, 2.0, 3.0).greater_than(Vec3::new(3.0, 2.0, 1.0)), + bvec3(false, false, true), + ); + + assert_eq!( + Vec3::new(1.0, 2.0, 3.0).lower_than(Vec3::new(3.0, 2.0, 1.0)), + bvec3(true, false, false), + ); + + assert_eq!( + Vec3::new(1.0, 2.0, 3.0).equal(Vec3::new(3.0, 2.0, 1.0)), + bvec3(false, true, false), + ); + + assert_eq!( + Vec3::new(1.0, 2.0, 3.0).not_equal(Vec3::new(3.0, 2.0, 1.0)), + bvec3(true, false, true), + ); + + assert!(bvec3(true, true, false).any()); + assert!(bvec3(false, true, false).any()); + assert!(bvec3(true, false, false).any()); + assert!(!bvec3(false, false, false).any()); + assert!(bvec3(false, false, false).none()); + assert!(bvec3(true, true, true).all()); + assert!(!bvec3(false, true, false).all()); + assert!(!bvec3(true, false, false).all()); + assert!(!bvec3(false, false, false).all()); + + assert_eq!(bvec3(true, false, true).not(), bvec3(false, true, false)); + assert_eq!( + bvec3(true, false, true).and(bvec3(true, true, false)), + bvec3(true, false, false) + ); + assert_eq!( + bvec3(true, false, false).or(bvec3(true, true, false)), + bvec3(true, true, false) + ); + + assert_eq!( + bvec3(true, false, true) + .select_vector(Vec3::new(1.0, 2.0, 3.0), Vec3::new(4.0, 5.0, 6.0)), + Vec3::new(1.0, 5.0, 3.0), + ); + } +} |