diff --git a/core/src/math/point.rs b/core/src/math/point.rs index cd642498..92b29b9b 100644 --- a/core/src/math/point.rs +++ b/core/src/math/point.rs @@ -6,7 +6,11 @@ use core::{ ops::{AddAssign, DivAssign, MulAssign, SubAssign}, }; -use super::{Affine, ApproxEq, Linear, Vector, space::Real, vary::ZDiv}; +use super::{ + Affine, ApproxEq, Linear, Vector, + space::{Euclidean, Real}, + vary::ZDiv, +}; #[repr(transparent)] pub struct Point(pub Repr, Pd); @@ -157,6 +161,43 @@ impl Point<[f32; N], Real> { pub fn clamp(&self, min: &Self, max: &Self) -> Self { array::from_fn(|i| self.0[i].clamp(min.0[i], max.0[i])).into() } + + /// Returns `self`, moved towards another point by an offset. + /// + /// If the distance to `other` is less than `d`, returns `other`. That is, + /// this method never "overshoots" the target. Negative values of `d` result + /// in moving away from `other`. If `self` equals `other`, returns `other` + /// independent of the value of `d`. + /// + /// To translate by a *relative* offset instead, use [`lerp`][super::Lerp::lerp]. + /// + /// # Examples + /// ``` + /// use retrofire_core::math::{Point2, pt2}; + /// + /// let a: Point2 = pt2(0.0, 0.0); + /// let b: Point2 = pt2(4.0, 3.0); + /// // Move two units along the line y = 3x/4: + /// assert_eq!(a.approach(&b, 2.0), pt2(1.6, 1.2)); + /// // Movement is clamped to b: + /// assert_eq!(a.approach(&b, 10.0), b); + /// // Negative values of `d` move away from b: + /// assert_eq!(a.approach(&b, -1.0), pt2(-0.8, -0.6)); + /// // Approaching the point itself does nothing: + /// assert_eq!(a.approach(&a, 1.0), a); + /// assert_eq!(a.approach(&a, -1.0), a); + /// ``` + #[cfg(feature = "fp")] + #[must_use] + pub fn approach(&self, other: &Self, d: f32) -> Self { + let v = *other - *self; + let l = v.len(); + if d < l && l != 0.0 { + *self + d / l * v + } else { + *other + } + } } impl Point<[Sc; 2], Real<2, B>> { @@ -222,6 +263,20 @@ where } } +impl Euclidean for Point<[f32; N], Real> { + fn origin() -> Self { + Point::origin() + } + + fn distance(&self, other: &Self) -> f32 { + other.sub(self).len() + } + + fn approach(&self, other: &Self, d: f32) -> Self { + self.approach(other, d) + } +} + impl ZDiv for Point<[Sc; N], Sp> where Sc: ZDiv + Copy, diff --git a/core/src/math/space.rs b/core/src/math/space.rs index ccec9ac0..0225e103 100644 --- a/core/src/math/space.rs +++ b/core/src/math/space.rs @@ -6,7 +6,7 @@ use core::fmt::{Debug, Formatter}; use core::iter::zip; use core::marker::PhantomData; -use crate::math::vary::{Iter, Vary, ZDiv}; +use super::vary::{Iter, Vary, ZDiv}; /// Trait for types representing elements of an affine space. /// @@ -92,6 +92,24 @@ pub trait Linear: Affine { fn mul(&self, scalar: Self::Scalar) -> Self; } +pub trait Euclidean: Affine { + /// Returns the canonical origin of this Euclidean space. + fn origin() -> Self; + + /// Returns the length of the translation vector between two points. + fn distance(&self, other: &Self) -> f32; + + /// Returns `self`, moved towards another point by an offset. + /// + /// If the distance to `other` is less than `d`, returns `other`. That is, + /// this method never "overshoots" the target. Negative values of `d` result + /// in moving away from `other`. If `self` equals `other`, returns `other` + /// independent of the value of `d`. + /// + /// To translate by a *relative* offset instead, use [`lerp`][super::Lerp::lerp]. + fn approach(&self, other: &Self, d: f32) -> Self; +} + /// Tag type for real vector spaces and Euclidean spaces. /// /// For example, the type `Real<3>` corresponds to ℝ³. @@ -136,6 +154,26 @@ impl Linear for f32 { } } +impl Euclidean for f32 { + fn origin() -> Self { + 0.0 + } + + fn distance(&self, other: &Self) -> f32 { + (other - self).abs() + } + + fn approach(&self, other: &Self, d: f32) -> Self { + let diff = other - self; + let dist = diff.abs(); + if d < dist && dist != 0.0 { + self + d * diff.signum() + } else { + *other + } + } +} + impl Affine for i32 { type Space = (); type Diff = Self; diff --git a/core/src/math/vec.rs b/core/src/math/vec.rs index e5ab5717..64e1961d 100644 --- a/core/src/math/vec.rs +++ b/core/src/math/vec.rs @@ -13,10 +13,9 @@ use core::{ use crate::math::{ Affine, ApproxEq, Linear, Point, - space::{Proj3, Real}, + space::{Euclidean, Proj3, Real}, vary::ZDiv, }; - // // Types // @@ -576,6 +575,20 @@ where } } +impl Euclidean for Vector<[f32; N], Real> { + fn origin() -> Self { + Vector::zero() + } + + fn distance(&self, other: &Self) -> f32 { + other.sub(self).len() + } + + fn approach(&self, other: &Self, d: f32) -> Self { + self.to_pt().approach(&other.to_pt(), d).to_vec() + } +} + impl ZDiv for Vector<[Sc; N], Sp> where Sc: ZDiv + Copy,