From 8551ea795cda8c399fd542001f0835576055f4cc Mon Sep 17 00:00:00 2001 From: Arjun Ramachandrula Date: Wed, 30 Dec 2020 10:48:40 -0500 Subject: [PATCH] Removed euclid dependency, while allowing for more math functions. --- android-example/rust/Cargo.toml | 4 - android-example/rust/src/lib.rs | 2 +- surfman/Cargo.toml | 5 +- surfman/examples/chaos_game.rs | 2 +- surfman/examples/offscreen.rs | 2 +- surfman/examples/threads.rs | 2 +- surfman/src/connection.rs | 2 +- surfman/src/device.rs | 2 +- surfman/src/geom/angle.rs | 327 +++ surfman/src/geom/approxeq.rs | 44 + surfman/src/geom/approxord.rs | 46 + surfman/src/geom/box2d.rs | 845 ++++++ surfman/src/geom/box3d.rs | 921 +++++++ surfman/src/geom/homogen.rs | 219 ++ surfman/src/geom/length.rs | 569 ++++ surfman/src/geom/macros.rs | 30 + surfman/src/geom/mod.rs | 94 + surfman/src/geom/num.rs | 132 + surfman/src/geom/point.rs | 1965 ++++++++++++++ surfman/src/geom/rect.rs | 929 +++++++ surfman/src/geom/rigid.rs | 274 ++ surfman/src/geom/rotation.rs | 984 +++++++ surfman/src/geom/scale.rs | 420 +++ surfman/src/geom/side_offsets.rs | 450 ++++ surfman/src/geom/size.rs | 1687 ++++++++++++ surfman/src/geom/transform2d.rs | 803 ++++++ surfman/src/geom/transform3d.rs | 1721 ++++++++++++ surfman/src/geom/translation.rs | 823 ++++++ surfman/src/geom/trig.rs | 82 + surfman/src/geom/vector.rs | 2379 +++++++++++++++++ surfman/src/implementation/connection.rs | 2 +- surfman/src/implementation/device.rs | 2 +- surfman/src/lib.rs | 3 + surfman/src/platform/android/connection.rs | 2 +- surfman/src/platform/android/surface.rs | 2 +- surfman/src/platform/generic/egl/surface.rs | 2 +- surfman/src/platform/generic/mod.rs | 2 +- .../src/platform/generic/multi/connection.rs | 2 +- surfman/src/platform/generic/multi/device.rs | 2 +- surfman/src/platform/generic/multi/surface.rs | 2 +- surfman/src/platform/macos/cgl/connection.rs | 2 +- surfman/src/platform/macos/cgl/surface.rs | 2 +- .../src/platform/macos/system/connection.rs | 2 +- surfman/src/platform/macos/system/surface.rs | 8 +- .../src/platform/unix/generic/connection.rs | 2 +- surfman/src/platform/unix/generic/surface.rs | 2 +- surfman/src/platform/unix/mod.rs | 2 +- .../src/platform/unix/wayland/connection.rs | 2 +- surfman/src/platform/unix/wayland/surface.rs | 2 +- surfman/src/platform/unix/x11/connection.rs | 2 +- surfman/src/platform/unix/x11/mod.rs | 2 +- surfman/src/platform/unix/x11/surface.rs | 2 +- surfman/src/renderbuffers.rs | 2 +- surfman/src/surface.rs | 2 +- surfman/src/tests.rs | 2 +- 55 files changed, 15783 insertions(+), 39 deletions(-) create mode 100644 surfman/src/geom/angle.rs create mode 100644 surfman/src/geom/approxeq.rs create mode 100644 surfman/src/geom/approxord.rs create mode 100644 surfman/src/geom/box2d.rs create mode 100644 surfman/src/geom/box3d.rs create mode 100644 surfman/src/geom/homogen.rs create mode 100644 surfman/src/geom/length.rs create mode 100644 surfman/src/geom/macros.rs create mode 100644 surfman/src/geom/mod.rs create mode 100644 surfman/src/geom/num.rs create mode 100644 surfman/src/geom/point.rs create mode 100644 surfman/src/geom/rect.rs create mode 100644 surfman/src/geom/rigid.rs create mode 100644 surfman/src/geom/rotation.rs create mode 100644 surfman/src/geom/scale.rs create mode 100644 surfman/src/geom/side_offsets.rs create mode 100644 surfman/src/geom/size.rs create mode 100644 surfman/src/geom/transform2d.rs create mode 100644 surfman/src/geom/transform3d.rs create mode 100644 surfman/src/geom/translation.rs create mode 100644 surfman/src/geom/trig.rs create mode 100644 surfman/src/geom/vector.rs diff --git a/android-example/rust/Cargo.toml b/android-example/rust/Cargo.toml index 10bc4fb7..c2a5d143 100644 --- a/android-example/rust/Cargo.toml +++ b/android-example/rust/Cargo.toml @@ -19,10 +19,6 @@ gl = "0.14" jni = "0.13" log = "0.4" -[dependencies.euclid] -version = "0.20" -features = [] - [dependencies.surfman] path = "../../surfman" features = ["sm-test"] diff --git a/android-example/rust/src/lib.rs b/android-example/rust/src/lib.rs index d7a4390f..8806835c 100644 --- a/android-example/rust/src/lib.rs +++ b/android-example/rust/src/lib.rs @@ -7,7 +7,7 @@ use crate::threads::common::ResourceLoader; use crate::threads::App; use android_logger::Config; -use euclid::default::Size2D; +use surfman::geom::default::Size2D; use jni::objects::{GlobalRef, JByteBuffer, JClass, JObject, JValue}; use jni::{JNIEnv, JavaVM}; use log::Level; diff --git a/surfman/Cargo.toml b/surfman/Cargo.toml index bbdd8d8b..4e13e0bd 100644 --- a/surfman/Cargo.toml +++ b/surfman/Cargo.toml @@ -34,10 +34,7 @@ lazy_static = "1" libc = "0.2" log = "0.4" parking_lot = "0.10.2" - -[dependencies.euclid] -version = "0.20" -features = [] +num-traits = "0.2" [dependencies.osmesa-sys] version = "0.1" diff --git a/surfman/examples/chaos_game.rs b/surfman/examples/chaos_game.rs index 9204cb50..003bc5d9 100644 --- a/surfman/examples/chaos_game.rs +++ b/surfman/examples/chaos_game.rs @@ -2,8 +2,8 @@ // //! Demonstrates how to use `surfman` to draw to a window surface via the CPU. -use euclid::default::Point2D; use rand::{self, Rng}; +use surfman::geom::default::Point2D; use surfman::{SurfaceAccess, SurfaceType}; use winit::dpi::PhysicalSize; use winit::{DeviceEvent, Event, EventsLoop, KeyboardInput, VirtualKeyCode}; diff --git a/surfman/examples/offscreen.rs b/surfman/examples/offscreen.rs index f6e605a0..8c2f0492 100644 --- a/surfman/examples/offscreen.rs +++ b/surfman/examples/offscreen.rs @@ -6,7 +6,6 @@ use crate::common::{ck, Buffer, FilesystemResourceLoader, Program, Shader, ShaderKind}; use clap::{App, Arg}; -use euclid::default::Size2D; use gl; use gl::types::{GLchar, GLenum, GLint, GLuint, GLvoid}; use png::{BitDepth, ColorType, Encoder}; @@ -14,6 +13,7 @@ use std::fs::File; use std::mem; use std::path::Path; use std::slice; +use surfman::geom::Size2D; use surfman::{Connection, ContextAttributeFlags, ContextAttributes, GLApi, GLVersion}; use surfman::{SurfaceAccess, SurfaceType}; diff --git a/surfman/examples/threads.rs b/surfman/examples/threads.rs index ec6f9ea1..deefed5d 100644 --- a/surfman/examples/threads.rs +++ b/surfman/examples/threads.rs @@ -4,10 +4,10 @@ use self::common::{ck, Buffer, Program, ResourceLoader, Shader, ShaderKind}; -use euclid::default::{Point2D, Rect, Size2D, Vector2D}; use gl::types::{GLchar, GLenum, GLint, GLuint, GLvoid}; use std::sync::mpsc::{self, Receiver, Sender}; use std::thread; +use surfman::geom::default::{Point2D, Rect, Size2D, Vector2D}; use surfman::{declare_surfman, SurfaceAccess, SurfaceTexture, SurfaceType}; use surfman::{Adapter, Connection, Context, ContextDescriptor, Device, GLApi, Surface}; diff --git a/surfman/src/connection.rs b/surfman/src/connection.rs index 91fc34e1..f4a58505 100644 --- a/surfman/src/connection.rs +++ b/surfman/src/connection.rs @@ -5,7 +5,7 @@ use crate::Error; use crate::GLApi; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::os::raw::c_void; diff --git a/surfman/src/device.rs b/surfman/src/device.rs index a684e680..639fec28 100644 --- a/surfman/src/device.rs +++ b/surfman/src/device.rs @@ -3,9 +3,9 @@ //! The abstract interface that all devices conform to. use super::connection::Connection as ConnectionInterface; +use crate::geom::default::Size2D; use crate::gl::types::{GLenum, GLuint}; use crate::{ContextAttributes, ContextID, Error, GLApi, SurfaceAccess, SurfaceInfo, SurfaceType}; -use euclid::default::Size2D; use std::os::raw::c_void; diff --git a/surfman/src/geom/angle.rs b/surfman/src/geom/angle.rs new file mode 100644 index 00000000..e86a5cf1 --- /dev/null +++ b/surfman/src/geom/angle.rs @@ -0,0 +1,327 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::geom::approxeq::ApproxEq; +use crate::geom::trig::Trig; +use core::cmp::{Eq, PartialEq}; +use core::hash::Hash; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; +use num_traits::{Float, FloatConst, NumCast, One, Zero}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// An angle in radians +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Angle { + /// Radians + pub radians: T, +} + +impl Angle { + /// Retusn the radians. + #[inline] + pub fn radians(radians: T) -> Self { + Angle { radians } + } + + /// Getter. + #[inline] + pub fn get(self) -> T { + self.radians + } +} + +impl Angle +where + T: Trig, +{ + /// Returns the degrees. + #[inline] + pub fn degrees(deg: T) -> Self { + Angle { + radians: T::degrees_to_radians(deg), + } + } + + /// Converts the radians to degrees. + #[inline] + pub fn to_degrees(self) -> T { + T::radians_to_degrees(self.radians) + } +} + +impl Angle +where + T: Rem + Sub + Add + 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 Angle +where + T: Rem + + Mul + + Sub + + Add + + 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 Angle +where + T: Float, +{ + /// Returns (sin(self), cos(self)). + pub fn sin_cos(self) -> (T, T) { + self.radians.sin_cos() + } +} + +impl Angle +where + T: Zero, +{ + /// Returns zero in radians. + pub fn zero() -> Self { + Angle::radians(T::zero()) + } +} + +impl Angle +where + T: FloatConst + Add, +{ + /// Returns pi in radians + pub fn pi() -> Self { + Angle::radians(T::PI()) + } + + /// Returns 2pi in radians. + pub fn two_pi() -> Self { + Angle::radians(T::PI() + T::PI()) + } + + /// Returns pi/2 radians. + pub fn frac_pi_2() -> Self { + Angle::radians(T::FRAC_PI_2()) + } + + /// Returns pi/3 radians. + pub fn frac_pi_3() -> Self { + Angle::radians(T::FRAC_PI_3()) + } + + /// Returns pi/4 radians. + pub fn frac_pi_4() -> Self { + Angle::radians(T::FRAC_PI_4()) + } +} + +impl Angle +where + T: NumCast + Copy, +{ + /// Cast from one numeric representation to another. + #[inline] + pub fn cast(&self) -> Angle { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another. + pub fn try_cast(&self) -> Option> { + NumCast::from(self.radians).map(|radians| Angle { radians }) + } + + // Convenience functions for common casts. + + /// Cast angle to `f32`. + #[inline] + pub fn to_f32(&self) -> Angle { + self.cast() + } + + /// Cast angle `f64`. + #[inline] + pub fn to_f64(&self) -> Angle { + self.cast() + } +} + +impl> Add for Angle { + type Output = Angle; + fn add(self, other: Angle) -> Angle { + Angle::radians(self.radians + other.radians) + } +} + +impl> AddAssign for Angle { + fn add_assign(&mut self, other: Angle) { + self.radians += other.radians; + } +} + +impl> Sub> for Angle { + type Output = Angle; + fn sub(self, other: Angle) -> ::Output { + Angle::radians(self.radians - other.radians) + } +} + +impl> SubAssign for Angle { + fn sub_assign(&mut self, other: Angle) { + self.radians -= other.radians; + } +} + +impl> Div> for Angle { + type Output = T; + #[inline] + fn div(self, other: Angle) -> T { + self.radians / other.radians + } +} + +impl> Div for Angle { + type Output = Angle; + #[inline] + fn div(self, factor: T) -> Angle { + Angle::radians(self.radians / factor) + } +} + +impl> DivAssign for Angle { + fn div_assign(&mut self, factor: T) { + self.radians /= factor; + } +} + +impl> Mul for Angle { + type Output = Angle; + #[inline] + fn mul(self, factor: T) -> Angle { + Angle::radians(self.radians * factor) + } +} + +impl> MulAssign for Angle { + fn mul_assign(&mut self, factor: T) { + self.radians *= factor; + } +} + +impl> Neg for Angle { + type Output = Self; + fn neg(self) -> Self { + Angle::radians(-self.radians) + } +} + +impl> ApproxEq for Angle { + #[inline] + fn approx_epsilon() -> T { + T::approx_epsilon() + } + + #[inline] + fn approx_eq_eps(&self, other: &Angle, 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; + + 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))); +} diff --git a/surfman/src/geom/approxeq.rs b/surfman/src/geom/approxeq.rs new file mode 100644 index 00000000..c7b825b4 --- /dev/null +++ b/surfman/src/geom/approxeq.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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Approximate equality module. + +/// Trait for testing approximate equality +pub trait ApproxEq { + /// 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/surfman/src/geom/approxord.rs b/surfman/src/geom/approxord.rs new file mode 100644 index 00000000..33052692 --- /dev/null +++ b/surfman/src/geom/approxord.rs @@ -0,0 +1,46 @@ +// 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 or the MIT license +// , 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. + +/// minimum comparison of the two values. +pub fn min(x: T, y: T) -> T { + if x <= y { + x + } else { + y + } +} + +/// maximum comparison of the two values. +pub fn max(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/surfman/src/geom/box2d.rs b/surfman/src/geom/box2d.rs new file mode 100644 index 00000000..a812093f --- /dev/null +++ b/surfman/src/geom/box2d.rs @@ -0,0 +1,845 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::geom::approxord::{max, min}; +use crate::geom::num::*; +use crate::geom::point::{point2, Point2D}; +use crate::geom::rect::Rect; +use crate::geom::scale::Scale; +use crate::geom::side_offsets::SideOffsets2D; +use crate::geom::size::Size2D; +use crate::geom::vector::{vec2, Vector2D}; + +use num_traits::NumCast; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +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}; + +/// 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 { + /// min + pub min: Point2D, + /// max + pub max: Point2D, +} + +impl Hash for Box2D { + fn hash(&self, h: &mut H) { + self.min.hash(h); + self.max.hash(h); + } +} + +impl Copy for Box2D {} + +impl Clone for Box2D { + fn clone(&self) -> Self { + Self::new(self.min.clone(), self.max.clone()) + } +} + +impl PartialEq for Box2D { + fn eq(&self, other: &Self) -> bool { + self.min.eq(&other.min) && self.max.eq(&other.max) + } +} + +impl Eq for Box2D {} + +impl fmt::Debug for Box2D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Box2D") + .field(&self.min) + .field(&self.max) + .finish() + } +} + +impl Box2D { + /// Constructor. + #[inline] + pub const fn new(min: Point2D, max: Point2D) -> Self { + Box2D { min, max } + } +} + +impl Box2D +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) -> 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 Box2D +where + T: Copy + PartialOrd, +{ + /// Returns the option'd non-empty box. + #[inline] + pub fn to_non_empty(&self) -> Option { + 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 { + 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)), + } + } + + /// Returns the union between the two boxes. + #[inline] + pub fn union(&self, other: &Self) -> Self { + 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 Box2D +where + T: Copy + Add, +{ + /// Returns the same box, translated by a vector. + #[inline] + pub fn translate(&self, by: Vector2D) -> Self { + Box2D { + min: self.min + by, + max: self.max + by, + } + } +} + +impl Box2D +where + T: Copy + Sub, +{ + /// Returns the box's size. + #[inline] + pub fn size(&self) -> Size2D { + (self.max - self.min).to_size() + } + + /// Returns the box's width. + #[inline] + pub fn width(&self) -> T { + self.max.x - self.min.x + } + + /// Returns the box's height. + #[inline] + pub fn height(&self) -> T { + self.max.y - self.min.y + } + + /// Returns the box as a rectangle. + #[inline] + pub fn to_rect(&self) -> Rect { + Rect { + origin: self.min, + size: self.size(), + } + } +} + +impl Box2D +where + T: Copy + Add + Sub, +{ + /// 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) -> 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) -> Self { + Box2D { + min: self.min - vec2(offsets.left, offsets.top), + max: self.max + vec2(offsets.right, offsets.bottom), + } + } +} + +impl Box2D +where + T: Copy + Zero + PartialOrd, +{ + /// Creates a Box2D of the given size, at offset zero. + #[inline] + pub fn from_size(size: Size2D) -> Self { + let zero = Point2D::zero(); + let point = size.to_vector().to_point(); + Box2D::from_points(&[zero, point]) + } + + /// Returns the smallest box containing all of the provided points. + pub fn from_points(points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow>, + { + 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 Box2D +where + T: Copy + One + Add + Sub + Mul, +{ + /// 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 Box2D +where + T: Copy + One + Add + Div, +{ + /// Returns the center of the box. + pub fn center(&self) -> Point2D { + let two = T::one() + T::one(); + (self.min + self.max.to_vector()) / two + } +} + +impl Box2D +where + T: Copy + Mul + Sub, +{ + /// Returns the area of the box. + #[inline] + pub fn area(&self) -> T { + let size = self.size(); + size.width * size.height + } +} + +impl Box2D +where + T: Zero, +{ + /// Constructor, setting all sides to zero. + pub fn zero() -> Self { + Box2D::new(Point2D::zero(), Point2D::zero()) + } +} + +impl Mul for Box2D { + type Output = Box2D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Box2D::new(self.min * scale, self.max * scale) + } +} + +impl MulAssign for Box2D { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self *= Scale::new(scale); + } +} + +impl Div for Box2D { + type Output = Box2D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Box2D::new(self.min / scale, self.max / scale) + } +} + +impl DivAssign for Box2D { + #[inline] + fn div_assign(&mut self, scale: T) { + *self /= Scale::new(scale); + } +} + +impl Mul> for Box2D { + type Output = Box2D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + Box2D::new(self.min * scale, self.max * scale) + } +} + +impl MulAssign> for Box2D { + #[inline] + fn mul_assign(&mut self, scale: Scale) { + self.min *= scale; + self.max *= scale; + } +} + +impl Div> for Box2D { + type Output = Box2D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + Box2D::new(self.min / scale, self.max / scale) + } +} + +impl DivAssign> for Box2D { + #[inline] + fn div_assign(&mut self, scale: Scale) { + self.min /= scale; + self.max /= scale; + } +} + +impl Box2D +where + T: Copy, +{ + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Box2D { + Box2D::new(self.min.to_untyped(), self.max.to_untyped()) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(c: &Box2D) -> Box2D { + Box2D::new(Point2D::from_untyped(c.min), Point2D::from_untyped(c.max)) + } + + /// Cast the unit + #[inline] + pub fn cast_unit(&self) -> Box2D { + Box2D::new(self.min.cast_unit(), self.max.cast_unit()) + } + + /// Returns the scaled box. + #[inline] + pub fn scale(&self, x: S, y: S) -> Self + where + T: Mul, + { + Box2D { + min: point2(self.min.x * x, self.min.y * y), + max: point2(self.max.x * x, self.max.y * y), + } + } +} + +impl Box2D { + /// 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(&self) -> Box2D { + 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(&self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` box. + #[inline] + pub fn to_f64(&self) -> Box2D { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl Box2D +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 Box2D +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 From> for Box2D +where + T: Copy + Zero + PartialOrd, +{ + fn from(b: Size2D) -> Self { + Self::from_size(b) + } +} + +#[cfg(test)] +mod tests { + use crate::geom::default::{Box2D, Point2D, SideOffsets2D}; + use crate::geom::point::point2; + use crate::geom::size::size2; + use crate::geom::vector::vec2; + + #[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()); + } +} diff --git a/surfman/src/geom/box3d.rs b/surfman/src/geom/box3d.rs new file mode 100644 index 00000000..ede8e2f7 --- /dev/null +++ b/surfman/src/geom/box3d.rs @@ -0,0 +1,921 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::geom::approxord::{max, min}; +use crate::geom::num::*; +use crate::geom::point::{point3, Point3D}; +use crate::geom::scale::Scale; +use crate::geom::size::Size3D; +use crate::geom::vector::Vector3D; + +use num_traits::NumCast; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +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}; + +/// 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 { + /// min + pub min: Point3D, + /// max + pub max: Point3D, +} + +impl Hash for Box3D { + fn hash(&self, h: &mut H) { + self.min.hash(h); + self.max.hash(h); + } +} + +impl Copy for Box3D {} + +impl Clone for Box3D { + fn clone(&self) -> Self { + Self::new(self.min.clone(), self.max.clone()) + } +} + +impl PartialEq for Box3D { + fn eq(&self, other: &Self) -> bool { + self.min.eq(&other.min) && self.max.eq(&other.max) + } +} + +impl Eq for Box3D {} + +impl fmt::Debug for Box3D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Box3D") + .field(&self.min) + .field(&self.max) + .finish() + } +} + +impl Box3D { + /// Constructor. + #[inline] + pub const fn new(min: Point3D, max: Point3D) -> Self { + Box3D { min, max } + } +} + +impl Box3D +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) + } + + /// Do this and the other box 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 + && 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) -> 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 Box3D +where + T: Copy + PartialOrd, +{ + /// Returns the option'd non-empty box. + #[inline] + pub fn to_non_empty(&self) -> Option { + if self.is_empty() { + return None; + } + + Some(*self) + } + + /// Returns the intersection. + #[inline] + pub fn intersection(&self, other: &Self) -> Option { + let b = self.intersection_unchecked(other); + + if b.is_empty() { + return None; + } + + Some(b) + } + + /// Returns the unchecked intersection. + 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) + } + + /// Returns the smallest box containing both of the provided boxes. + #[inline] + pub fn union(&self, other: &Self) -> Self { + 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 Box3D +where + T: Copy + Add, +{ + /// Returns the same box3d, translated by a vector. + #[inline] + #[must_use] + pub fn translate(&self, by: Vector3D) -> Self { + Box3D { + min: self.min + by, + max: self.max + by, + } + } +} + +impl Box3D +where + T: Copy + Sub, +{ + /// Returns the size + #[inline] + pub fn size(&self) -> Size3D { + Size3D::new( + self.max.x - self.min.x, + self.max.y - self.min.y, + self.max.z - self.min.z, + ) + } + + /// Returns the width. + #[inline] + pub fn width(&self) -> T { + self.max.x - self.min.x + } + + /// Returns the height. + #[inline] + pub fn height(&self) -> T { + self.max.y - self.min.y + } + + /// Returns the depth. + #[inline] + pub fn depth(&self) -> T { + self.max.z - self.min.z + } +} + +impl Box3D +where + T: Copy + Add + Sub, +{ + /// 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 Box3D +where + T: Copy + Zero + PartialOrd, +{ + /// Creates a Box3D of the given size, at offset zero. + #[inline] + pub fn from_size(size: Size3D) -> Self { + let zero = Point3D::zero(); + let point = size.to_vector().to_point(); + Box3D::from_points(&[zero, point]) + } + + /// Returns the smallest box containing all of the provided points. + pub fn from_points(points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow>, + { + 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 Box3D +where + T: Copy + One + Add + Sub + Mul, +{ + /// 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 Box3D +where + T: Copy + One + Add + Div, +{ + /// Returns the center point of the box. + pub fn center(&self) -> Point3D { + let two = T::one() + T::one(); + (self.min + self.max.to_vector()) / two + } +} + +impl Box3D +where + T: Copy + Mul + Sub, +{ + /// Returns the volume + #[inline] + pub fn volume(&self) -> T { + let size = self.size(); + size.width * size.height * size.depth + } + + /// Returns the xy area + #[inline] + pub fn xy_area(&self) -> T { + let size = self.size(); + size.width * size.height + } + + /// Returns the yz area + #[inline] + pub fn yz_area(&self) -> T { + let size = self.size(); + size.depth * size.height + } + + /// Returns the xz area + #[inline] + pub fn xz_area(&self) -> T { + let size = self.size(); + size.depth * size.width + } +} + +impl Box3D +where + T: Zero, +{ + /// Constructor, setting all sides to zero. + pub fn zero() -> Self { + Box3D::new(Point3D::zero(), Point3D::zero()) + } +} + +impl Mul for Box3D { + type Output = Box3D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Box3D::new(self.min * scale, self.max * scale) + } +} + +impl MulAssign for Box3D { + #[inline] + fn mul_assign(&mut self, scale: T) { + self.min *= scale; + self.max *= scale; + } +} + +impl Div for Box3D { + type Output = Box3D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Box3D::new(self.min / scale.clone(), self.max / scale) + } +} + +impl DivAssign for Box3D { + #[inline] + fn div_assign(&mut self, scale: T) { + self.min /= scale; + self.max /= scale; + } +} + +impl Mul> for Box3D { + type Output = Box3D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + Box3D::new(self.min * scale.clone(), self.max * scale) + } +} + +impl MulAssign> for Box3D { + #[inline] + fn mul_assign(&mut self, scale: Scale) { + self.min *= scale.clone(); + self.max *= scale; + } +} + +impl Div> for Box3D { + type Output = Box3D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + Box3D::new(self.min / scale.clone(), self.max / scale) + } +} + +impl DivAssign> for Box3D { + #[inline] + fn div_assign(&mut self, scale: Scale) { + self.min /= scale.clone(); + self.max /= scale; + } +} + +impl Box3D +where + T: Copy, +{ + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Box3D { + Box3D { + min: self.min.to_untyped(), + max: self.max.to_untyped(), + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(c: &Box3D) -> Box3D { + Box3D { + min: Point3D::from_untyped(c.min), + max: Point3D::from_untyped(c.max), + } + } + + /// Cast the unit + #[inline] + pub fn cast_unit(&self) -> Box3D { + Box3D::new(self.min.cast_unit(), self.max.cast_unit()) + } + + /// Scales the box by a value. + #[inline] + pub fn scale(&self, x: S, y: S, z: S) -> Self + where + T: Mul, + { + 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 Box3D { + /// 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(&self) -> Box3D { + 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(&self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` box3d. + #[inline] + pub fn to_f64(&self) -> Box3D { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl Box3D +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 Box3D +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 From> for Box3D +where + T: Copy + Zero + PartialOrd, +{ + fn from(b: Size3D) -> Self { + Self::from_size(b) + } +} + +/// Shorthand for `Box3D::new(Point3D::new(x1, y1, z1), Point3D::new(x2, y2, z2))`. +pub fn box3d( + min_x: T, + min_y: T, + min_z: T, + max_x: T, + max_y: T, + max_z: T, +) -> Box3D { + Box3D::new( + Point3D::new(min_x, min_y, min_z), + Point3D::new(max_x, max_y, max_z), + ) +} + +#[cfg(test)] +mod tests { + use crate::geom::default::{Box3D, Point3D}; + use crate::geom::{point::point3, size::size3, vector::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::::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/surfman/src/geom/homogen.rs b/surfman/src/geom/homogen.rs new file mode 100644 index 00000000..7a936f43 --- /dev/null +++ b/surfman/src/geom/homogen.rs @@ -0,0 +1,219 @@ +// Copyright 2018 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::geom::point::{Point2D, Point3D}; +use crate::geom::vector::{Vector2D, Vector3D}; + +use crate::geom::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; + +/// Homogeneous vector in 3D space. +#[repr(C)] +pub struct HomogeneousVector { + /// x + pub x: T, + /// y + pub y: T, + /// z + pub z: T, + /// w + pub w: T, + #[doc(hidden)] + pub _unit: PhantomData, +} + +impl Copy for HomogeneousVector {} + +impl Clone for HomogeneousVector { + 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 +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + 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 serde::Serialize for HomogeneousVector +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.x, &self.y, &self.z, &self.w).serialize(serializer) + } +} + +impl Eq for HomogeneousVector where T: Eq {} + +impl PartialEq for HomogeneousVector +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 Hash for HomogeneousVector +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + self.z.hash(h); + self.w.hash(h); + } +} + +impl HomogeneousVector { + /// 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 + Zero + PartialOrd, U> HomogeneousVector { + /// 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> { + 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> { + if self.w > T::zero() { + Some(Point3D::new( + self.x / self.w, + self.y / self.w, + self.z / self.w, + )) + } else { + None + } + } +} + +impl From> for HomogeneousVector { + #[inline] + fn from(v: Vector2D) -> Self { + HomogeneousVector::new(v.x, v.y, T::zero(), T::zero()) + } +} + +impl From> for HomogeneousVector { + #[inline] + fn from(v: Vector3D) -> Self { + HomogeneousVector::new(v.x, v.y, v.z, T::zero()) + } +} + +impl From> for HomogeneousVector { + #[inline] + fn from(p: Point2D) -> Self { + HomogeneousVector::new(p.x, p.y, T::zero(), T::one()) + } +} + +impl From> for HomogeneousVector { + #[inline] + fn from(p: Point3D) -> Self { + HomogeneousVector::new(p.x, p.y, p.z, T::one()) + } +} + +impl fmt::Debug for HomogeneousVector { + 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::geom::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::::new(1.0, 2.0, 3.0, 0.0).to_point2d() + ); + assert_eq!( + None, + HomogeneousVector::::new(1.0, -2.0, -3.0, -2.0).to_point3d() + ); + } +} diff --git a/surfman/src/geom/length.rs b/surfman/src/geom/length.rs new file mode 100644 index 00000000..36cc24b1 --- /dev/null +++ b/surfman/src/geom/length.rs @@ -0,0 +1,569 @@ +// 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 or the MIT license +// , 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::geom::approxeq::ApproxEq; +use crate::geom::approxord::{max, min}; +use crate::geom::num::{One, Zero}; +use crate::geom::scale::Scale; + +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +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}; + +/// 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(pub T, #[doc(hidden)] pub PhantomData); + +impl Clone for Length { + fn clone(&self) -> Self { + Length(self.0.clone(), PhantomData) + } +} + +impl Copy for Length {} + +#[cfg(feature = "serde")] +impl<'de, T, U> Deserialize<'de> for Length +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Length(Deserialize::deserialize(deserializer)?, PhantomData)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Length +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +impl Length { + /// Associate a value with a unit of measure. + #[inline] + pub const fn new(x: T) -> Self { + Length(x, PhantomData) + } +} + +impl Length { + /// Unpack the underlying value from the wrapper. + pub fn get(self) -> T { + self.0 + } + + /// Cast the unit + #[inline] + pub fn cast_unit(self) -> Length { + Length::new(self.0) + } + + /// Linearly interpolate between this length and another length. + /// + /// # Example + /// + /// ```rust + /// use surfman::geom::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 + Mul + Add, + { + let one_t = T::one() - t.clone(); + Length::new(one_t * self.0.clone() + t * other.0) + } +} + +impl Length { + /// 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 Length { + /// Cast from one numeric representation to another, preserving the units. + #[inline] + pub fn cast(self) -> Length { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + pub fn try_cast(self) -> Option> { + NumCast::from(self.0).map(Length::new) + } +} + +impl fmt::Debug for Length { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Default for Length { + #[inline] + fn default() -> Self { + Length::new(Default::default()) + } +} + +impl Hash for Length { + fn hash(&self, h: &mut H) { + self.0.hash(h); + } +} + +// length + length +impl Add for Length { + type Output = Length; + + fn add(self, other: Self) -> Self::Output { + Length::new(self.0 + other.0) + } +} + +// length += length +impl AddAssign for Length { + fn add_assign(&mut self, other: Self) { + self.0 += other.0; + } +} + +// length - length +impl Sub for Length { + type Output = Length; + + fn sub(self, other: Length) -> Self::Output { + Length::new(self.0 - other.0) + } +} + +// length -= length +impl SubAssign for Length { + fn sub_assign(&mut self, other: Self) { + self.0 -= other.0; + } +} + +// Saturating length + length and length - length. +impl Saturating for Length { + 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 Div> for Length { + type Output = Scale; + + #[inline] + fn div(self, other: Length) -> Self::Output { + Scale::new(self.0 / other.0) + } +} + +// length * scalar +impl Mul for Length { + type Output = Length; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Length::new(self.0 * scale) + } +} + +// length *= scalar +impl, U> MulAssign for Length { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +// length / scalar +impl Div for Length { + type Output = Length; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Length::new(self.0 / scale) + } +} + +// length /= scalar +impl, U> DivAssign for Length { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +// length * scaleFactor +impl Mul> for Length { + type Output = Length; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + Length::new(self.0 * scale.0) + } +} + +// length / scaleFactor +impl Div> for Length { + type Output = Length; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + Length::new(self.0 / scale.0) + } +} + +// -length +impl Neg for Length { + type Output = Length; + + #[inline] + fn neg(self) -> Self::Output { + Length::new(-self.0) + } +} + +impl PartialEq for Length { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl PartialOrd for Length { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Eq for Length {} + +impl Ord for Length { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl Zero for Length { + #[inline] + fn zero() -> Self { + Length::new(Zero::zero()) + } +} + +impl> ApproxEq for Length { + #[inline] + fn approx_epsilon() -> T { + T::approx_epsilon() + } + + #[inline] + fn approx_eq_eps(&self, other: &Length, approx_epsilon: &T) -> bool { + self.0.approx_eq_eps(&other.0, approx_epsilon) + } +} + +#[cfg(test)] +mod tests { + use super::Length; + use crate::geom::num::Zero; + + use crate::geom::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 = 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 = 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 = Length::new(250); + let length2: Length = Length::new(5); + + let result = length1 + length2; + + assert_eq!(result.get(), 255); + } + + #[test] + fn test_addassign() { + let one_cm: Length = Length::new(10.0); + let mut measurement: Length = Length::new(5.0); + + measurement += one_cm; + + assert_eq!(measurement.get(), 15.0); + } + + #[test] + fn test_sub() { + let length1: Length = Length::new(250); + let length2: Length = Length::new(5); + + let result = length1 - length2; + + assert_eq!(result.get(), 245); + } + + #[test] + fn test_subassign() { + let one_cm: Length = Length::new(10.0); + let mut measurement: Length = Length::new(5.0); + + measurement -= one_cm; + + assert_eq!(measurement.get(), -5.0); + } + + #[test] + fn test_saturating_add() { + let length1: Length = Length::new(250); + let length2: Length = Length::new(6); + + let result = length1.saturating_add(length2); + + assert_eq!(result.get(), 255); + } + + #[test] + fn test_saturating_sub() { + let length1: Length = Length::new(5); + let length2: Length = 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 = Length::new(5.0); + let duration: Length = Length::new(10.0); + + let result = length / duration; + + let expected: Scale = Scale::new(0.5); + assert_eq!(result, expected); + } + + #[test] + fn test_multiplication() { + let length_mm: Length = Length::new(10.0); + let cm_per_mm: Scale = Scale::new(0.1); + + let result = length_mm * cm_per_mm; + + let expected: Length = Length::new(1.0); + assert_eq!(result, expected); + } + + #[test] + fn test_multiplication_with_scalar() { + let length_mm: Length = Length::new(10.0); + + let result = length_mm * 2.0; + + let expected: Length = Length::new(20.0); + assert_eq!(result, expected); + } + + #[test] + fn test_multiplication_assignment() { + let mut length: Length = Length::new(10.0); + + length *= 2.0; + + let expected: Length = Length::new(20.0); + assert_eq!(length, expected); + } + + #[test] + fn test_division_by_scalefactor() { + let length: Length = Length::new(5.0); + let cm_per_second: Scale = Scale::new(10.0); + + let result = length / cm_per_second; + + let expected: Length = Length::new(0.5); + assert_eq!(result, expected); + } + + #[test] + fn test_division_by_scalar() { + let length: Length = Length::new(5.0); + + let result = length / 2.0; + + let expected: Length = Length::new(2.5); + assert_eq!(result, expected); + } + + #[test] + fn test_division_assignment() { + let mut length: Length = Length::new(10.0); + + length /= 2.0; + + let expected: Length = Length::new(5.0); + assert_eq!(length, expected); + } + + #[test] + fn test_negation() { + let length: Length = Length::new(5.0); + + let result = -length; + + let expected: Length = Length::new(-5.0); + assert_eq!(result, expected); + } + + #[test] + fn test_cast() { + let length_as_i32: Length = Length::new(5); + + let result: Length = length_as_i32.cast(); + + let length_as_f32: Length = Length::new(5.0); + assert_eq!(result, length_as_f32); + } + + #[test] + fn test_equality() { + let length_5_point_0: Length = Length::new(5.0); + let length_5_point_1: Length = Length::new(5.1); + let length_0_point_1: Length = 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 = Length::new(5.0); + let length_5_point_1: Length = Length::new(5.1); + let length_0_point_1: Length = 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; + let length: LengthCm = Length::new(5.0); + + let result = length - LengthCm::zero(); + + assert_eq!(result, length); + } + + #[test] + fn test_zero_division() { + type LengthCm = Length; + let length: LengthCm = Length::new(5.0); + let length_zero: LengthCm = Length::zero(); + + let result = length / length_zero; + + let expected: Scale = Scale::new(INFINITY); + assert_eq!(result, expected); + } +} diff --git a/surfman/src/geom/macros.rs b/surfman/src/geom/macros.rs new file mode 100644 index 00000000..9cc392eb --- /dev/null +++ b/surfman/src/geom/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 or the MIT license +// , 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 From> for $name { + fn from(v: mint::$std_name) -> Self { + $name { + $( $field: v.$field, )* + _unit: PhantomData, + } + } + } + #[cfg(feature = "mint")] + impl Into> for $name { + fn into(self) -> mint::$std_name { + mint::$std_name { + $( $field: self.$field, )* + } + } + } + } +} diff --git a/surfman/src/geom/mod.rs b/surfman/src/geom/mod.rs new file mode 100644 index 00000000..e8dd40a1 --- /dev/null +++ b/surfman/src/geom/mod.rs @@ -0,0 +1,94 @@ +//! Your euclid replacement, with any kind of shape you would ever need. +#[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; +pub mod vector; + +pub use crate::geom::angle::Angle; +pub use crate::geom::box2d::Box2D; +pub use crate::geom::homogen::HomogeneousVector; +pub use crate::geom::length::Length; +pub use crate::geom::point::{point2, point3, Point2D, Point3D}; +pub use crate::geom::scale::Scale; +pub use crate::geom::transform2d::Transform2D; +pub use crate::geom::transform3d::Transform3D; +pub use crate::geom::vector::{bvec2, bvec3, BoolVector2D, BoolVector3D}; +pub use crate::geom::vector::{vec2, vec3, Vector2D, Vector3D}; + +pub use crate::geom::box3d::{box3d, Box3D}; +pub use crate::geom::rect::{rect, Rect}; +pub use crate::geom::rigid::RigidTransform3D; +pub use crate::geom::rotation::{Rotation2D, Rotation3D}; +pub use crate::geom::side_offsets::SideOffsets2D; +pub use crate::geom::size::{size2, size3, Size2D, Size3D}; +pub use crate::geom::translation::{Translation2D, Translation3D}; +pub use crate::geom::trig::Trig; + +/// 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; + /// Length with no unit. + pub type Length = super::Length; + /// Point with no unit. + pub type Point2D = super::Point2D; + /// 3DPoint with no unit. + pub type Point3D = super::Point3D; + /// Vector with no unit. + pub type Vector2D = super::Vector2D; + /// 3DVector with no unit. + pub type Vector3D = super::Vector3D; + /// Homogeneous Vector with no unit. + pub type HomogeneousVector = super::HomogeneousVector; + /// Size with no unit. + pub type Size2D = super::Size2D; + /// 3DSize with no unit. + pub type Size3D = super::Size3D; + /// Rect with no unit. + pub type Rect = super::Rect; + /// Box with no unit. + pub type Box2D = super::Box2D; + /// 3D Box with no unit. + pub type Box3D = super::Box3D; + /// Side offsets with no unit. + pub type SideOffsets2D = super::SideOffsets2D; + /// Transform with no unit. + pub type Transform2D = super::Transform2D; + /// 3D Transform with no unit. + pub type Transform3D = super::Transform3D; + /// Rotation with no unit. + pub type Rotation2D = super::Rotation2D; + /// 3D Rotation with no unit. + pub type Rotation3D = super::Rotation3D; + /// Translation with no unit. + pub type Translation2D = super::Translation2D; + /// 3DTranslation with no unit. + pub type Translation3D = super::Translation3D; + /// Scale with no unit. + pub type Scale = super::Scale; + /// Rigid 3D Transformation with no unit. + pub type RigidTransform3D = super::RigidTransform3D; +} diff --git a/surfman/src/geom/num.rs b/surfman/src/geom/num.rs new file mode 100644 index 00000000..ee272950 --- /dev/null +++ b/surfman/src/geom/num.rs @@ -0,0 +1,132 @@ +// 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 or the MIT license +// , 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 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 ::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. + +/// Trait Zero +pub trait Zero { + /// function zero + fn zero() -> Self; +} + +impl Zero for T { + fn zero() -> T { + num_traits::Zero::zero() + } +} + +/// Trait One +pub trait One { + /// function one + fn one() -> Self; +} + +impl 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/surfman/src/geom/point.rs b/surfman/src/geom/point.rs new file mode 100644 index 00000000..bff17aca --- /dev/null +++ b/surfman/src/geom/point.rs @@ -0,0 +1,1965 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::geom::approxeq::ApproxEq; +use crate::geom::approxord::{max, min}; +use crate::geom::length::Length; +use crate::geom::num::*; +use crate::geom::scale::Scale; +use crate::geom::size::{Size2D, Size3D}; +use crate::geom::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::{Float, NumCast}; +#[cfg(feature = "serde")] +use serde; + +/// A 2d Point tagged with a unit. +#[repr(C)] +pub struct Point2D { + /// x + pub x: T, + /// y + pub y: T, + #[doc(hidden)] + pub _unit: PhantomData, +} + +impl Copy for Point2D {} + +impl Clone for Point2D { + 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 +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let (x, y) = serde::Deserialize::deserialize(deserializer)?; + Ok(Point2D { + x, + y, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Point2D +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.x, &self.y).serialize(serializer) + } +} + +impl Eq for Point2D where T: Eq {} + +impl PartialEq for Point2D +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +impl Hash for Point2D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + } +} + +mint_vec!(Point2D[x, y] = Point2); + +impl fmt::Debug for Point2D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("").field(&self.x).field(&self.y).finish() + } +} + +impl Default for Point2D { + fn default() -> Self { + Point2D::new(Default::default(), Default::default()) + } +} + +impl Point2D { + /// 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, y: Length) -> Self { + point2(x.0, y.0) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Point2D) -> Self { + point2(p.x, p.y) + } +} + +impl Point2D { + /// Create a 3d point from this one, using the specified z value. + #[inline] + pub fn extend(self, z: T) -> Point3D { + 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 { + Vector2D { + x: self.x, + y: self.y, + _unit: PhantomData, + } + } + + /// Swap x and y. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::{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 surfman::geom::{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 { + point2(self.x, self.y) + } + + /// Cast the unit, preserving the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::{Point2D, point2}; + /// enum Mm {} + /// enum Cm {} + /// + /// let point: Point2D<_, Mm> = point2(1, -8); + /// + /// assert_eq!(point.x, point.cast_unit::().x); + /// assert_eq!(point.y, point.cast_unit::().y); + /// ``` + #[inline] + pub fn cast_unit(self) -> Point2D { + point2(self.x, self.y) + } + + /// Cast into an array with x and y. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::{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 surfman::geom::{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 + 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 surfman::geom::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 surfman::geom::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 surfman::geom::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 surfman::geom::point2; + /// use surfman::geom::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 + Mul + Add, + { + let one_t = T::one() - t; + point2(one_t * self.x + t * other.x, one_t * self.y + t * other.y) + } +} + +impl Point2D { + /// Return minimum point between this and another + #[inline] + pub fn min(self, other: Self) -> Self { + point2(min(self.x, other.x), min(self.y, other.y)) + } + + /// Return maximum point between this and another + #[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 Point2D { + /// 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(self) -> Point2D { + 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(self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` point. + #[inline] + pub fn to_f64(self) -> Point2D { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl, U> Point2D { + /// Add this to a size + #[inline] + pub fn add_size(self, other: &Size2D) -> Self { + point2(self.x + other.width, self.y + other.height) + } +} + +impl, U> Point2D { + /// Get the distance between this and the other. + #[inline] + pub fn distance_to(self, other: Self) -> T { + (self - other).length() + } +} + +impl Neg for Point2D { + type Output = Point2D; + + #[inline] + fn neg(self) -> Self::Output { + point2(-self.x, -self.y) + } +} + +impl Add> for Point2D { + type Output = Point2D; + + #[inline] + fn add(self, other: Size2D) -> Self::Output { + point2(self.x + other.width, self.y + other.height) + } +} + +impl AddAssign> for Point2D { + #[inline] + fn add_assign(&mut self, other: Size2D) { + self.x += other.width; + self.y += other.height; + } +} + +impl Add> for Point2D { + type Output = Point2D; + + #[inline] + fn add(self, other: Vector2D) -> Self::Output { + point2(self.x + other.x, self.y + other.y) + } +} + +impl, U> AddAssign> for Point2D { + #[inline] + fn add_assign(&mut self, other: Vector2D) { + *self = *self + other + } +} + +impl Sub for Point2D { + type Output = Vector2D; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + vec2(self.x - other.x, self.y - other.y) + } +} + +impl Sub> for Point2D { + type Output = Point2D; + + #[inline] + fn sub(self, other: Size2D) -> Self::Output { + point2(self.x - other.width, self.y - other.height) + } +} + +impl SubAssign> for Point2D { + #[inline] + fn sub_assign(&mut self, other: Size2D) { + self.x -= other.width; + self.y -= other.height; + } +} + +impl Sub> for Point2D { + type Output = Point2D; + + #[inline] + fn sub(self, other: Vector2D) -> Self::Output { + point2(self.x - other.x, self.y - other.y) + } +} + +impl, U> SubAssign> for Point2D { + #[inline] + fn sub_assign(&mut self, other: Vector2D) { + *self = *self - other + } +} + +impl Mul for Point2D { + type Output = Point2D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + point2(self.x * scale, self.y * scale) + } +} + +impl, U> MulAssign for Point2D { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +impl Mul> for Point2D { + type Output = Point2D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + point2(self.x * scale.0, self.y * scale.0) + } +} + +impl MulAssign> for Point2D { + #[inline] + fn mul_assign(&mut self, scale: Scale) { + self.x *= scale.0; + self.y *= scale.0; + } +} + +impl Div for Point2D { + type Output = Point2D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + point2(self.x / scale, self.y / scale) + } +} + +impl, U> DivAssign for Point2D { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +impl Div> for Point2D { + type Output = Point2D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + point2(self.x / scale.0, self.y / scale.0) + } +} + +impl DivAssign> for Point2D { + #[inline] + fn div_assign(&mut self, scale: Scale) { + self.x /= scale.0; + self.y /= scale.0; + } +} + +impl Zero for Point2D { + #[inline] + fn zero() -> Self { + Self::origin() + } +} + +impl Round for Point2D { + /// See [Point2D::round()](#method.round) + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl Ceil for Point2D { + /// See [Point2D::ceil()](#method.ceil) + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl Floor for Point2D { + /// See [Point2D::floor()](#method.floor) + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl, U> ApproxEq> for Point2D { + #[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 Into<[T; 2]> for Point2D { + fn into(self) -> [T; 2] { + [self.x, self.y] + } +} + +impl From<[T; 2]> for Point2D { + fn from([x, y]: [T; 2]) -> Self { + point2(x, y) + } +} + +impl Into<(T, T)> for Point2D { + fn into(self) -> (T, T) { + (self.x, self.y) + } +} + +impl From<(T, T)> for Point2D { + fn from(tuple: (T, T)) -> Self { + point2(tuple.0, tuple.1) + } +} + +/// A 3d Point tagged with a unit. +#[repr(C)] +pub struct Point3D { + /// x + pub x: T, + /// y + pub y: T, + /// z + pub z: T, + #[doc(hidden)] + pub _unit: PhantomData, +} + +mint_vec!(Point3D[x, y, z] = Point3); + +impl Copy for Point3D {} + +impl Clone for Point3D { + 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 +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let (x, y, z) = serde::Deserialize::deserialize(deserializer)?; + Ok(Point3D { + x, + y, + z, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Point3D +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.x, &self.y, &self.z).serialize(serializer) + } +} + +impl Eq for Point3D where T: Eq {} + +impl PartialEq for Point3D +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z + } +} + +impl Hash for Point3D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + self.z.hash(h); + } +} + +impl fmt::Debug for Point3D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("") + .field(&self.x) + .field(&self.y) + .field(&self.z) + .finish() + } +} + +impl Default for Point3D { + fn default() -> Self { + Point3D::new(Default::default(), Default::default(), Default::default()) + } +} + +impl Point3D { + /// 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, y: Length, z: Length) -> Self { + point3(x.0, y.0, z.0) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Point3D) -> Self { + point3(p.x, p.y, p.z) + } +} + +impl Point3D { + /// Cast this point into a vector. + /// + /// Equivalent to subtracting the origin to this point. + #[inline] + pub fn to_vector(self) -> Vector3D { + 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 { + point2(self.x, self.y) + } + + /// Returns a 2d point using this point's x and z coordinates + #[inline] + pub fn xz(self) -> Point2D { + point2(self.x, self.z) + } + + /// Returns a 2d point using this point's x and z coordinates + #[inline] + pub fn yz(self) -> Point2D { + point2(self.y, self.z) + } + + /// Cast into an array with x, y and z. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::{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] + } + + /// Go from Point3D to an array of length 4. + #[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 surfman::geom::{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) + } + + /// Go from Point3D to a tuple of length 4. + #[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 surfman::geom::{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 { + point3(self.x, self.y, self.z) + } + + /// Cast the unit, preserving the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::{Point3D, point3}; + /// enum Mm {} + /// enum Cm {} + /// + /// let point: Point3D<_, Mm> = point3(1, -8, 0); + /// + /// assert_eq!(point.x, point.cast_unit::().x); + /// assert_eq!(point.y, point.cast_unit::().y); + /// assert_eq!(point.z, point.cast_unit::().z); + /// ``` + #[inline] + pub fn cast_unit(self) -> Point3D { + point3(self.x, self.y, self.z) + } + + /// Convert into a 2d point. + #[inline] + pub fn to_2d(self) -> Point2D { + self.xy() + } + + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use surfman::geom::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 surfman::geom::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 surfman::geom::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 surfman::geom::point3; + /// use surfman::geom::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 + Mul + Add, + { + 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 Point3D { + /// Returns the minimum point between this and the other. + #[inline] + pub fn min(self, other: Self) -> Self { + point3( + min(self.x, other.x), + min(self.y, other.y), + min(self.z, other.z), + ) + } + + /// Returns the maximum point between this and the other. + #[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 Point3D { + /// 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(self) -> Point3D { + 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(self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` point. + #[inline] + pub fn to_f64(self) -> Point3D { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl, U> Point3D { + /// Add the size to this point. + #[inline] + pub fn add_size(self, other: Size3D) -> Self { + point3( + self.x + other.width, + self.y + other.height, + self.z + other.depth, + ) + } +} + +impl, U> Point3D { + /// Find the distance from this to the other point. + #[inline] + pub fn distance_to(self, other: Self) -> T { + (self - other).length() + } +} + +impl Neg for Point3D { + type Output = Point3D; + + #[inline] + fn neg(self) -> Self::Output { + point3(-self.x, -self.y, -self.z) + } +} + +impl Add> for Point3D { + type Output = Point3D; + + #[inline] + fn add(self, other: Size3D) -> Self::Output { + point3( + self.x + other.width, + self.y + other.height, + self.z + other.depth, + ) + } +} + +impl AddAssign> for Point3D { + #[inline] + fn add_assign(&mut self, other: Size3D) { + self.x += other.width; + self.y += other.height; + self.z += other.depth; + } +} + +impl Add> for Point3D { + type Output = Point3D; + + #[inline] + fn add(self, other: Vector3D) -> Self::Output { + point3(self.x + other.x, self.y + other.y, self.z + other.z) + } +} + +impl, U> AddAssign> for Point3D { + #[inline] + fn add_assign(&mut self, other: Vector3D) { + *self = *self + other + } +} + +impl Sub for Point3D { + type Output = Vector3D; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + vec3(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl Sub> for Point3D { + type Output = Point3D; + + #[inline] + fn sub(self, other: Size3D) -> Self::Output { + point3( + self.x - other.width, + self.y - other.height, + self.z - other.depth, + ) + } +} + +impl SubAssign> for Point3D { + #[inline] + fn sub_assign(&mut self, other: Size3D) { + self.x -= other.width; + self.y -= other.height; + self.z -= other.depth; + } +} + +impl Sub> for Point3D { + type Output = Point3D; + + #[inline] + fn sub(self, other: Vector3D) -> Self::Output { + point3(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl, U> SubAssign> for Point3D { + #[inline] + fn sub_assign(&mut self, other: Vector3D) { + *self = *self - other + } +} + +impl Mul for Point3D { + type Output = Point3D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + point3(self.x * scale, self.y * scale, self.z * scale) + } +} + +impl MulAssign for Point3D { + #[inline] + fn mul_assign(&mut self, scale: T) { + self.x *= scale; + self.y *= scale; + self.z *= scale; + } +} + +impl Mul> for Point3D { + type Output = Point3D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + point3(self.x * scale.0, self.y * scale.0, self.z * scale.0) + } +} + +impl MulAssign> for Point3D { + #[inline] + fn mul_assign(&mut self, scale: Scale) { + *self *= scale.0; + } +} + +impl Div for Point3D { + type Output = Point3D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + point3(self.x / scale, self.y / scale, self.z / scale) + } +} + +impl DivAssign for Point3D { + #[inline] + fn div_assign(&mut self, scale: T) { + self.x /= scale; + self.y /= scale; + self.z /= scale; + } +} + +impl Div> for Point3D { + type Output = Point3D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + point3(self.x / scale.0, self.y / scale.0, self.z / scale.0) + } +} + +impl DivAssign> for Point3D { + #[inline] + fn div_assign(&mut self, scale: Scale) { + *self /= scale.0; + } +} + +impl Zero for Point3D { + #[inline] + fn zero() -> Self { + Self::origin() + } +} + +impl Round for Point3D { + /// See [Point3D::round()](#method.round) + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl Ceil for Point3D { + /// See [Point3D::ceil()](#method.ceil) + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl Floor for Point3D { + /// See [Point3D::floor()](#method.floor) + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl, U> ApproxEq> for Point3D { + #[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 Into<[T; 3]> for Point3D { + fn into(self) -> [T; 3] { + [self.x, self.y, self.z] + } +} + +impl From<[T; 3]> for Point3D { + fn from([x, y, z]: [T; 3]) -> Self { + point3(x, y, z) + } +} + +impl Into<(T, T, T)> for Point3D { + fn into(self) -> (T, T, T) { + (self.x, self.y, self.z) + } +} + +impl From<(T, T, T)> for Point3D { + 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(x: T, y: T) -> Point2D { + Point2D { + x, + y, + _unit: PhantomData, + } +} + +/// Shorthand for `Point3D::new(x, y)`. +#[inline] +pub const fn point3(x: T, y: T, z: T) -> Point3D { + Point3D { + x, + y, + z, + _unit: PhantomData, + } +} + +#[cfg(test)] +mod point2d { + use crate::geom::default::Point2D; + use crate::geom::point::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 = point2(x, y); + assert_eq!(p.to_vector().to_point(), p); + } + } + + #[test] + pub fn test_swizzling() { + let p: Point2D = 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::geom::default::Point2D; + use crate::geom::scale::Scale; + use crate::geom::{size::size2, vector::vec2, vector::Vector2D}; + + pub enum Mm {} + pub enum Cm {} + + pub type Point2DMm = crate::geom::point::Point2D; + pub type Point2DCm = crate::geom::point::Point2D; + + #[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 = 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 = 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 = Scale::new(0.1); + + p1 *= scale; + + assert_eq!(p1, Point2DMm::new(0.1, 0.2)); + } + + #[test] + pub fn test_div_scalar() { + let p1: Point2D = 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 = 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 = 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 = 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::geom::default; + use crate::geom::default::Point3D; + use crate::geom::point::{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::geom::point::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 = point3(x, y, z); + assert_eq!(p.to_vector().to_point(), p); + } + } + + #[test] + pub fn test_swizzling() { + let p: default::Point3D = 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::geom::default::Point3D; + use crate::geom::scale::Scale; + use crate::geom::{size::size3, vector::vec3, vector::Vector3D}; + + pub enum Mm {} + pub enum Cm {} + + pub type Point3DMm = crate::geom::point::Point3D; + pub type Point3DCm = crate::geom::point::Point3D; + + #[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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = Scale::new(0.1); + + p1 /= scale; + + assert_eq!(p1, Point3DMm::new(1.0, 2.0, 3.0)); + } + } +} diff --git a/surfman/src/geom/rect.rs b/surfman/src/geom/rect.rs new file mode 100644 index 00000000..61b84dda --- /dev/null +++ b/surfman/src/geom/rect.rs @@ -0,0 +1,929 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::geom::box2d::Box2D; +use crate::geom::num::*; +use crate::geom::point::Point2D; +use crate::geom::scale::Scale; +use crate::geom::side_offsets::SideOffsets2D; +use crate::geom::size::Size2D; +use crate::geom::vector::Vector2D; + +use num_traits::NumCast; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +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 [`Rect`] 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 { + /// origin + pub origin: Point2D, + /// size + pub size: Size2D, +} + +impl Hash for Rect { + fn hash(&self, h: &mut H) { + self.origin.hash(h); + self.size.hash(h); + } +} + +impl Copy for Rect {} + +impl Clone for Rect { + fn clone(&self) -> Self { + Self::new(self.origin.clone(), self.size.clone()) + } +} + +impl PartialEq for Rect { + fn eq(&self, other: &Self) -> bool { + self.origin.eq(&other.origin) && self.size.eq(&other.size) + } +} + +impl Eq for Rect {} + +impl fmt::Debug for Rect { + 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 Default for Rect { + fn default() -> Self { + Rect::new(Default::default(), Default::default()) + } +} + +impl Rect { + /// Constructor. + #[inline] + pub const fn new(origin: Point2D, size: Size2D) -> Self { + Rect { origin, size } + } +} + +impl Rect +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) -> Self { + Rect { + origin: Point2D::zero(), + size, + } + } +} + +impl Rect +where + T: Copy + Add, +{ + /// min of rect + #[inline] + pub fn min(&self) -> Point2D { + self.origin + } + + /// max of rect + #[inline] + pub fn max(&self) -> Point2D { + self.origin + self.size + } + + /// max x of rect + #[inline] + pub fn max_x(&self) -> T { + self.origin.x + self.size.width + } + + /// min x of rect + #[inline] + pub fn min_x(&self) -> T { + self.origin.x + } + + /// max y of rect + #[inline] + pub fn max_y(&self) -> T { + self.origin.y + self.size.height + } + + /// min y of rect + #[inline] + pub fn min_y(&self) -> T { + self.origin.y + } + + /// width of x + #[inline] + pub fn width(&self) -> T { + self.size.width + } + + /// height of x + #[inline] + pub fn height(&self) -> T { + self.size.height + } + + /// range of x + #[inline] + pub fn x_range(&self) -> Range { + self.min_x()..self.max_x() + } + + /// range of y + #[inline] + pub fn y_range(&self) -> Range { + self.min_y()..self.max_y() + } + + /// Returns the same rectangle, translated by a vector. + #[inline] + #[must_use] + pub fn translate(&self, by: Vector2D) -> Self { + Self::new(self.origin + by, self.size) + } + + /// Returns the rectangle as a box. + #[inline] + pub fn to_box2d(&self) -> Box2D { + Box2D { + min: self.min(), + max: self.max(), + } + } +} + +impl Rect +where + T: Copy + PartialOrd + Add, +{ + /// 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) -> bool { + self.to_box2d().contains(p) + } + + /// Do the boxes intersect? + #[inline] + pub fn intersects(&self, other: &Self) -> bool { + self.to_box2d().intersects(&other.to_box2d()) + } +} + +impl Rect +where + T: Copy + PartialOrd + Add + Sub, +{ + /// Returns the intersection of the two. + #[inline] + pub fn intersection(&self, other: &Self) -> Option { + let box2d = self.to_box2d().intersection_unchecked(&other.to_box2d()); + + if box2d.is_empty() { + return None; + } + + Some(box2d.to_rect()) + } +} + +impl Rect +where + T: Copy + Add + Sub, +{ + /// Returns inflated Rect + #[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 Rect +where + T: Copy + Zero + PartialOrd + Add, +{ + /// 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 Rect +where + T: Copy + Zero + PartialOrd + Add + Sub, +{ + /// 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) -> 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 Rect +where + T: Copy + Add + Sub, +{ + /// 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) -> 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 Rect +where + T: Copy + Zero + PartialOrd + Sub, +{ + /// 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(points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow>, + { + Box2D::from_points(points).to_rect() + } +} + +impl Rect +where + T: Copy + One + Add + Sub + Mul, +{ + /// 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 Rect +where + T: Copy + One + Add + Div, +{ + /// Returns the center of the Rect. + pub fn center(&self) -> Point2D { + let two = T::one() + T::one(); + self.origin + self.size.to_vector() / two + } +} + +impl Rect +where + T: Copy + PartialOrd + Add + Sub + Zero, +{ + /// Returns the union of the two Rects. + #[inline] + pub fn union(&self, other: &Self) -> Self { + if self.size == Zero::zero() { + return *other; + } + if other.size == Zero::zero() { + return *self; + } + + self.to_box2d().union(&other.to_box2d()).to_rect() + } +} + +impl Rect { + /// Returns the scaled Rect. + #[inline] + pub fn scale(&self, x: S, y: S) -> Self + where + T: Copy + Mul, + { + Rect::new( + Point2D::new(self.origin.x * x, self.origin.y * y), + Size2D::new(self.size.width * x, self.size.height * y), + ) + } +} + +impl, U> Rect { + /// Returns the area of the Rect. + #[inline] + pub fn area(&self) -> T { + self.size.area() + } +} + +impl Rect { + /// Is the rectangle empty? + #[inline] + pub fn is_empty(&self) -> bool { + self.size.is_empty() + } +} + +impl Rect { + /// Returns an option'd rectangle. + #[inline] + pub fn to_non_empty(&self) -> Option { + if self.is_empty() { + return None; + } + + Some(*self) + } +} + +impl Mul for Rect { + type Output = Rect; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Rect::new(self.origin * scale, self.size * scale) + } +} + +impl MulAssign for Rect { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self *= Scale::new(scale); + } +} + +impl Div for Rect { + type Output = Rect; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Rect::new(self.origin / scale.clone(), self.size / scale) + } +} + +impl DivAssign for Rect { + #[inline] + fn div_assign(&mut self, scale: T) { + *self /= Scale::new(scale); + } +} + +impl Mul> for Rect { + type Output = Rect; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + Rect::new(self.origin * scale.clone(), self.size * scale) + } +} + +impl MulAssign> for Rect { + #[inline] + fn mul_assign(&mut self, scale: Scale) { + self.origin *= scale.clone(); + self.size *= scale; + } +} + +impl Div> for Rect { + type Output = Rect; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + Rect::new(self.origin / scale.clone(), self.size / scale) + } +} + +impl DivAssign> for Rect { + #[inline] + fn div_assign(&mut self, scale: Scale) { + self.origin /= scale.clone(); + self.size /= scale; + } +} + +impl Rect { + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Rect { + Rect::new(self.origin.to_untyped(), self.size.to_untyped()) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(r: &Rect) -> Rect { + Rect::new( + Point2D::from_untyped(r.origin), + Size2D::from_untyped(r.size), + ) + } + + /// Cast the unit + #[inline] + pub fn cast_unit(&self) -> Rect { + Rect::new(self.origin.cast_unit(), self.size.cast_unit()) + } +} + +impl Rect { + /// 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(&self) -> Rect { + 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(&self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` rectangle. + #[inline] + pub fn to_f64(&self) -> Rect { + 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 { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl + Sub, U> Rect { + /// 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 From> for Rect +where + T: Zero, +{ + fn from(size: Size2D) -> Self { + Self::from_size(size) + } +} + +/// Shorthand for `Rect::new(Point2D::new(x, y), Size2D::new(w, h))`. +pub const fn rect(x: T, y: T, w: T, h: T) -> Rect { + Rect::new(Point2D::new(x, y), Size2D::new(w, h)) +} + +#[cfg(test)] +mod tests { + use crate::geom::default::{Point2D, Rect, Size2D}; + use crate::geom::side_offsets::SideOffsets2D; + use crate::geom::{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 = rect(-2, 5, 4, 10); + assert_eq!(r.center(), point2(0, 10)); + + let r: Rect = 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 = rect(-2.0, 5.0, 4.0, std::f32::NAN); + let r2: Rect = rect(std::f32::NAN, -1.0, 3.0, 10.0); + + assert_eq!(r1.intersection(&r2), None); + } +} diff --git a/surfman/src/geom/rigid.rs b/surfman/src/geom/rigid.rs new file mode 100644 index 00000000..0148841e --- /dev/null +++ b/surfman/src/geom/rigid.rs @@ -0,0 +1,274 @@ +//! 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::geom::approxeq::ApproxEq; +use crate::geom::trig::Trig; +use crate::geom::{Rotation3D, Transform3D, UnknownUnit, Vector3D}; +use num_traits::Float; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// 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(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(C)] +pub struct RigidTransform3D { + /// rotation + pub rotation: Rotation3D, + /// translation + pub translation: Vector3D, +} + +impl RigidTransform3D { + /// Construct a new rigid transformation, where the `rotation` applies first + #[inline] + pub const fn new(rotation: Rotation3D, translation: Vector3D) -> Self { + Self { + rotation, + translation, + } + } +} + +impl RigidTransform3D { + /// casts the unit + pub fn cast_unit(&self) -> RigidTransform3D { + RigidTransform3D { + rotation: self.rotation.cast_unit(), + translation: self.translation.cast_unit(), + } + } +} + +impl, Src, Dst> RigidTransform3D { + /// 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, + rotation: Rotation3D, + ) -> 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, + } + } + + /// returns the result from rotation + #[inline] + pub fn from_rotation(rotation: Rotation3D) -> Self { + Self { + rotation, + translation: Vector3D::zero(), + } + } + /// returns the result from translation + #[inline] + pub fn from_translation(translation: Vector3D) -> 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, Rotation3D) { + // 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( + &self, + other: &RigidTransform3D, + ) -> RigidTransform3D { + // 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 { + // 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()) + } + + /// returns the object to transform + pub fn to_transform(&self) -> Transform3D + 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 { + RigidTransform3D { + rotation: self.rotation.to_untyped(), + translation: self.translation.to_untyped(), + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(transform: &RigidTransform3D) -> Self { + RigidTransform3D { + rotation: Rotation3D::from_untyped(&transform.rotation), + translation: Vector3D::from_untyped(transform.translation), + } + } +} + +impl, Src, Dst> From> + for RigidTransform3D +{ + fn from(rot: Rotation3D) -> Self { + Self::from_rotation(rot) + } +} + +impl, Src, Dst> From> for RigidTransform3D { + fn from(t: Vector3D) -> Self { + Self::from_translation(t) + } +} + +#[cfg(test)] +mod test { + use super::RigidTransform3D; + use crate::geom::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/surfman/src/geom/rotation.rs b/surfman/src/geom/rotation.rs new file mode 100644 index 00000000..2c3fdfa0 --- /dev/null +++ b/surfman/src/geom/rotation.rs @@ -0,0 +1,984 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::geom::approxeq::ApproxEq; +use crate::geom::trig::Trig; +use crate::geom::{point2, point3, vec3, Angle, Point2D, Point3D, Vector2D, Vector3D}; +use crate::geom::{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::{Float, NumCast, One, Zero}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// 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 { + /// Angle in radians + pub angle: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +impl Copy for Rotation2D {} + +impl Clone for Rotation2D { + fn clone(&self) -> Self { + Rotation2D { + angle: self.angle.clone(), + _unit: PhantomData, + } + } +} + +impl Eq for Rotation2D where T: Eq {} + +impl PartialEq for Rotation2D +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.angle == other.angle + } +} + +impl Hash for Rotation2D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.angle.hash(h); + } +} + +impl Rotation2D { + /// Creates a rotation from an angle in radians. + #[inline] + pub fn new(angle: Angle) -> 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 Rotation2D { + /// Cast the unit, preserving the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::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::().angle); + /// ``` + #[inline] + pub fn cast_unit(&self) -> Rotation2D { + Rotation2D { + angle: self.angle, + _unit: PhantomData, + } + } + + /// Drop the units, preserving only the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::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 { + self.cast_unit() + } + + /// Tag a unitless value with units. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::Rotation2D; + /// use surfman::geom::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) -> Self { + r.cast_unit() + } +} + +impl Rotation2D +where + T: Copy, +{ + /// Returns self.angle as a strongly typed `Angle`. + pub fn get_angle(&self) -> Angle { + Angle::radians(self.angle) + } +} + +impl Rotation2D { + /// Creates a 3d rotation (around the z axis) from this 2d rotation. + #[inline] + pub fn to_3d(&self) -> Rotation3D { + Rotation3D::around_z(self.get_angle()) + } + + /// Returns the inverse of this rotation. + #[inline] + pub fn inverse(&self) -> Rotation2D { + Rotation2D::radians(-self.angle) + } + + /// Returns a rotation representing the other rotation followed by this rotation. + #[inline] + pub fn then(&self, other: &Rotation2D) -> Rotation2D { + 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) -> Point2D { + let (sin, cos) = Float::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) -> Vector2D { + self.transform_point(vector.to_point()).to_vector() + } +} + +impl Rotation2D +where + T: Copy + Add + Sub + Mul + Zero + Trig, +{ + /// Returns the matrix representation of this rotation. + #[inline] + pub fn to_transform(&self) -> Transform2D { + 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 { + /// 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 Copy for Rotation3D {} + +impl Clone for Rotation3D { + fn clone(&self) -> Self { + Rotation3D { + i: self.i.clone(), + j: self.j.clone(), + k: self.k.clone(), + r: self.r.clone(), + _unit: PhantomData, + } + } +} + +impl Eq for Rotation3D where T: Eq {} + +impl PartialEq for Rotation3D +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 Hash for Rotation3D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.i.hash(h); + self.j.hash(h); + self.k.hash(h); + self.r.hash(h); + } +} + +impl Rotation3D { + /// 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 Rotation3D +where + T: Copy, +{ + /// Returns the vector part (i, j, k) of this quaternion. + #[inline] + pub fn vector_part(&self) -> Vector3D { + vec3(self.i, self.j, self.k) + } + + /// Cast the unit, preserving the numeric value. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::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::().i); + /// assert_eq!(to_world.j, to_world.cast_unit::().j); + /// assert_eq!(to_world.k, to_world.cast_unit::().k); + /// assert_eq!(to_world.r, to_world.cast_unit::().r); + /// ``` + #[inline] + pub fn cast_unit(&self) -> Rotation3D { + 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 surfman::geom::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 { + self.cast_unit() + } + + /// Tag a unitless value with units. + /// + /// # Example + /// + /// ```rust + /// # use surfman::geom::Rotation3D; + /// use surfman::geom::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) -> Self { + r.cast_unit() + } +} + +impl Rotation3D +where + T: Float, +{ + /// 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, angle: Angle) -> 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) -> 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) -> 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) -> 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, pitch: Angle, yaw: Angle) -> Self { + let half = T::one() / (T::one() + T::one()); + + let (sy, cy) = Float::sin_cos(half * yaw.get()); + let (sp, cp) = Float::sin_cos(half * pitch.get()); + let (sr, cr) = Float::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 { + 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, + { + 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, + { + 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 = Float::min(dot, one); + + // Angle between r1 and the result. + let theta = Float::acos(dot) * t; + + // r1 and r3 form an orthonormal basis. + let r3 = r2.sub(r1.mul(dot)).normalize(); + let (sin, cos) = Float::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) -> Point3D + where + T: ApproxEq, + { + 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) -> Point2D + where + T: ApproxEq, + { + 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) -> Vector3D + where + T: ApproxEq, + { + 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) -> Vector2D + where + T: ApproxEq, + { + self.transform_vector3d(vector.to_3d()).xy() + } + + /// Returns the matrix representation of this rotation. + #[inline] + pub fn to_transform(&self) -> Transform3D + where + T: ApproxEq, + { + 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(&self, other: &Rotation3D) -> Rotation3D + where + T: ApproxEq, + { + 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 fmt::Debug for Rotation3D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Quat({:?}*i + {:?}*j + {:?}*k + {:?})", + self.i, self.j, self.k, self.r + ) + } +} + +impl ApproxEq for Rotation3D +where + T: Copy + Neg + ApproxEq, +{ + 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::geom::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::geom::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::geom::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::geom::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::geom::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::geom::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::geom::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/surfman/src/geom/scale.rs b/surfman/src/geom/scale.rs new file mode 100644 index 00000000..3b242ebd --- /dev/null +++ b/surfman/src/geom/scale.rs @@ -0,0 +1,420 @@ +// 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 or the MIT license +// , 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::geom::num::One; + +use crate::geom::{Box2D, Box3D, Point2D, Point3D, Rect, Size2D, Vector2D}; +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}; + +/// 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 surfman::geom::{Scale, Length}; +/// enum Mm {}; +/// enum Inch {}; +/// +/// let mm_per_inch: Scale = Scale::new(25.4); +/// +/// let one_foot: Length = Length::new(12.0); +/// let one_foot_in_mm: Length = 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(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>); + +impl Scale { + /// Creates a new scale. + #[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 surfman::geom::{Scale, point2}; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale = Scale::new(10); + /// + /// assert_eq!(to_mm.transform_point(point2(42, -42)), point2(420, -420)); + /// ``` + #[inline] + pub fn transform_point(self, point: Point2D) -> Point2D + 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) -> Point3D + 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 surfman::geom::{Scale, vec2}; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale = Scale::new(10); + /// + /// assert_eq!(to_mm.transform_vector(vec2(42, -42)), vec2(420, -420)); + /// ``` + #[inline] + pub fn transform_vector(self, vec: Vector2D) -> Vector2D + 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 surfman::geom::{Scale, size2}; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale = Scale::new(10); + /// + /// assert_eq!(to_mm.transform_size(size2(42, -42)), size2(420, -420)); + /// ``` + #[inline] + pub fn transform_size(self, size: Size2D) -> Size2D + 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 surfman::geom::{Scale, rect}; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale = 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) -> Rect + 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) -> Box2D + 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) -> Box3D + 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 surfman::geom::Scale; + /// use surfman::geom::num::One; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let cm_per_mm: Scale = Scale::new(0.1); + /// let mm_per_mm: Scale = 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 surfman::geom::Scale; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let cm_per_mm: Scale = Scale::new(0.1); + /// + /// assert_eq!(cm_per_mm.inverse(), Scale::new(10.0)); + /// ``` + pub fn inverse(self) -> Scale + where + T: One + Div, + { + let one: T = One::one(); + Scale::new(one / self.0) + } +} + +impl Scale { + /// 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 surfman::geom::Scale; + /// enum Mm {}; + /// enum Cm {}; + /// + /// let to_mm: Scale = Scale::new(10); + /// + /// assert_eq!(to_mm.cast::(), Scale::new(10.0)); + /// ``` + /// That conversion will panic, because `i32` not enough to store such big numbers: + /// ```rust,should_panic + /// use surfman::geom::Scale; + /// enum Mm {};// millimeter = 10^-2 meters + /// enum Em {};// exameter = 10^18 meters + /// + /// // Panics + /// let to_em: Scale = Scale::new(10e20).cast(); + /// ``` + #[inline] + pub fn cast(self) -> Scale { + 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 surfman::geom::Scale; + /// enum Mm {}; + /// enum Cm {}; + /// enum Em {};// Exameter = 10^18 meters + /// + /// let to_mm: Scale = Scale::new(10); + /// let to_em: Scale = Scale::new(10e20); + /// + /// assert_eq!(to_mm.try_cast::(), Some(Scale::new(10.0))); + /// // Integer to small to store that number + /// assert_eq!(to_em.try_cast::(), None); + /// ``` + pub fn try_cast(self) -> Option> { + NumCast::from(self.0).map(Scale::new) + } +} + +// scale0 * scale1 +// (A,B) * (B,C) = (A,C) +impl Mul> for Scale { + type Output = Scale; + + #[inline] + fn mul(self, other: Scale) -> Self::Output { + Scale::new(self.0 * other.0) + } +} + +// scale0 + scale1 +impl Add for Scale { + type Output = Scale; + + #[inline] + fn add(self, other: Scale) -> Self::Output { + Scale::new(self.0 + other.0) + } +} + +// scale0 - scale1 +impl Sub for Scale { + type Output = Scale; + + #[inline] + fn sub(self, other: Scale) -> 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 PartialEq for Scale { + fn eq(&self, other: &Scale) -> bool { + self.0 == other.0 + } +} + +impl Eq for Scale {} + +impl PartialOrd for Scale { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for Scale { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl Clone for Scale { + fn clone(&self) -> Scale { + Scale::new(self.0.clone()) + } +} + +impl Copy for Scale {} + +impl fmt::Debug for Scale { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Default for Scale { + fn default() -> Self { + Self::new(T::default()) + } +} + +impl Hash for Scale { + fn hash(&self, state: &mut H) { + self.0.hash(state) + } +} + +impl One for Scale { + #[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 = Scale::new(25.4); + let cm_per_mm: Scale = Scale::new(0.1); + + let mm_per_cm: Scale = cm_per_mm.inverse(); + assert_eq!(mm_per_cm.get(), 10.0); + + let one: Scale = cm_per_mm * mm_per_cm; + assert_eq!(one.get(), 1.0); + + let one: Scale = mm_per_cm * cm_per_mm; + assert_eq!(one.get(), 1.0); + + let cm_per_inch: Scale = 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 = Scale::new(2); + let b: Scale = 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/surfman/src/geom/side_offsets.rs b/surfman/src/geom/side_offsets.rs new file mode 100644 index 00000000..dbdf92e8 --- /dev/null +++ b/surfman/src/geom/side_offsets.rs @@ -0,0 +1,450 @@ +// 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 or the MIT license +// , 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::geom::length::Length; +use crate::geom::num::Zero; +use crate::geom::scale::Scale; +use crate::geom::vector::Vector2D; +use core::cmp::{Eq, PartialEq}; +use core::fmt; +use core::hash::Hash; +use core::marker::PhantomData; +use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Neg}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// 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 { + /// top + pub top: T, + /// right + pub right: T, + /// bottom + pub bottom: T, + /// left + pub left: T, + #[doc(hidden)] + pub _unit: PhantomData, +} + +impl Copy for SideOffsets2D {} + +impl Clone for SideOffsets2D { + fn clone(&self) -> Self { + SideOffsets2D { + top: self.top.clone(), + right: self.right.clone(), + bottom: self.bottom.clone(), + left: self.left.clone(), + _unit: PhantomData, + } + } +} + +impl Eq for SideOffsets2D where T: Eq {} + +impl PartialEq for SideOffsets2D +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 Hash for SideOffsets2D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.top.hash(h); + self.right.hash(h); + self.bottom.hash(h); + self.left.hash(h); + } +} + +impl fmt::Debug for SideOffsets2D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "({:?},{:?},{:?},{:?})", + self.top, self.right, self.bottom, self.left + ) + } +} + +impl Default for SideOffsets2D { + fn default() -> Self { + SideOffsets2D { + top: Default::default(), + right: Default::default(), + bottom: Default::default(), + left: Default::default(), + _unit: PhantomData, + } + } +} + +impl SideOffsets2D { + /// 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, + right: Length, + bottom: Length, + left: Length, + ) -> 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, max: Vector2D) -> Self + where + T: Neg, + { + 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, max: Vector2D) -> Self + where + T: Neg, + { + 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) -> Self + where + T: Copy, + { + SideOffsets2D::new_all_same(all.0) + } + + /// horizontal function + pub fn horizontal(&self) -> T + where + T: Copy + Add, + { + self.left + self.right + } + + /// vertical function + pub fn vertical(&self) -> T + where + T: Copy + Add, + { + self.top + self.bottom + } +} + +impl Add for SideOffsets2D +where + T: Add, +{ + 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 Mul for SideOffsets2D { + type Output = SideOffsets2D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + SideOffsets2D::new( + self.top * scale, + self.right * scale, + self.bottom * scale, + self.left * scale, + ) + } +} + +impl MulAssign for SideOffsets2D { + #[inline] + fn mul_assign(&mut self, other: T) { + self.top *= other; + self.right *= other; + self.bottom *= other; + self.left *= other; + } +} + +impl Mul> for SideOffsets2D { + type Output = SideOffsets2D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + SideOffsets2D::new( + self.top * scale.0, + self.right * scale.0, + self.bottom * scale.0, + self.left * scale.0, + ) + } +} + +impl MulAssign> for SideOffsets2D { + #[inline] + fn mul_assign(&mut self, other: Scale) { + *self *= other.0; + } +} + +impl Div for SideOffsets2D { + type Output = SideOffsets2D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + SideOffsets2D::new( + self.top / scale, + self.right / scale, + self.bottom / scale, + self.left / scale, + ) + } +} + +impl DivAssign for SideOffsets2D { + #[inline] + fn div_assign(&mut self, other: T) { + self.top /= other; + self.right /= other; + self.bottom /= other; + self.left /= other; + } +} + +impl Div> for SideOffsets2D { + type Output = SideOffsets2D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + SideOffsets2D::new( + self.top / scale.0, + self.right / scale.0, + self.bottom / scale.0, + self.left / scale.0, + ) + } +} + +impl DivAssign> for SideOffsets2D { + fn div_assign(&mut self, other: Scale) { + *self /= other.0; + } +} + +#[test] +fn from_vectors() { + use crate::geom::{point2, vec2}; + type Box2D = crate::geom::default::Box2D; + + 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 = SideOffsets2D::new_all_same(0.0); + assert!(s1.is_zero()); + + let s2: SideOffsets2D = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0); + assert!(!s2.is_zero()); +} + +#[cfg(test)] +mod ops { + use crate::geom::scale::Scale; + + pub enum Mm {} + pub enum Cm {} + + type SideOffsets2D = crate::geom::default::SideOffsets2D; + type SideOffsets2DMm = crate::geom::side_offsets::SideOffsets2D; + type SideOffsets2DCm = crate::geom::side_offsets::SideOffsets2D; + + #[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 = 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 = 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 = 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 = Scale::new(0.1); + + s /= scale; + + assert_eq!(s, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0)); + } +} diff --git a/surfman/src/geom/size.rs b/surfman/src/geom/size.rs new file mode 100644 index 00000000..01195d22 --- /dev/null +++ b/surfman/src/geom/size.rs @@ -0,0 +1,1687 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::geom::approxord::{max, min}; +use crate::geom::length::Length; +use crate::geom::num::*; +use crate::geom::scale::Scale; +use crate::geom::vector::{vec2, vec3, BoolVector2D, BoolVector3D, Vector2D, Vector3D}; +#[cfg(feature = "mint")] +use mint; + +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}; +use num_traits::{NumCast, Signed}; +#[cfg(feature = "serde")] +use serde; + +/// A 2d size tagged with a unit. +#[repr(C)] +pub struct Size2D { + /// 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, +} + +impl Copy for Size2D {} + +impl Clone for Size2D { + 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 +where + T: serde::Deserialize<'de>, +{ + /// Deserializes 2d size from tuple of width and height. + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let (width, height) = serde::Deserialize::deserialize(deserializer)?; + Ok(Size2D { + width, + height, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Size2D +where + T: serde::Serialize, +{ + /// Serializes 2d size to tuple of width and height. + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.width, &self.height).serialize(serializer) + } +} + +impl Eq for Size2D where T: Eq {} + +impl PartialEq for Size2D +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.width == other.width && self.height == other.height + } +} + +impl Hash for Size2D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.width.hash(h); + self.height.hash(h); + } +} + +impl fmt::Debug for Size2D { + 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 Default for Size2D { + fn default() -> Self { + Size2D::new(Default::default(), Default::default()) + } +} + +impl Size2D { + /// 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, height: Length) -> Self { + Size2D::new(width.0, height.0) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Size2D) -> Self { + Size2D::new(p.width, p.height) + } +} + +impl Size2D { + /// 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 { + vec2(self.width, self.height) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(self) -> Size2D { + self.cast_unit() + } + + /// Cast the unit + #[inline] + pub fn cast_unit(self) -> Size2D { + 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 surfman::geom::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 surfman::geom::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 surfman::geom::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 surfman::geom::size2; + /// use surfman::geom::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 + Mul + Add, + { + let one_t = T::one() - t; + self * one_t + other * t + } +} + +impl Size2D { + /// 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(self) -> Size2D { + 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(self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` size. + #[inline] + pub fn to_f64(self) -> Size2D { + 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 { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl Size2D { + /// 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 Size2D { + /// 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 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 Size2D { + /// 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 Round for Size2D { + /// See [`Size2D::round()`](#method.round). + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl Ceil for Size2D { + /// See [`Size2D::ceil()`](#method.ceil). + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl Floor for Size2D { + /// See [`Size2D::floor()`](#method.floor). + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl Zero for Size2D { + #[inline] + fn zero() -> Self { + Size2D::new(Zero::zero(), Zero::zero()) + } +} + +impl Neg for Size2D { + type Output = Size2D; + + #[inline] + fn neg(self) -> Self::Output { + Size2D::new(-self.width, -self.height) + } +} + +impl Add for Size2D { + type Output = Size2D; + + #[inline] + fn add(self, other: Self) -> Self::Output { + Size2D::new(self.width + other.width, self.height + other.height) + } +} + +impl AddAssign for Size2D { + #[inline] + fn add_assign(&mut self, other: Self) { + self.width += other.width; + self.height += other.height; + } +} + +impl Sub for Size2D { + type Output = Size2D; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + Size2D::new(self.width - other.width, self.height - other.height) + } +} + +impl SubAssign for Size2D { + #[inline] + fn sub_assign(&mut self, other: Self) { + self.width -= other.width; + self.height -= other.height; + } +} + +impl Mul for Size2D { + type Output = Size2D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Size2D::new(self.width * scale, self.height * scale) + } +} + +impl MulAssign for Size2D { + #[inline] + fn mul_assign(&mut self, other: T) { + self.width *= other; + self.height *= other; + } +} + +impl Mul> for Size2D { + type Output = Size2D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + Size2D::new(self.width * scale.0, self.height * scale.0) + } +} + +impl MulAssign> for Size2D { + #[inline] + fn mul_assign(&mut self, other: Scale) { + *self *= other.0; + } +} + +impl Div for Size2D { + type Output = Size2D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Size2D::new(self.width / scale, self.height / scale) + } +} + +impl DivAssign for Size2D { + #[inline] + fn div_assign(&mut self, other: T) { + self.width /= other; + self.height /= other; + } +} + +impl Div> for Size2D { + type Output = Size2D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + Size2D::new(self.width / scale.0, self.height / scale.0) + } +} + +impl DivAssign> for Size2D { + #[inline] + fn div_assign(&mut self, other: Scale) { + *self /= other.0; + } +} + +/// Shorthand for `Size2D::new(w, h)`. +#[inline] +pub const fn size2(w: T, h: T) -> Size2D { + Size2D::new(w, h) +} + +#[cfg(feature = "mint")] +impl From> for Size2D { + #[inline] + fn from(v: mint::Vector2) -> Self { + Size2D { + width: v.x, + height: v.y, + _unit: PhantomData, + } + } +} +#[cfg(feature = "mint")] +impl Into> for Size2D { + #[inline] + fn into(self) -> mint::Vector2 { + mint::Vector2 { + x: self.width, + y: self.height, + } + } +} + +impl From> for Size2D { + #[inline] + fn from(v: Vector2D) -> Self { + size2(v.x, v.y) + } +} + +impl Into<[T; 2]> for Size2D { + #[inline] + fn into(self) -> [T; 2] { + [self.width, self.height] + } +} + +impl From<[T; 2]> for Size2D { + #[inline] + fn from([w, h]: [T; 2]) -> Self { + size2(w, h) + } +} + +impl Into<(T, T)> for Size2D { + #[inline] + fn into(self) -> (T, T) { + (self.width, self.height) + } +} + +impl From<(T, T)> for Size2D { + #[inline] + fn from(tuple: (T, T)) -> Self { + size2(tuple.0, tuple.1) + } +} + +#[cfg(test)] +mod size2d { + use crate::geom::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::geom::default::Size2D; + use crate::geom::scale::Scale; + + pub enum Mm {} + pub enum Cm {} + + pub type Size2DMm = crate::Size2D; + pub type Size2DCm = crate::Size2D; + + #[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)); + + 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(-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)); + } + + #[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_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 = 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 = 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 = Scale::new(0.1); + + s1 *= scale; + + assert_eq!(s1, Size2DMm::new(0.1, 0.2)); + } + + #[test] + pub fn test_div_scalar() { + let s1: Size2D = 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 = 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 = 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 = 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 { + /// 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, +} + +impl Copy for Size3D {} + +impl Clone for Size3D { + 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 +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let (width, height, depth) = serde::Deserialize::deserialize(deserializer)?; + Ok(Size3D { + width, + height, + depth, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Size3D +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.width, &self.height, &self.depth).serialize(serializer) + } +} + +impl Eq for Size3D where T: Eq {} + +impl PartialEq for Size3D +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.width == other.width && self.height == other.height && self.depth == other.depth + } +} + +impl Hash for Size3D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.width.hash(h); + self.height.hash(h); + self.depth.hash(h); + } +} + +impl fmt::Debug for Size3D { + 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 Default for Size3D { + fn default() -> Self { + Size3D::new(Default::default(), Default::default(), Default::default()) + } +} + +impl Size3D { + /// 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, height: Length, depth: Length) -> Self { + Size3D::new(width.0, height.0, depth.0) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Size3D) -> Self { + Size3D::new(p.width, p.height, p.depth) + } +} + +impl Size3D { + /// 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 { + vec3(self.width, self.height, self.depth) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(self) -> Size3D { + self.cast_unit() + } + + /// Cast the unit + #[inline] + pub fn cast_unit(self) -> Size3D { + 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 surfman::geom::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 surfman::geom::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 surfman::geom::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, + { + self.width * self.height * self.depth + } + + /// Linearly interpolate between this size and another size. + /// + /// # Example + /// + /// ```rust + /// use surfman::geom::size3; + /// use surfman::geom::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 + Mul + Add, + { + let one_t = T::one() - t; + self * one_t + other * t + } +} + +impl Size3D { + /// 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(self) -> Size3D { + 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(self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` size. + #[inline] + pub fn to_f64(self) -> Size3D { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl Size3D { + /// 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 Size3D { + /// 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 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 Size3D { + /// 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 Round for Size3D { + /// See [`Size3D::round()`](#method.round). + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl Ceil for Size3D { + /// See [`Size3D::ceil()`](#method.ceil). + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl Floor for Size3D { + /// See [`Size3D::floor()`](#method.floor). + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl Zero for Size3D { + #[inline] + fn zero() -> Self { + Size3D::new(Zero::zero(), Zero::zero(), Zero::zero()) + } +} + +impl Neg for Size3D { + type Output = Size3D; + + #[inline] + fn neg(self) -> Self::Output { + Size3D::new(-self.width, -self.height, -self.depth) + } +} + +impl Add for Size3D { + type Output = Size3D; + + #[inline] + fn add(self, other: Self) -> Self::Output { + Size3D::new( + self.width + other.width, + self.height + other.height, + self.depth + other.depth, + ) + } +} + +impl AddAssign for Size3D { + #[inline] + fn add_assign(&mut self, other: Self) { + self.width += other.width; + self.height += other.height; + self.depth += other.depth; + } +} + +impl Sub for Size3D { + type Output = Size3D; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + Size3D::new( + self.width - other.width, + self.height - other.height, + self.depth - other.depth, + ) + } +} + +impl SubAssign for Size3D { + #[inline] + fn sub_assign(&mut self, other: Self) { + self.width -= other.width; + self.height -= other.height; + self.depth -= other.depth; + } +} + +impl Mul for Size3D { + type Output = Size3D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + Size3D::new(self.width * scale, self.height * scale, self.depth * scale) + } +} + +impl MulAssign for Size3D { + #[inline] + fn mul_assign(&mut self, other: T) { + self.width *= other; + self.height *= other; + self.depth *= other; + } +} + +impl Mul> for Size3D { + type Output = Size3D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + Size3D::new( + self.width * scale.0, + self.height * scale.0, + self.depth * scale.0, + ) + } +} + +impl MulAssign> for Size3D { + #[inline] + fn mul_assign(&mut self, other: Scale) { + *self *= other.0; + } +} + +impl Div for Size3D { + type Output = Size3D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + Size3D::new(self.width / scale, self.height / scale, self.depth / scale) + } +} + +impl DivAssign for Size3D { + #[inline] + fn div_assign(&mut self, other: T) { + self.width /= other; + self.height /= other; + self.depth /= other; + } +} + +impl Div> for Size3D { + type Output = Size3D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + Size3D::new( + self.width / scale.0, + self.height / scale.0, + self.depth / scale.0, + ) + } +} + +impl DivAssign> for Size3D { + #[inline] + fn div_assign(&mut self, other: Scale) { + *self /= other.0; + } +} + +#[cfg(feature = "mint")] +impl From> for Size3D { + #[inline] + fn from(v: mint::Vector3) -> Self { + size3(v.x, v.y, v.z) + } +} +#[cfg(feature = "mint")] +impl Into> for Size3D { + #[inline] + fn into(self) -> mint::Vector3 { + mint::Vector3 { + x: self.width, + y: self.height, + z: self.depth, + } + } +} + +impl From> for Size3D { + #[inline] + fn from(v: Vector3D) -> Self { + size3(v.x, v.y, v.z) + } +} + +impl Into<[T; 3]> for Size3D { + #[inline] + fn into(self) -> [T; 3] { + [self.width, self.height, self.depth] + } +} + +impl From<[T; 3]> for Size3D { + #[inline] + fn from([w, h, d]: [T; 3]) -> Self { + size3(w, h, d) + } +} + +impl Into<(T, T, T)> for Size3D { + #[inline] + fn into(self) -> (T, T, T) { + (self.width, self.height, self.depth) + } +} + +impl From<(T, T, T)> for Size3D { + #[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(w: T, h: T, d: T) -> Size3D { + Size3D::new(w, h, d) +} + +#[cfg(test)] +mod size3d { + mod ops { + use crate::geom::default::Size3D; + use crate::geom::scale::Scale; + + pub enum Mm {} + pub enum Cm {} + + pub type Size3DMm = crate::geom::size::Size3D; + pub type Size3DCm = crate::geom::size::Size3D; + + #[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)); + + 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(-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)); + } + + #[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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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/surfman/src/geom/transform2d.rs b/surfman/src/geom/transform2d.rs new file mode 100644 index 00000000..66410069 --- /dev/null +++ b/surfman/src/geom/transform2d.rs @@ -0,0 +1,803 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::{Angle, UnknownUnit}; +use crate::geom::approxeq::ApproxEq; +use crate::geom::box2d::Box2D; +use crate::geom::num::{One, Zero}; +use crate::geom::point::{point2, Point2D}; +use crate::geom::rect::Rect; +use crate::geom::transform3d::Transform3D; +use crate::geom::trig::Trig; +use crate::geom::vector::{vec2, Vector2D}; +use core::cmp::{Eq, PartialEq}; +use core::fmt; +use core::hash::Hash; +use core::marker::PhantomData; +use core::ops::{Add, Div, Mul, Sub}; +#[cfg(feature = "mint")] +use mint; +use num_traits::NumCast; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// 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::transform_point4d` +/// takes a `Point2D` and returns a `Point2D`. +/// +/// 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 { + /// 11 + pub m11: T, + /// 12 + pub m12: T, + /// 21 + pub m21: T, + /// 22 + pub m22: T, + /// 31 + pub m31: T, + /// 32 + pub m32: T, + + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +impl Copy for Transform2D {} + +impl Clone for Transform2D { + 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 Eq for Transform2D where T: Eq {} + +impl PartialEq for Transform2D +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 Hash for Transform2D +where + T: Hash, +{ + fn hash(&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 Transform2D { + /// 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 surfman::geom::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, + { + >::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, + { + >::approx_eq_eps(&self, &other, &eps) + } +} + +impl Transform2D { + /// 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 { + 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) -> 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(&self) -> Transform2D { + 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(&self) -> Transform2D { + 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 + where + T: Zero + One, + { + Transform3D::new_2d(self.m11, self.m12, self.m21, self.m22, self.m31, self.m32) + } +} + +impl Transform2D { + /// Cast from one numeric representation to another, preserving the units. + #[inline] + pub fn cast(&self) -> Transform2D { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + pub fn try_cast(&self) -> Option> { + 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 Transform2D +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 Transform2D +where + T: Copy + Add + Mul, +{ + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies after self's transformation. + #[must_use] + pub fn then(&self, mat: &Transform2D) -> Transform2D { + 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 Transform2D +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) -> Self + where + T: Copy + Add + Mul, + { + 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) -> Self + where + T: Copy + Add + Mul, + { + Transform2D::translation(v.x, v.y).then(self) + } +} + +/// Methods for creating and combining rotation transformations +impl Transform2D +where + T: Copy + Add + Sub + Mul + Zero + Trig, +{ + /// Returns a rotation transform. + #[inline] + pub fn rotation(theta: Angle) -> 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) -> 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) -> Self { + Transform2D::rotation(theta).then(self) + } +} + +/// Methods for creating and combining scale transformations +impl Transform2D { + /// 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 + Mul + 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, + { + 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 Transform2D +where + T: Copy + Add + Mul, +{ + /// Returns the given point transformed by this transform. + #[inline] + #[must_use] + pub fn transform_point(&self, point: Point2D) -> Point2D { + 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) -> Vector2D { + 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) -> Rect + where + T: Sub + 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) -> Box2D + where + T: Sub + 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 Transform2D +where + T: Copy + Sub + Mul + Div + 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> { + 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 Default for Transform2D +where + T: Zero + One, +{ + /// Returns the [identity transform](#method.identity). + fn default() -> Self { + Self::identity() + } +} + +impl, Src, Dst> ApproxEq for Transform2D { + #[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 fmt::Debug for Transform2D +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 From> for Transform2D { + fn from(m: mint::RowMatrix3x2) -> 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 Into> for Transform2D { + fn into(self) -> mint::RowMatrix3x2 { + 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::geom::approxeq::ApproxEq; + use crate::geom::default; + #[cfg(feature = "mint")] + use mint; + + use core::f32::consts::FRAC_PI_2; + + type Mat = default::Transform2D; + + fn rad(v: f32) -> Angle { + 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::>(), 6 * size_of::()); + assert_eq!(size_of::>(), 6 * size_of::()); + } + + #[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/surfman/src/geom/transform3d.rs b/surfman/src/geom/transform3d.rs new file mode 100644 index 00000000..2bcbf539 --- /dev/null +++ b/surfman/src/geom/transform3d.rs @@ -0,0 +1,1721 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::{Angle, UnknownUnit}; +use crate::geom::approxeq::ApproxEq; +use crate::geom::box2d::Box2D; +use crate::geom::box3d::Box3D; +use crate::geom::homogen::HomogeneousVector; +use crate::geom::num::{One, Zero}; +use crate::geom::point::{point2, point3, Point2D, Point3D}; +use crate::geom::rect::Rect; +use crate::geom::scale::Scale; +use crate::geom::transform2d::Transform2D; +use crate::geom::trig::Trig; +use crate::geom::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, Div, Mul, Neg, Sub}; +#[cfg(feature = "mint")] +use mint; +use num_traits::NumCast; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// 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::transform_point3d` +/// takes a `Point3D` and returns a `Point3D`. +/// +/// 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 { + /// 11 + pub m11: T, + /// 12 + pub m12: T, + /// 13 + pub m13: T, + /// 14 + pub m14: T, + /// 21 + pub m21: T, + /// 22 + pub m22: T, + /// 23 + pub m23: T, + /// 24 + pub m24: T, + /// 31 + pub m31: T, + /// 32 + pub m32: T, + /// 33 + pub m33: T, + /// 34 + pub m34: T, + /// 41 + pub m41: T, + /// 42 + pub m42: T, + /// 43 + pub m43: T, + /// 44 + pub m44: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +impl Copy for Transform3D {} + +impl Clone for Transform3D { + 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 Eq for Transform3D where T: Eq {} + +impl PartialEq for Transform3D +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 Hash for Transform3D +where + T: Hash, +{ + fn hash(&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 Transform3D { + /// 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 surfman::geom::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] + 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 + #[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 Transform3D { + /// 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) -> 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 { + 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(&self) -> Transform3D { + 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(&self) -> Transform3D { + 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 { + Transform2D::new(self.m11, self.m12, self.m21, self.m22, self.m41, self.m42) + } +} + +impl Transform3D +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 + pub fn skew(alpha: Angle, beta: Angle) -> 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 . + pub fn perspective(d: T) -> Self + where + T: Neg + Div, + { + 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 Transform3D +where + T: Copy + Add + Mul, +{ + /// 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(&self, other: &Transform3D) -> Transform3D { + 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 Transform3D +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) -> Self + where + T: Copy + Add + Mul, + { + 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) -> Self + where + T: Copy + Add + Mul, + { + self.then(&Transform3D::translation(v.x, v.y, v.z)) + } +} + +/// Methods for creating and combining rotation transformations +impl Transform3D +where + T: Copy + + Add + + Sub + + Mul + + Div + + 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) -> 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) -> 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) -> Self { + Transform3D::rotation(x, y, z, theta).then(self) + } +} + +/// Methods for creating and combining scale transformations +impl Transform3D +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 + Mul, + { + 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 + Mul, + { + self.then(&Transform3D::scale(x, y, z)) + } +} + +/// Methods for apply transformations to objects +impl Transform3D +where + T: Copy + Add + Mul, +{ + /// 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) -> HomogeneousVector { + 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) -> Option> + where + T: Div + 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) -> Vector2D { + 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) -> HomogeneousVector { + 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) -> Option> + where + T: Div + 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) -> Vector3D { + 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) -> Option> + where + T: Sub + Div + 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) -> Option> + where + T: Sub + Div + 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) -> Option> + where + T: Sub + Div + 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 Transform3D +where + T: Copy + + Add + + Sub + + Mul + + Div + + Neg + + 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> { + 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) -> Self { + Transform3D::scale(scale.get(), scale.get(), scale.get()) + } +} + +impl Transform3D +where + T: Copy + Mul + Div + 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 Transform3D { + /// Cast from one numeric representation to another, preserving the units. + #[inline] + pub fn cast(&self) -> Transform3D { + self.try_cast().unwrap() + } + + /// Fallible cast from one numeric representation to another, preserving the units. + pub fn try_cast(&self) -> Option> { + 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, Src, Dst> Transform3D { + /// 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 { + >::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 { + >::approx_eq_eps(&self, &other, &eps) + } +} + +impl, Src, Dst> ApproxEq for Transform3D { + #[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 Default for Transform3D +where + T: Zero + One, +{ + /// Returns the [identity transform](#method.identity). + fn default() -> Self { + Self::identity() + } +} + +impl fmt::Debug for Transform3D +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 From> for Transform3D { + fn from(m: mint::RowMatrix4) -> 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 Into> for Transform3D { + fn into(self) -> mint::RowMatrix4 { + 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 super::*; + use crate::geom::approxeq::ApproxEq; + use crate::geom::default; + use crate::geom::point::{point2, point3}; + + use core::f32::consts::{FRAC_PI_2, PI}; + + type Mf32 = default::Transform3D; + + // For convenience. + fn rad(v: f32) -> Angle { + 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::>(), + 16 * size_of::() + ); + assert_eq!( + size_of::>(), + 16 * size_of::() + ); + } + + #[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/surfman/src/geom/translation.rs b/surfman/src/geom/translation.rs new file mode 100644 index 00000000..9b7a4c48 --- /dev/null +++ b/surfman/src/geom/translation.rs @@ -0,0 +1,823 @@ +// Copyright 2018 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::geom::num::*; +use crate::geom::point::{Point2D, Point3D}; +use crate::geom::size::Size2D; +use crate::geom::transform2d::Transform2D; +use crate::geom::transform3d::Transform3D; +use crate::geom::vector::{Vector2D, Vector3D}; +use crate::geom::UnknownUnit; +use crate::geom::{point2, point3, vec2, vec3, Box2D, Box3D, Rect}; +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}; + +/// 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 surfman::geom::{Translation2D, Point2D, point2}; +/// struct ParentSpace; +/// struct ChildSpace; +/// type ScrollOffset = Translation2D; +/// type ParentPoint = Point2D; +/// type ChildPoint = Point2D; +/// +/// let scrolling = ScrollOffset::new(0, 100); +/// let p1: ParentPoint = point2(0, 0); +/// let p2: ChildPoint = scrolling.transform_point(p1); +/// ``` +/// +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound( + serialize = "T: serde::Serialize", + deserialize = "T: serde::Deserialize<'de>" + )) +)] +pub struct Translation2D { + /// x + pub x: T, + /// y + pub y: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +impl Copy for Translation2D {} + +impl Clone for Translation2D { + fn clone(&self) -> Self { + Translation2D { + x: self.x.clone(), + y: self.y.clone(), + _unit: PhantomData, + } + } +} + +impl Eq for Translation2D where T: Eq {} + +impl PartialEq for Translation2D +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +impl Hash for Translation2D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + } +} + +impl Translation2D { + #[inline] + /// Creates a new Translation2D instance. + pub const fn new(x: T, y: T) -> Self { + Translation2D { + x, + y, + _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 surfman::geom::default::Translation2D; + /// + /// assert_eq!(Translation2D::::identity().is_identity(), true); + /// assert_eq!(Translation2D::new(0, 0).is_identity(), true); + /// assert_eq!(Translation2D::new(1, 0).is_identity(), false); + /// assert_eq!(Translation2D::new(0, 1).is_identity(), false); + /// ``` + #[inline] + pub fn is_identity(&self) -> bool + where + T: Zero + PartialEq, + { + let _0 = T::zero(); + self.x == _0 && self.y == _0 + } + + /// No-op, just cast the unit. + #[inline] + pub fn transform_size(&self, s: Size2D) -> Size2D { + Size2D::new(s.width, s.height) + } +} + +impl Translation2D { + /// Cast into a 2D vector. + #[inline] + pub fn to_vector(&self) -> Vector2D { + vec2(self.x, self.y) + } + + /// Cast into an array with x and y. + #[inline] + pub fn to_array(&self) -> [T; 2] { + [self.x, self.y] + } + + /// Cast into a tuple with x and y. + #[inline] + pub fn to_tuple(&self) -> (T, T) { + (self.x, self.y) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Translation2D { + Translation2D { + x: self.x, + y: self.y, + _unit: PhantomData, + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(t: &Translation2D) -> Self { + Translation2D { + x: t.x, + y: t.y, + _unit: PhantomData, + } + } + + /// Returns the matrix representation of this translation. + #[inline] + pub fn to_transform(&self) -> Transform2D + where + T: Zero + One, + { + (*self).into() + } + + /// Translate a point and cast its unit. + #[inline] + pub fn transform_point(&self, p: Point2D) -> Point2D + where + T: Add, + { + point2(p.x + self.x, p.y + self.y) + } + + /// Translate a rectangle and cast its unit. + #[inline] + pub fn transform_rect(&self, r: &Rect) -> Rect + where + T: Add, + { + Rect { + origin: self.transform_point(r.origin), + size: self.transform_size(r.size), + } + } + + /// Translate a 2D box and cast its unit. + #[inline] + pub fn transform_box(&self, r: &Box2D) -> Box2D + where + T: Add, + { + Box2D { + min: self.transform_point(r.min), + max: self.transform_point(r.max), + } + } + + /// Return the inverse transformation. + #[inline] + pub fn inverse(&self) -> Translation2D + where + T: Neg, + { + Translation2D::new(-self.x, -self.y) + } +} + +impl Add> for Translation2D { + type Output = Translation2D; + + fn add(self, other: Translation2D) -> Self::Output { + Translation2D::new(self.x + other.x, self.y + other.y) + } +} + +impl AddAssign> for Translation2D { + fn add_assign(&mut self, other: Translation2D) { + self.x += other.x; + self.y += other.y; + } +} + +impl Sub> for Translation2D { + type Output = Translation2D; + + fn sub(self, other: Translation2D) -> Self::Output { + Translation2D::new(self.x - other.x, self.y - other.y) + } +} + +impl SubAssign> for Translation2D { + fn sub_assign(&mut self, other: Translation2D) { + self.x -= other.x; + self.y -= other.y; + } +} + +impl From> for Translation2D { + fn from(v: Vector2D) -> Self { + Translation2D::new(v.x, v.y) + } +} + +impl Into> for Translation2D { + fn into(self) -> Vector2D { + vec2(self.x, self.y) + } +} + +impl Into> for Translation2D +where + T: Zero + One, +{ + fn into(self) -> Transform2D { + Transform2D::translation(self.x, self.y) + } +} + +impl Default for Translation2D +where + T: Zero, +{ + fn default() -> Self { + Self::identity() + } +} + +impl fmt::Debug for Translation2D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Translation({:?},{:?})", self.x, self.y) + } +} + +/// A 3d transformation from a space to another that can only express translations. +/// +/// The main benefit of this type over a Vector3D is the ability to cast +/// between a source and a destination spaces. +#[repr(C)] +pub struct Translation3D { + /// x + pub x: T, + /// y + pub y: T, + /// x + pub z: T, + #[doc(hidden)] + pub _unit: PhantomData<(Src, Dst)>, +} + +impl Copy for Translation3D {} + +impl Clone for Translation3D { + fn clone(&self) -> Self { + Translation3D { + x: self.x.clone(), + y: self.y.clone(), + z: self.z.clone(), + _unit: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T, Src, Dst> serde::Deserialize<'de> for Translation3D +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let (x, y, z) = serde::Deserialize::deserialize(deserializer)?; + Ok(Translation3D { + x, + y, + z, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Translation3D +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.x, &self.y, &self.z).serialize(serializer) + } +} + +impl Eq for Translation3D where T: Eq {} + +impl PartialEq for Translation3D +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z + } +} + +impl Hash for Translation3D +where + T: Hash, +{ + fn hash(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + self.z.hash(h); + } +} + +impl Translation3D { + /// Creates a new Translation3D instance. + #[inline] + pub const fn new(x: T, y: T, z: T) -> Self { + Translation3D { + x, + y, + z, + _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 surfman::geom::default::Translation3D; + /// + /// assert_eq!(Translation3D::::identity().is_identity(), true); + /// assert_eq!(Translation3D::new(0, 0, 0).is_identity(), true); + /// assert_eq!(Translation3D::new(1, 0, 0).is_identity(), false); + /// assert_eq!(Translation3D::new(0, 1, 0).is_identity(), false); + /// assert_eq!(Translation3D::new(0, 0, 1).is_identity(), false); + /// ``` + #[inline] + pub fn is_identity(&self) -> bool + where + T: Zero + PartialEq, + { + let _0 = T::zero(); + self.x == _0 && self.y == _0 && self.z == _0 + } + + /// No-op, just cast the unit. + #[inline] + pub fn transform_size(self, s: Size2D) -> Size2D { + Size2D::new(s.width, s.height) + } +} + +impl Translation3D { + /// Cast into a 3D vector. + #[inline] + pub fn to_vector(&self) -> Vector3D { + vec3(self.x, self.y, self.z) + } + + /// Cast into an array with x, y and z. + #[inline] + pub fn to_array(&self) -> [T; 3] { + [self.x, self.y, self.z] + } + + /// Cast into a tuple with x, y and z. + #[inline] + pub fn to_tuple(&self) -> (T, T, T) { + (self.x, self.y, self.z) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Translation3D { + Translation3D { + x: self.x, + y: self.y, + z: self.z, + _unit: PhantomData, + } + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(t: &Translation3D) -> Self { + Translation3D { + x: t.x, + y: t.y, + z: t.z, + _unit: PhantomData, + } + } + + /// Returns the matrix representation of this translation. + #[inline] + pub fn to_transform(&self) -> Transform3D + where + T: Zero + One, + { + (*self).into() + } + + /// Translate a point and cast its unit. + #[inline] + pub fn transform_point3d(&self, p: &Point3D) -> Point3D + where + T: Add, + { + point3(p.x + self.x, p.y + self.y, p.z + self.z) + } + + /// Translate a point and cast its unit. + #[inline] + pub fn transform_point2d(&self, p: &Point2D) -> Point2D + where + T: Add, + { + point2(p.x + self.x, p.y + self.y) + } + + /// Translate a 2D box and cast its unit. + #[inline] + pub fn transform_box2d(&self, b: &Box2D) -> Box2D + where + T: Add, + { + Box2D { + min: self.transform_point2d(&b.min), + max: self.transform_point2d(&b.max), + } + } + + /// Translate a 3D box and cast its unit. + #[inline] + pub fn transform_box3d(&self, b: &Box3D) -> Box3D + where + T: Add, + { + Box3D { + min: self.transform_point3d(&b.min), + max: self.transform_point3d(&b.max), + } + } + + /// Translate a rectangle and cast its unit. + #[inline] + pub fn transform_rect(&self, r: &Rect) -> Rect + where + T: Add, + { + Rect { + origin: self.transform_point2d(&r.origin), + size: self.transform_size(r.size), + } + } + + /// Return the inverse transformation. + #[inline] + pub fn inverse(&self) -> Translation3D + where + T: Neg, + { + Translation3D::new(-self.x, -self.y, -self.z) + } +} + +impl Add> for Translation3D { + type Output = Translation3D; + + fn add(self, other: Translation3D) -> Self::Output { + Translation3D::new(self.x + other.x, self.y + other.y, self.z + other.z) + } +} + +impl AddAssign> for Translation3D { + fn add_assign(&mut self, other: Translation3D) { + self.x += other.x; + self.y += other.y; + self.z += other.z; + } +} + +impl Sub> for Translation3D { + type Output = Translation3D; + + fn sub(self, other: Translation3D) -> Self::Output { + Translation3D::new(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl SubAssign> for Translation3D { + fn sub_assign(&mut self, other: Translation3D) { + self.x -= other.x; + self.y -= other.y; + self.z -= other.z; + } +} + +impl From> for Translation3D { + fn from(v: Vector3D) -> Self { + Translation3D::new(v.x, v.y, v.z) + } +} + +impl Into> for Translation3D { + fn into(self) -> Vector3D { + vec3(self.x, self.y, self.z) + } +} + +impl Into> for Translation3D +where + T: Zero + One, +{ + fn into(self) -> Transform3D { + Transform3D::translation(self.x, self.y, self.z) + } +} + +impl Default for Translation3D +where + T: Zero, +{ + fn default() -> Self { + Self::identity() + } +} + +impl fmt::Debug for Translation3D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Translation({:?},{:?},{:?})", self.x, self.y, self.z) + } +} + +#[cfg(test)] +mod _2d { + #[test] + fn simple() { + use crate::geom::{rect, Rect, Translation2D}; + + struct A; + struct B; + + type Translation = Translation2D; + type SrcRect = Rect; + type DstRect = Rect; + + let tx = Translation::new(10, -10); + let r1: SrcRect = rect(10, 20, 30, 40); + let r2: DstRect = tx.transform_rect(&r1); + assert_eq!(r2, rect(20, 10, 30, 40)); + + let inv_tx = tx.inverse(); + assert_eq!(inv_tx.transform_rect(&r2), r1); + + assert!((tx + inv_tx).is_identity()); + } + + /// Operation tests + mod ops { + use crate::geom::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::geom::{point3, Point3D, Translation3D}; + + struct A; + struct B; + + type Translation = Translation3D; + type SrcPoint = Point3D; + type DstPoint = Point3D; + + let tx = Translation::new(10, -10, 100); + let p1: SrcPoint = point3(10, 20, 30); + let p2: DstPoint = tx.transform_point3d(&p1); + assert_eq!(p2, point3(20, 10, 130)); + + let inv_tx = tx.inverse(); + assert_eq!(inv_tx.transform_point3d(&p2), p1); + + assert!((tx + inv_tx).is_identity()); + } + + /// Operation tests + mod ops { + use crate::geom::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/surfman/src/geom/trig.rs b/surfman/src/geom/trig.rs new file mode 100644 index 00000000..274232ad --- /dev/null +++ b/surfman/src/geom/trig.rs @@ -0,0 +1,82 @@ +// 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 or the MIT license +// , 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 { + /// sine + fn sin(self) -> Self; + /// cosine + fn cos(self) -> Self; + /// tangent + fn tan(self) -> Self; + /// wierd tangent + fn fast_atan2(y: Self, x: Self) -> Self; + /// degrees to radians conversion + fn degrees_to_radians(deg: Self) -> Self; + /// radians to degrees conversion + 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 { + // 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/surfman/src/geom/vector.rs b/surfman/src/geom/vector.rs new file mode 100644 index 00000000..378eb21c --- /dev/null +++ b/surfman/src/geom/vector.rs @@ -0,0 +1,2379 @@ +//! Any kind of thing to do with a vector. +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::UnknownUnit; +use crate::geom::angle::Angle; +use crate::geom::approxeq::ApproxEq; +use crate::geom::approxord::{max, min}; +use crate::geom::length::Length; +use crate::geom::num::*; +use crate::geom::point::{point2, point3, Point2D, Point3D}; +use crate::geom::scale::Scale; +use crate::geom::size::{size2, size3, Size2D, Size3D}; +use crate::geom::transform2d::Transform2D; +use crate::geom::transform3d::Transform3D; +use crate::geom::trig::Trig; +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::{Float, NumCast, Signed}; +#[cfg(feature = "serde")] +use serde; + +/// A 2d Vector tagged with a unit. +#[repr(C)] +pub struct Vector2D { + /// The `x` (traditionally, horizontal) coordinate. + pub x: T, + /// The `y` (traditionally, vertical) coordinate. + pub y: T, + #[doc(hidden)] + pub _unit: PhantomData, +} + +mint_vec!(Vector2D[x, y] = Vector2); + +impl Copy for Vector2D {} + +impl Clone for Vector2D { + 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 +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let (x, y) = serde::Deserialize::deserialize(deserializer)?; + Ok(Vector2D { + x, + y, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Vector2D +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.x, &self.y).serialize(serializer) + } +} + +impl Eq for Vector2D {} + +impl PartialEq for Vector2D { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +impl Hash for Vector2D { + fn hash(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + } +} + +impl Zero for Vector2D { + /// Constructor, setting all components to zero. + #[inline] + fn zero() -> Self { + Vector2D::new(Zero::zero(), Zero::zero()) + } +} + +impl fmt::Debug for Vector2D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("").field(&self.x).field(&self.y).finish() + } +} + +impl Default for Vector2D { + fn default() -> Self { + Vector2D::new(Default::default(), Default::default()) + } +} + +impl Vector2D { + /// Constructor, setting all components to zero. + #[inline] + pub fn zero() -> Self + where + T: Zero, + { + Vector2D::new(Zero::zero(), Zero::zero()) + } + + /// Constructor taking scalar values directly. + #[inline] + pub const fn new(x: T, y: T) -> Self { + Vector2D { + x, + y, + _unit: PhantomData, + } + } + + /// Constructor taking angle and length + pub fn from_angle_and_length(angle: Angle, length: T) -> Self + where + T: Trig + Mul + 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, y: Length) -> Self { + vec2(x.0, y.0) + } + + /// Tag a unit-less value with units. + #[inline] + pub fn from_untyped(p: Vector2D) -> Self { + vec2(p.x, p.y) + } + + /// Computes the vector with absolute values of each component. + /// + /// # Example + /// + /// ```rust + /// # use std::{i32, f32}; + /// # use surfman::geom::vector::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 + Mul, + { + 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 + Mul, + { + self.x * other.y - self.y * other.x + } +} + +impl Vector2D { + /// Create a 3d vector from this one, using the specified z value. + #[inline] + pub fn extend(self, z: T) -> Vector3D { + 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 { + 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 { + size2(self.x, self.y) + } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(self) -> Vector2D { + vec2(self.x, self.y) + } + + /// Cast the unit. + #[inline] + pub fn cast_unit(self) -> Vector2D { + vec2(self.x, self.y) + } + + /// Cast into an array with x and y. + #[inline] + pub fn to_array(self) -> [T; 2] { + [self.x, self.y] + } + + /// Cast into a tuple with x and y. + #[inline] + pub fn to_tuple(self) -> (T, T) { + (self.x, self.y) + } + + /// Convert into a 3d vector with `z` coordinate equals to `T::zero()`. + #[inline] + pub fn to_3d(self) -> Vector3D + 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 surfman::geom::vector::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 surfman::geom::vector::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 surfman::geom::vector::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 + 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 + where + T: Zero + One, + { + Transform2D::translation(self.x, self.y) + } +} + +impl Vector2D +where + T: Copy + Mul + Add, +{ + /// 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 + Div, + { + 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 + where + T: Sub + Trig, + { + Angle::radians(Trig::fast_atan2(self.cross(other), self.dot(other))) + } +} + +impl Vector2D { + /// 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 { + let len = self.length(); + if len == T::zero() { + None + } else { + Some(self / len) + } + } + + /// 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 + } + } + + /// 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 Vector2D +where + T: Copy + One + Add + Sub + Mul, +{ + /// Linearly interpolate each component between this vector and another vector. + /// + /// # Example + /// + /// ```rust + /// use surfman::geom::vector::vec2; + /// use surfman::geom::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 Vector2D { + /// 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 Vector2D { + /// 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 Vector2D { + /// 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(self) -> Vector2D { + 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(self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` vector. + #[inline] + pub fn to_f64(self) -> Vector2D { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl Neg for Vector2D { + type Output = Vector2D; + + #[inline] + fn neg(self) -> Self::Output { + vec2(-self.x, -self.y) + } +} + +impl Add for Vector2D { + type Output = Vector2D; + + #[inline] + fn add(self, other: Self) -> Self::Output { + Vector2D::new(self.x + other.x, self.y + other.y) + } +} + +impl, U> AddAssign for Vector2D { + #[inline] + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl Sub for Vector2D { + type Output = Vector2D; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + vec2(self.x - other.x, self.y - other.y) + } +} + +impl, U> SubAssign> for Vector2D { + #[inline] + fn sub_assign(&mut self, other: Self) { + *self = *self - other + } +} + +impl Mul for Vector2D { + type Output = Vector2D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + vec2(self.x * scale, self.y * scale) + } +} + +impl, U> MulAssign for Vector2D { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +impl Mul> for Vector2D { + type Output = Vector2D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + vec2(self.x * scale.0, self.y * scale.0) + } +} + +impl MulAssign> for Vector2D { + #[inline] + fn mul_assign(&mut self, scale: Scale) { + self.x *= scale.0; + self.y *= scale.0; + } +} + +impl Div for Vector2D { + type Output = Vector2D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + vec2(self.x / scale, self.y / scale) + } +} + +impl, U> DivAssign for Vector2D { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +impl Div> for Vector2D { + type Output = Vector2D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + vec2(self.x / scale.0, self.y / scale.0) + } +} + +impl DivAssign> for Vector2D { + #[inline] + fn div_assign(&mut self, scale: Scale) { + self.x /= scale.0; + self.y /= scale.0; + } +} + +impl Round for Vector2D { + /// See [`Vector2D::round()`](#method.round) + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl Ceil for Vector2D { + /// See [`Vector2D::ceil()`](#method.ceil) + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl Floor for Vector2D { + /// See [`Vector2D::floor()`](#method.floor) + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl, U> ApproxEq> for Vector2D { + #[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 Into<[T; 2]> for Vector2D { + fn into(self) -> [T; 2] { + [self.x, self.y] + } +} + +impl From<[T; 2]> for Vector2D { + fn from([x, y]: [T; 2]) -> Self { + vec2(x, y) + } +} + +impl Into<(T, T)> for Vector2D { + fn into(self) -> (T, T) { + (self.x, self.y) + } +} + +impl From<(T, T)> for Vector2D { + fn from(tuple: (T, T)) -> Self { + vec2(tuple.0, tuple.1) + } +} + +impl From> for Vector2D { + fn from(size: Size2D) -> Self { + vec2(size.width, size.height) + } +} + +/// A 3d Vector tagged with a unit. +#[repr(C)] +pub struct Vector3D { + /// 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, +} + +mint_vec!(Vector3D[x, y, z] = Vector3); + +impl Copy for Vector3D {} + +impl Clone for Vector3D { + 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 +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let (x, y, z) = serde::Deserialize::deserialize(deserializer)?; + Ok(Vector3D { + x, + y, + z, + _unit: PhantomData, + }) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Vector3D +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.x, &self.y, &self.z).serialize(serializer) + } +} + +impl Eq for Vector3D {} + +impl PartialEq for Vector3D { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z + } +} + +impl Hash for Vector3D { + fn hash(&self, h: &mut H) { + self.x.hash(h); + self.y.hash(h); + self.z.hash(h); + } +} + +impl Zero for Vector3D { + /// Constructor, setting all components to zero. + #[inline] + fn zero() -> Self { + vec3(Zero::zero(), Zero::zero(), Zero::zero()) + } +} + +impl fmt::Debug for Vector3D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("") + .field(&self.x) + .field(&self.y) + .field(&self.z) + .finish() + } +} + +impl Default for Vector3D { + fn default() -> Self { + Vector3D::new(Default::default(), Default::default(), Default::default()) + } +} + +impl Vector3D { + /// Constructor, setting all components to zero. + #[inline] + pub fn zero() -> Self + where + T: Zero, + { + vec3(Zero::zero(), Zero::zero(), Zero::zero()) + } + + /// Constructor taking scalar values directly. + #[inline] + pub const fn new(x: T, y: T, z: T) -> Self { + Vector3D { + x, + y, + z, + _unit: PhantomData, + } + } + + /// Constructor taking properly Lengths instead of scalar values. + #[inline] + pub fn from_lengths(x: Length, y: Length, z: Length) -> Vector3D { + vec3(x.0, y.0, z.0) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: Vector3D) -> Self { + vec3(p.x, p.y, p.z) + } + + /// Computes the vector with absolute values of each component. + /// + /// # Example + /// + /// ```rust + /// # use std::{i32, f32}; + /// # use surfman::geom::vector::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 + Mul, + { + self.x * other.x + self.y * other.y + self.z * other.z + } +} + +impl Vector3D { + /// Cross product. + #[inline] + pub fn cross(self, other: Self) -> Self + where + T: Sub + Mul, + { + 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, + ) + } + + /// Cast this vector into a point. + /// + /// Equivalent to adding this vector to the origin. + #[inline] + pub fn to_point(self) -> Point3D { + 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 { + vec2(self.x, self.y) + } + + /// Returns a 2d vector using this vector's x and z coordinates + #[inline] + pub fn xz(self) -> Vector2D { + vec2(self.x, self.z) + } + + /// Returns a 2d vector using this vector's x and z coordinates + #[inline] + pub fn yz(self) -> Vector2D { + 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 { + vec3(self.x, self.y, self.z) + } + + /// Cast the unit. + #[inline] + pub fn cast_unit(self) -> Vector3D { + vec3(self.x, self.y, self.z) + } + + /// Convert into a 2d vector. + #[inline] + pub fn to_2d(self) -> Vector2D { + self.xy() + } + + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// + /// ```rust + /// # use surfman::geom::vector::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 surfman::geom::vector::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 surfman::geom::vector::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 + where + T: Zero + One, + { + Transform3D::translation(self.x, self.y, self.z) + } +} + +impl Vector3D +where + T: Copy + Mul + Add, +{ + /// 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 + Div, + { + onto * (self.dot(onto) / onto.square_length()) + } +} + +impl Vector3D { + /// 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 + 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 { + let len = self.length(); + if len == T::zero() { + None + } else { + Some(self / len) + } + } + + /// 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 + } + } + + /// 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 Vector3D +where + T: Copy + One + Add + Sub + Mul, +{ + /// Linearly interpolate each component between this vector and another vector. + /// + /// # Example + /// + /// ```rust + /// use surfman::geom::vector::vec3; + /// use surfman::geom::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 Vector3D { + /// 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 Vector3D { + /// 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 Vector3D { + /// 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(self) -> Vector3D { + 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(self) -> Option> { + 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 { + self.cast() + } + + /// Cast into an `f64` vector. + #[inline] + pub fn to_f64(self) -> Vector3D { + 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 { + 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 { + 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 { + 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 { + self.cast() + } +} + +impl Neg for Vector3D { + type Output = Vector3D; + + #[inline] + fn neg(self) -> Self::Output { + vec3(-self.x, -self.y, -self.z) + } +} + +impl Add for Vector3D { + type Output = Vector3D; + + #[inline] + fn add(self, other: Self) -> Self::Output { + vec3(self.x + other.x, self.y + other.y, self.z + other.z) + } +} + +impl, U> AddAssign for Vector3D { + #[inline] + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl Sub for Vector3D { + type Output = Vector3D; + + #[inline] + fn sub(self, other: Self) -> Self::Output { + vec3(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl, U> SubAssign> for Vector3D { + #[inline] + fn sub_assign(&mut self, other: Self) { + *self = *self - other + } +} + +impl Mul for Vector3D { + type Output = Vector3D; + + #[inline] + fn mul(self, scale: T) -> Self::Output { + vec3(self.x * scale, self.y * scale, self.z * scale) + } +} + +impl, U> MulAssign for Vector3D { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +impl Mul> for Vector3D { + type Output = Vector3D; + + #[inline] + fn mul(self, scale: Scale) -> Self::Output { + vec3(self.x * scale.0, self.y * scale.0, self.z * scale.0) + } +} + +impl MulAssign> for Vector3D { + #[inline] + fn mul_assign(&mut self, scale: Scale) { + self.x *= scale.0; + self.y *= scale.0; + self.z *= scale.0; + } +} + +impl Div for Vector3D { + type Output = Vector3D; + + #[inline] + fn div(self, scale: T) -> Self::Output { + vec3(self.x / scale, self.y / scale, self.z / scale) + } +} + +impl, U> DivAssign for Vector3D { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +impl Div> for Vector3D { + type Output = Vector3D; + + #[inline] + fn div(self, scale: Scale) -> Self::Output { + vec3(self.x / scale.0, self.y / scale.0, self.z / scale.0) + } +} + +impl DivAssign> for Vector3D { + #[inline] + fn div_assign(&mut self, scale: Scale) { + self.x /= scale.0; + self.y /= scale.0; + self.z /= scale.0; + } +} + +impl Round for Vector3D { + /// See [`Vector3D::round()`](#method.round) + #[inline] + fn round(self) -> Self { + self.round() + } +} + +impl Ceil for Vector3D { + /// See [`Vector3D::ceil()`](#method.ceil) + #[inline] + fn ceil(self) -> Self { + self.ceil() + } +} + +impl Floor for Vector3D { + /// See [`Vector3D::floor()`](#method.floor) + #[inline] + fn floor(self) -> Self { + self.floor() + } +} + +impl, U> ApproxEq> for Vector3D { + #[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 Into<[T; 3]> for Vector3D { + fn into(self) -> [T; 3] { + [self.x, self.y, self.z] + } +} + +impl From<[T; 3]> for Vector3D { + fn from([x, y, z]: [T; 3]) -> Self { + vec3(x, y, z) + } +} + +impl Into<(T, T, T)> for Vector3D { + fn into(self) -> (T, T, T) { + (self.x, self.y, self.z) + } +} + +impl From<(T, T, T)> for Vector3D { + 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 { + /// x + pub x: bool, + /// y + pub y: bool, +} + +/// A 3d vector of booleans, useful for component-wise logic operations. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct BoolVector3D { + /// x + pub x: bool, + /// y + pub y: bool, + /// z + 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(self, a: Point2D, b: Point2D) -> Point2D { + 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(self, a: Vector2D, b: Vector2D) -> Vector2D { + 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(self, a: Size2D, b: Size2D) -> Size2D { + 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(self, a: Point3D, b: Point3D) -> Point3D { + 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(self, a: Vector3D, b: Vector3D) -> Vector3D { + 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(self, a: Size3D, b: Size3D) -> Size3D { + 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 fn vec2(x: T, y: T) -> Vector2D { + Vector2D { + x, + y, + _unit: PhantomData, + } +} + +/// Convenience constructor. +#[inline] +pub fn vec3(x: T, y: T, z: T) -> Vector3D { + Vector3D { + x, + y, + z, + _unit: PhantomData, + } +} + +/// Shorthand for `BoolVector2D { x, y }`. +#[inline] +pub fn bvec2(x: bool, y: bool) -> BoolVector2D { + BoolVector2D { x, y } +} + +/// Shorthand for `BoolVector3D { x, y, z }`. +#[inline] +pub fn bvec3(x: bool, y: bool, z: bool) -> BoolVector3D { + BoolVector3D { x, y, z } +} + +#[cfg(test)] +mod vector2d { + use crate::geom::scale::Scale; + use crate::geom::{default, vector::vec2}; + + #[cfg(feature = "mint")] + use mint; + type Vec2 = default::Vector2D; + + #[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::geom::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::geom::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::geom::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::geom::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 = super::Vector2D; + pub type Vector2DCm = super::Vector2D; + + #[test] + pub fn test_add() { + let p1 = Vector2DMm::new(1.0, 2.0); + let p2 = Vector2DMm::new(3.0, 4.0); + + let result = p1 + p2; + + assert_eq!(result, vec2(4.0, 6.0)); + } + + #[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::::new(0.1); + + let result: Vector2DCm = p1 * cm_per_mm; + + assert_eq!(result, vec2(0.1, 0.2)); + } + + #[test] + pub fn test_swizzling() { + let p: default::Vector2D = vec2(1, 2); + assert_eq!(p.yx(), vec2(2, 1)); + } + + #[test] + pub fn test_reflect() { + use crate::geom::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::geom::scale::Scale; + use crate::geom::{default, vector::vec2, vector::vec3}; + #[cfg(feature = "mint")] + use mint; + + type Vec3 = default::Vector3D; + + #[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::::new(1.0, 2.0, 3.0); + let cm_per_mm = Scale::::new(0.1); + + let result: super::Vector3D = 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::geom::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::geom::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::geom::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::geom::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::geom::default; + type Vec2 = default::Vector2D; + type Vec3 = default::Vector3D; + + #[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), + ); + } +} diff --git a/surfman/src/implementation/connection.rs b/surfman/src/implementation/connection.rs index 63de8f37..c295c9c8 100644 --- a/surfman/src/implementation/connection.rs +++ b/surfman/src/implementation/connection.rs @@ -10,7 +10,7 @@ use crate::connection::Connection as ConnectionInterface; use crate::info::GLApi; use crate::Error; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::os::raw::c_void; diff --git a/surfman/src/implementation/device.rs b/surfman/src/implementation/device.rs index 01c2c78b..0e25589d 100644 --- a/surfman/src/implementation/device.rs +++ b/surfman/src/implementation/device.rs @@ -9,9 +9,9 @@ use super::super::device::{Adapter, Device}; use super::super::surface::{NativeWidget, Surface, SurfaceTexture}; use crate::connection::Connection as ConnectionInterface; use crate::device::Device as DeviceInterface; +use crate::geom::default::Size2D; use crate::gl::types::{GLenum, GLuint}; use crate::{ContextAttributes, ContextID, Error, GLApi, SurfaceAccess, SurfaceInfo, SurfaceType}; -use euclid::default::Size2D; use std::os::raw::c_void; diff --git a/surfman/src/lib.rs b/surfman/src/lib.rs index fdbc0e9b..38ea8abf 100644 --- a/surfman/src/lib.rs +++ b/surfman/src/lib.rs @@ -51,6 +51,9 @@ pub use crate::info::{GLApi, GLVersion}; mod surface; pub use crate::surface::{SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType, SystemSurfaceInfo}; +pub mod geom; +pub use crate::geom::Size2D; + pub mod macros; #[cfg(not(target_os = "android"))] diff --git a/surfman/src/platform/android/connection.rs b/surfman/src/platform/android/connection.rs index a39b0fc0..9cbfb3fa 100644 --- a/surfman/src/platform/android/connection.rs +++ b/surfman/src/platform/android/connection.rs @@ -10,7 +10,7 @@ use super::surface::NativeWidget; use crate::Error; use crate::GLApi; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::os::raw::c_void; diff --git a/surfman/src/platform/android/surface.rs b/surfman/src/platform/android/surface.rs index 6b3bb679..ab455f15 100644 --- a/surfman/src/platform/android/surface.rs +++ b/surfman/src/platform/android/surface.rs @@ -25,7 +25,7 @@ use crate::platform::generic::egl::ffi::EGL_NO_IMAGE_KHR; use crate::renderbuffers::Renderbuffers; use crate::{Error, SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType, WindowingApiError}; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; use std::os::raw::c_void; diff --git a/surfman/src/platform/generic/egl/surface.rs b/surfman/src/platform/generic/egl/surface.rs index a39cee2e..5d469bab 100644 --- a/surfman/src/platform/generic/egl/surface.rs +++ b/surfman/src/platform/generic/egl/surface.rs @@ -20,7 +20,7 @@ use crate::renderbuffers::Renderbuffers; use crate::Gl; use crate::{ContextAttributes, ContextID, Error, SurfaceID, SurfaceInfo}; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; use std::mem; diff --git a/surfman/src/platform/generic/mod.rs b/surfman/src/platform/generic/mod.rs index d893330b..f0e5e2ce 100644 --- a/surfman/src/platform/generic/mod.rs +++ b/surfman/src/platform/generic/mod.rs @@ -5,4 +5,4 @@ #[cfg(any(android, angle, linux))] pub(crate) mod egl; -pub mod multi; +pub mod multi; \ No newline at end of file diff --git a/surfman/src/platform/generic/multi/connection.rs b/surfman/src/platform/generic/multi/connection.rs index c06b1bd1..6fd68b09 100644 --- a/surfman/src/platform/generic/multi/connection.rs +++ b/surfman/src/platform/generic/multi/connection.rs @@ -9,7 +9,7 @@ use crate::device::Device as DeviceInterface; use crate::Error; use crate::GLApi; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::os::raw::c_void; diff --git a/surfman/src/platform/generic/multi/device.rs b/surfman/src/platform/generic/multi/device.rs index 92febbd6..eb073302 100644 --- a/surfman/src/platform/generic/multi/device.rs +++ b/surfman/src/platform/generic/multi/device.rs @@ -8,9 +8,9 @@ use super::surface::{NativeWidget, Surface, SurfaceTexture}; use crate::connection::Connection as ConnectionInterface; use crate::context::ContextAttributes; use crate::device::Device as DeviceInterface; +use crate::geom::default::Size2D; use crate::gl::types::{GLenum, GLuint}; use crate::{ContextID, Error, GLApi, SurfaceAccess, SurfaceInfo, SurfaceType}; -use euclid::default::Size2D; use std::os::raw::c_void; diff --git a/surfman/src/platform/generic/multi/surface.rs b/surfman/src/platform/generic/multi/surface.rs index ad23615a..7cdf3e24 100644 --- a/surfman/src/platform/generic/multi/surface.rs +++ b/surfman/src/platform/generic/multi/surface.rs @@ -6,9 +6,9 @@ use super::context::Context; use super::device::Device; use crate::connection::Connection as ConnectionInterface; use crate::device::Device as DeviceInterface; +use crate::geom::default::Size2D; use crate::gl::types::{GLenum, GLuint}; use crate::{Error, SurfaceAccess, SurfaceInfo, SurfaceType}; -use euclid::default::Size2D; use std::fmt::{self, Debug, Formatter}; diff --git a/surfman/src/platform/macos/cgl/connection.rs b/surfman/src/platform/macos/cgl/connection.rs index bcb7dd71..6154cbd5 100644 --- a/surfman/src/platform/macos/cgl/connection.rs +++ b/surfman/src/platform/macos/cgl/connection.rs @@ -12,7 +12,7 @@ use crate::platform::macos::system::surface::NativeWidget; use crate::Error; use crate::GLApi; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::os::raw::c_void; diff --git a/surfman/src/platform/macos/cgl/surface.rs b/surfman/src/platform/macos/cgl/surface.rs index f3bda5b1..6d80b0cb 100644 --- a/surfman/src/platform/macos/cgl/surface.rs +++ b/surfman/src/platform/macos/cgl/surface.rs @@ -11,8 +11,8 @@ use crate::platform::macos::system::surface::Surface as SystemSurface; use crate::renderbuffers::Renderbuffers; use crate::{gl, Error, SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType, WindowingApiError}; +use crate::geom::default::Size2D; use core_foundation::base::TCFType; -use euclid::default::Size2D; use io_surface::{self, IOSurface}; use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; diff --git a/surfman/src/platform/macos/system/connection.rs b/surfman/src/platform/macos/system/connection.rs index edd6ddbe..af61c929 100644 --- a/surfman/src/platform/macos/system/connection.rs +++ b/surfman/src/platform/macos/system/connection.rs @@ -17,7 +17,7 @@ use core_foundation::bundle::CFBundleGetMainBundle; use core_foundation::dictionary::{CFMutableDictionary, CFMutableDictionaryRef}; use core_foundation::string::CFString; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::os::raw::c_void; use std::str::FromStr; diff --git a/surfman/src/platform/macos/system/surface.rs b/surfman/src/platform/macos/system/surface.rs index 8e4eedde..9fe9a4a6 100644 --- a/surfman/src/platform/macos/system/surface.rs +++ b/surfman/src/platform/macos/system/surface.rs @@ -8,6 +8,7 @@ use super::ffi::{kCVReturnSuccess, kIOMapWriteCombineCache}; use super::ffi::{IOSurfaceGetAllocSize, IOSurfaceGetBaseAddress, IOSurfaceGetBytesPerRow}; use crate::{Error, SurfaceAccess, SurfaceID, SurfaceType, SystemSurfaceInfo}; +use crate::geom::default::Size2D; use cocoa::appkit::{NSScreen, NSView as NSViewMethods, NSWindow}; use cocoa::base::{id, YES}; use cocoa::foundation::{NSPoint, NSRect, NSSize}; @@ -18,7 +19,6 @@ use core_foundation::number::CFNumber; use core_foundation::string::CFString; use core_graphics::geometry::{CGRect, CGSize, CG_ZERO_POINT}; use display_link::macos::cvdisplaylink::{CVDisplayLink, CVTimeStamp, DisplayLink}; -use euclid::default::Size2D; use io_surface::{self, kIOSurfaceBytesPerElement, kIOSurfaceBytesPerRow, IOSurface, IOSurfaceRef}; use io_surface::{kIOSurfaceCacheMode, kIOSurfaceHeight, kIOSurfacePixelFormat, kIOSurfaceWidth}; use mach::kern_return::KERN_SUCCESS; @@ -244,7 +244,11 @@ impl Device { } /// Resizes a widget surface - pub fn resize_surface(&self, surface: &mut Surface, mut size: Size2D) -> Result<(), Error> { + pub fn resize_surface( + &self, + surface: &mut Surface, + mut size: Size2D, + ) -> Result<(), Error> { // The surface will not appear if its width is not a multiple of 4 (i.e. stride is a // multiple of 16 bytes). Enforce this. let width = size.width as i32; diff --git a/surfman/src/platform/unix/generic/connection.rs b/surfman/src/platform/unix/generic/connection.rs index 00f4b60a..69e5b31f 100644 --- a/surfman/src/platform/unix/generic/connection.rs +++ b/surfman/src/platform/unix/generic/connection.rs @@ -11,7 +11,7 @@ use crate::platform::generic::egl::device::EGL_FUNCTIONS; use crate::platform::generic::egl::ffi::EGL_PLATFORM_SURFACELESS_MESA; use crate::Error; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::os::raw::c_void; use std::sync::Arc; diff --git a/surfman/src/platform/unix/generic/surface.rs b/surfman/src/platform/unix/generic/surface.rs index ad15ba7b..c12cd156 100644 --- a/surfman/src/platform/unix/generic/surface.rs +++ b/surfman/src/platform/unix/generic/surface.rs @@ -9,7 +9,7 @@ use crate::gl::types::{GLenum, GLuint}; use crate::platform::generic::egl::surface::{EGLBackedSurface, EGLSurfaceTexture}; use crate::{Error, SurfaceAccess, SurfaceInfo, SurfaceType}; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::marker::PhantomData; // FIXME(pcwalton): Is this right, or should it be `TEXTURE_EXTERNAL_OES`? diff --git a/surfman/src/platform/unix/mod.rs b/surfman/src/platform/unix/mod.rs index a5247c91..f117faba 100644 --- a/surfman/src/platform/unix/mod.rs +++ b/surfman/src/platform/unix/mod.rs @@ -16,4 +16,4 @@ pub mod generic; #[cfg(linux)] pub mod wayland; #[cfg(x11)] -pub mod x11; +pub mod x11; \ No newline at end of file diff --git a/surfman/src/platform/unix/wayland/connection.rs b/surfman/src/platform/unix/wayland/connection.rs index 78811ab0..fb4aff67 100644 --- a/surfman/src/platform/unix/wayland/connection.rs +++ b/surfman/src/platform/unix/wayland/connection.rs @@ -11,7 +11,7 @@ use crate::platform::generic::egl::device::EGL_FUNCTIONS; use crate::platform::generic::egl::ffi::EGL_PLATFORM_WAYLAND_KHR; use crate::Error; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::os::raw::c_void; use std::ptr; use std::sync::Arc; diff --git a/surfman/src/platform/unix/wayland/surface.rs b/surfman/src/platform/unix/wayland/surface.rs index 7702ad85..40834ecd 100644 --- a/surfman/src/platform/unix/wayland/surface.rs +++ b/surfman/src/platform/unix/wayland/surface.rs @@ -10,7 +10,7 @@ use crate::platform::generic::egl::context; use crate::platform::generic::egl::surface::{EGLBackedSurface, EGLSurfaceTexture}; use crate::{Error, SurfaceAccess, SurfaceInfo, SurfaceType}; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::marker::PhantomData; use std::os::raw::c_void; use wayland_sys::client::wl_proxy; diff --git a/surfman/src/platform/unix/x11/connection.rs b/surfman/src/platform/unix/x11/connection.rs index 934c12ba..335821f6 100644 --- a/surfman/src/platform/unix/x11/connection.rs +++ b/surfman/src/platform/unix/x11/connection.rs @@ -12,7 +12,7 @@ use crate::platform::generic::egl::device::EGL_FUNCTIONS; use crate::platform::generic::egl::ffi::EGL_PLATFORM_X11_KHR; use crate::platform::unix::generic::device::Adapter; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::marker::PhantomData; use std::os::raw::c_void; diff --git a/surfman/src/platform/unix/x11/mod.rs b/surfman/src/platform/unix/x11/mod.rs index 8bce13ca..1e76dd5a 100644 --- a/surfman/src/platform/unix/x11/mod.rs +++ b/surfman/src/platform/unix/x11/mod.rs @@ -12,4 +12,4 @@ mod implementation; #[cfg(test)] #[path = "../../../tests.rs"] -mod tests; +mod tests; \ No newline at end of file diff --git a/surfman/src/platform/unix/x11/surface.rs b/surfman/src/platform/unix/x11/surface.rs index 381e450f..149e12f6 100644 --- a/surfman/src/platform/unix/x11/surface.rs +++ b/surfman/src/platform/unix/x11/surface.rs @@ -12,7 +12,7 @@ use crate::platform::generic::egl::context; use crate::platform::generic::egl::surface::{EGLBackedSurface, EGLSurfaceTexture}; use crate::{Error, SurfaceAccess, SurfaceInfo, SurfaceType}; -use euclid::default::Size2D; +use crate::geom::default::Size2D; use std::marker::PhantomData; use std::os::raw::c_void; use x11::xlib::{Window, XGetGeometry}; diff --git a/surfman/src/renderbuffers.rs b/surfman/src/renderbuffers.rs index f83d2136..31bb04a3 100644 --- a/surfman/src/renderbuffers.rs +++ b/surfman/src/renderbuffers.rs @@ -7,7 +7,7 @@ use crate::gl; use crate::gl::types::GLuint; use crate::Gl; -use euclid::default::Size2D; +use crate::geom::default::Size2D; pub(crate) enum Renderbuffers { IndividualDepthStencil { depth: GLuint, stencil: GLuint }, diff --git a/surfman/src/surface.rs b/surfman/src/surface.rs index 06927dff..864e66ae 100644 --- a/surfman/src/surface.rs +++ b/surfman/src/surface.rs @@ -4,8 +4,8 @@ use crate::context::ContextID; +use crate::geom::default::Size2D; use crate::gl::types::GLuint; -use euclid::default::Size2D; use std::fmt::{self, Display, Formatter}; /// Various data about the surface. diff --git a/surfman/src/tests.rs b/surfman/src/tests.rs index d17ec077..95a76b9d 100644 --- a/surfman/src/tests.rs +++ b/surfman/src/tests.rs @@ -17,7 +17,7 @@ use crate::gl::types::{GLenum, GLuint}; use crate::{ContextAttributeFlags, ContextAttributes, Error, GLApi, GLVersion, Gl, SurfaceAccess}; use crate::{SurfaceType, WindowingApiError}; -use euclid::default::Size2D; +use crate::geom::Size2D; use std::os::raw::c_void; use std::sync::mpsc; use std::thread;