retrofire_core/math/
point.rs

1use core::{
2    array,
3    fmt::{Debug, Formatter},
4    marker::PhantomData as Pd,
5    ops::{Add, AddAssign, Index, IndexMut, Sub, SubAssign},
6};
7
8use super::{Affine, ApproxEq, Linear, Vector, space::Real, vary::ZDiv};
9
10#[repr(transparent)]
11pub struct Point<Repr, Space = ()>(pub Repr, Pd<Space>);
12
13/// A 2-point with `f32` components.
14pub type Point2<Basis = ()> = Point<[f32; 2], Real<2, Basis>>;
15/// A 3-point with `f32` components.
16pub type Point3<Basis = ()> = Point<[f32; 3], Real<3, Basis>>;
17
18/// A 2-point with `u32` components.
19pub type Point2u<Basis = ()> = Point<[u32; 2], Real<2, Basis>>;
20
21/// Returns a real 2-point with `x` and `y` components.
22pub const fn pt2<Sc, B>(x: Sc, y: Sc) -> Point<[Sc; 2], Real<2, B>> {
23    Point([x, y], Pd)
24}
25/// Returns a real 3-point with `x`, `y`, and `z` components.
26pub const fn pt3<Sc, B>(x: Sc, y: Sc, z: Sc) -> Point<[Sc; 3], Real<3, B>> {
27    Point([x, y, z], Pd)
28}
29
30impl<R, Sp> Point<R, Sp> {
31    #[inline]
32    pub const fn new(repr: R) -> Self {
33        Self(repr, Pd)
34    }
35
36    /// Returns a point with value equal to `self` but in space `S`.
37    // TODO Cannot be const (yet?) due to E0493 :(
38    #[inline]
39    pub fn to<S>(self) -> Point<R, S> {
40        Point(self.0, Pd)
41    }
42
43    /// Returns the vector equivalent to `self`.
44    // TODO Cannot be const (yet?) due to E0493 :(
45    #[inline]
46    pub fn to_vec(self) -> Vector<R, Sp> {
47        Vector::new(self.0)
48    }
49}
50
51impl<Sc: Copy, Sp, const N: usize> Point<[Sc; N], Sp> {
52    /// Returns a vector of the same dimension as `self` by applying `f`
53    /// component-wise.
54    ///
55    /// # Examples
56    /// ```
57    /// use retrofire_core::math::{pt3};
58    ///
59    /// let p = pt3::<i32, ()>(1, 2, 3);
60    /// assert_eq!(p.map(|x| x as f32 + 0.5), pt3(1.5, 2.5, 3.5));
61    /// ```
62    #[inline]
63    #[must_use]
64    pub fn map<T>(self, f: impl FnMut(Sc) -> T) -> Point<[T; N], Sp> {
65        self.0.map(f).into()
66    }
67
68    /// Returns a vector of the same dimension as `self` by applying `f`
69    /// component-wise to `self` and `other`.
70    ///
71    /// # Examples
72    /// ```
73    /// use retrofire_core::math::pt3;
74    ///
75    /// let a = pt3::<f32, ()>(1.0, 2.0, 3.0);
76    /// let b = pt3(4, 3, 2);
77    /// assert_eq!(a.zip_map(b, |x, exp| x.powi(exp)), pt3(1.0, 8.0, 9.0));
78    /// ```
79    #[inline]
80    #[must_use]
81    pub fn zip_map<T: Copy, U>(
82        self,
83        other: Point<[T; N], Sp>,
84        mut f: impl FnMut(Sc, T) -> U,
85    ) -> Point<[U; N], Sp> {
86        array::from_fn(|i| f(self.0[i], other.0[i])).into()
87    }
88}
89
90impl<const N: usize, B> Point<[f32; N], Real<N, B>> {
91    /// Returns the canonical origin point (0, …, 0).
92    #[inline]
93    pub const fn origin() -> Self {
94        Self::new([0.0; N])
95    }
96
97    /// Returns the Euclidean distance between `self` and another point.
98    ///
99    /// # Example
100    /// ```
101    /// use retrofire_core::math::{Point2, pt2};
102    ///
103    /// let x3: Point2 = pt2(3.0, 0.0);
104    /// let y4 = pt2(0.0, 4.0);
105    /// assert_eq!(x3.distance(&y4), 5.0);
106    /// ```
107    #[cfg(feature = "fp")]
108    #[inline]
109    pub fn distance(&self, other: &Self) -> f32 {
110        self.sub(other).len()
111    }
112
113    /// Returns the square of the Euclidean distance between `self` and another
114    /// point.
115    ///
116    /// The squared distance is faster to compute than the [distance][Self::distance].
117    ///
118    /// # Example
119    /// ```
120    /// use retrofire_core::math::{Point2, pt2};
121    ///
122    /// let x3: Point2 = pt2(3.0, 0.0);
123    /// let y4 = pt2(0.0, 4.0);
124    /// assert_eq!(x3.distance_sqr(&y4), 5.0 * 5.0);
125    /// ```
126    #[inline]
127    pub fn distance_sqr(&self, other: &Self) -> f32 {
128        self.sub(other).len_sqr()
129    }
130
131    /// Returns `self` clamped component-wise to the given range.
132    ///
133    /// The result is a vector `v` such that for each valid index `i`,
134    /// `v[i]` is equal to `self[i].clamp(min[i], max[i])`.
135    ///
136    /// See also [`f32::clamp`].
137    ///
138    /// # Panics
139    /// If `min[i] > max[i]` for any valid index `i`,
140    /// or if either `min` or `max` contains a NaN.
141    ///
142    /// # Examples
143    /// ```
144    /// use retrofire_core::math::{pt3, Point3};
145    ///
146    /// let pt: Point3 = pt3(0.5, 1.5, -2.0);
147    /// // Clamp to the unit cube
148    /// let clamped = pt.clamp(&pt3(0.0, 0.0, 0.0), &pt3(1.0, 1.0, 1.0));
149    /// assert_eq!(clamped, pt3(0.5, 1.0, 0.0));
150    /// ```
151    #[must_use]
152    pub fn clamp(&self, min: &Self, max: &Self) -> Self {
153        array::from_fn(|i| self.0[i].clamp(min.0[i], max.0[i])).into()
154    }
155}
156
157impl<R, B, Sc> Point<R, Real<2, B>>
158where
159    R: Index<usize, Output = Sc>,
160    Sc: Copy,
161{
162    /// Returns the x component of `self`.
163    #[inline]
164    pub fn x(&self) -> Sc {
165        self.0[0]
166    }
167    /// Returns the y component of `self`.
168    #[inline]
169    pub fn y(&self) -> Sc {
170        self.0[1]
171    }
172}
173
174impl Point2 {
175    /// Converts `self` into a `Point3`, with z equal to 0.
176    pub fn to_pt3(self) -> Point3 {
177        pt3(self.x(), self.y(), 0.0)
178    }
179}
180
181impl<R, Sc, B> Point<R, Real<3, B>>
182where
183    R: Index<usize, Output = Sc>,
184    Sc: Copy,
185{
186    /// Returns the x component of `self`.
187    #[inline]
188    pub fn x(&self) -> Sc {
189        self.0[0]
190    }
191    /// Returns the y component of `self`.
192    #[inline]
193    pub fn y(&self) -> Sc {
194        self.0[1]
195    }
196    /// Returns the z component of `self`.
197    #[inline]
198    pub fn z(&self) -> Sc {
199        self.0[2]
200    }
201}
202
203//
204// Local trait impls
205//
206
207impl<ScSelf, ScDiff, Sp, const N: usize> Affine for Point<[ScSelf; N], Sp>
208where
209    ScSelf: Affine<Diff = ScDiff> + Copy,
210    ScDiff: Linear<Scalar = ScDiff> + Copy,
211{
212    type Space = Sp;
213    type Diff = Vector<[ScDiff; N], Sp>;
214    const DIM: usize = N;
215
216    #[inline]
217    fn add(&self, other: &Self::Diff) -> Self {
218        // TODO Profile performance of array::from_fn
219        Self(array::from_fn(|i| self.0[i].add(&other.0[i])), Pd)
220    }
221    #[inline]
222    fn sub(&self, other: &Self) -> Self::Diff {
223        Vector::new(array::from_fn(|i| self.0[i].sub(&other.0[i])))
224    }
225}
226
227impl<Sc, Sp, const N: usize> ZDiv for Point<[Sc; N], Sp>
228where
229    Sc: ZDiv + Copy,
230{
231    fn z_div(self, z: f32) -> Self {
232        self.map(|c| c.z_div(z))
233    }
234}
235
236impl<Sc: ApproxEq, Sp, const N: usize> ApproxEq<Self, Sc>
237    for Point<[Sc; N], Sp>
238{
239    fn approx_eq_eps(&self, other: &Self, eps: &Sc) -> bool {
240        self.0.approx_eq_eps(&other.0, eps)
241    }
242    fn relative_epsilon() -> Sc {
243        Sc::relative_epsilon()
244    }
245}
246
247//
248// Foreign trait impls
249//
250
251// Manual impls of Copy, Clone, Eq, and PartialEq to avoid
252// superfluous where S: Trait bound
253
254impl<R: Copy, S> Copy for Point<R, S> {}
255
256impl<R: Clone, S> Clone for Point<R, S> {
257    fn clone(&self) -> Self {
258        Self(self.0.clone(), Pd)
259    }
260}
261
262impl<R: Default, S> Default for Point<R, S> {
263    fn default() -> Self {
264        Self(R::default(), Pd)
265    }
266}
267
268impl<R: Debug, Sp: Debug + Default> Debug for Point<R, Sp> {
269    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
270        write!(f, "Point<{:?}>", Sp::default())?;
271        Debug::fmt(&self.0, f)
272    }
273}
274
275impl<R: Eq, S> Eq for Point<R, S> {}
276
277impl<R: PartialEq, S> PartialEq for Point<R, S> {
278    fn eq(&self, other: &Self) -> bool {
279        self.0 == other.0
280    }
281}
282
283impl<R, Sp> From<R> for Point<R, Sp> {
284    #[inline]
285    fn from(repr: R) -> Self {
286        Self(repr, Pd)
287    }
288}
289/*
290impl<B> From<Point3<B>> for HomVec3<B> {
291    fn from(p: Point3<B>) -> Self {
292        let [x, y, z] = p.0;
293        [x, y, z, 1.0].into()
294    }
295}
296
297impl<B> From<Point2<B>> for HomVec2<B> {
298    fn from(p: Point2<B>) -> Self {
299        let [x, y] = p.0;
300        [x, y, 1.0].into()
301    }
302}*/
303
304impl<R: Index<usize>, Sp> Index<usize> for Point<R, Sp> {
305    type Output = R::Output;
306
307    fn index(&self, i: usize) -> &Self::Output {
308        self.0.index(i)
309    }
310}
311
312impl<R: IndexMut<usize>, Sp> IndexMut<usize> for Point<R, Sp> {
313    fn index_mut(&mut self, i: usize) -> &mut R::Output {
314        self.0.index_mut(i)
315    }
316}
317
318impl<R, Sp> Add<<Self as Affine>::Diff> for Point<R, Sp>
319where
320    Self: Affine,
321{
322    type Output = Self;
323
324    fn add(self, other: <Self as Affine>::Diff) -> Self {
325        Affine::add(&self, &other)
326    }
327}
328
329impl<R, Sp> AddAssign<<Self as Affine>::Diff> for Point<R, Sp>
330where
331    Self: Affine,
332{
333    fn add_assign(&mut self, other: <Self as Affine>::Diff) {
334        *self = Affine::add(self, &other);
335    }
336}
337
338impl<R, Sp> Sub<<Self as Affine>::Diff> for Point<R, Sp>
339where
340    Self: Affine,
341{
342    type Output = Self;
343
344    fn sub(self, other: <Self as Affine>::Diff) -> Self {
345        Affine::add(&self, &other.neg())
346    }
347}
348
349impl<R, Sp> SubAssign<<Self as Affine>::Diff> for Point<R, Sp>
350where
351    Self: Affine,
352{
353    fn sub_assign(&mut self, other: <Self as Affine>::Diff) {
354        *self = Affine::add(self, &other.neg());
355    }
356}
357
358impl<R, Sp> Sub for Point<R, Sp>
359where
360    Self: Affine,
361{
362    type Output = <Self as Affine>::Diff;
363
364    fn sub(self, other: Self) -> Self::Output {
365        Affine::sub(&self, &other)
366    }
367}
368
369#[cfg(test)]
370#[allow(non_upper_case_globals)]
371mod tests {
372    use super::*;
373    use crate::math::{Lerp, vec2, vec3};
374
375    mod f32 {
376        use super::*;
377
378        const pt2: fn(f32, f32) -> Point2 = super::pt2;
379        const pt3: fn(f32, f32, f32) -> Point3 = super::pt3;
380        #[test]
381        fn vector_addition() {
382            assert_eq!(pt2(1.0, 2.0) + vec2(-2.0, 3.0), pt2(-1.0, 5.0));
383            assert_eq!(
384                pt3(1.0, 2.0, 3.0) + vec3(-2.0, 3.0, 1.0),
385                pt3(-1.0, 5.0, 4.0)
386            )
387        }
388        #[test]
389        fn vector_subtraction() {
390            assert_eq!(pt2(1.0, 2.0) - vec2(-2.0, 3.0), pt2(3.0, -1.0));
391            assert_eq!(
392                pt3(1.0, 2.0, 3.0) - vec3(-2.0, 3.0, 1.0),
393                pt3(3.0, -1.0, 2.0)
394            )
395        }
396        #[test]
397        fn point_subtraction() {
398            assert_eq!(pt2(1.0, 2.0) - pt2(-2.0, 3.0), vec2(3.0, -1.0));
399            assert_eq!(
400                pt3(1.0, 2.0, 3.0) - pt3(-2.0, 3.0, 1.0),
401                vec3(3.0, -1.0, 2.0)
402            )
403        }
404        #[test]
405        fn point_point_distance_sqr() {
406            assert_eq!(pt2(1.0, -1.0).distance_sqr(&pt2(-2.0, 3.0)), 25.0);
407            assert_eq!(
408                pt3(1.0, -3.0, 2.0).distance_sqr(&pt3(-2.0, 3.0, 4.0)),
409                49.0
410            );
411        }
412        #[test]
413        #[cfg(feature = "fp")]
414        fn point_point_distance() {
415            assert_eq!(pt2(1.0, -1.0).distance(&pt2(-2.0, 3.0)), 5.0);
416            assert_eq!(pt3(1.0, -3.0, 2.0).distance(&pt3(-2.0, 3.0, 4.0)), 7.0);
417        }
418        #[test]
419        fn point2_clamp() {
420            let (min, max) = (&pt2(-2.0, -1.0), &pt2(3.0, 2.0));
421            assert_eq!(pt2(1.0, -1.0).clamp(min, max), pt2(1.0, -1.0));
422            assert_eq!(pt2(3.0, -2.0).clamp(min, max), pt2(3.0, -1.0));
423            assert_eq!(pt2(-3.0, 4.0).clamp(min, max), pt2(-2.0, 2.0));
424        }
425        #[test]
426        fn point3_clamp() {
427            let (min, max) = (&pt3(-2.0, -1.0, 0.0), &pt3(3.0, 2.0, 1.0));
428            assert_eq!(
429                pt3(1.0, -1.0, 0.0).clamp(min, max),
430                pt3(1.0, -1.0, 0.0)
431            );
432            assert_eq!(
433                pt3(3.0, -2.0, -1.0).clamp(min, max),
434                pt3(3.0, -1.0, 0.0)
435            );
436            assert_eq!(
437                pt3(-3.0, 4.0, 2.0).clamp(min, max),
438                pt3(-2.0, 2.0, 1.0)
439            );
440        }
441        #[test]
442        fn point2_indexing() {
443            let mut p = pt2(2.0, -1.0);
444            assert_eq!(p[0], p.x());
445            assert_eq!(p[1], p.y());
446
447            p[1] -= 1.0;
448            assert_eq!(p[1], -2.0);
449        }
450        #[test]
451        fn point3_indexing() {
452            let mut p = pt3(2.0, -1.0, 3.0);
453            assert_eq!(p[0], p.x());
454            assert_eq!(p[1], p.y());
455            assert_eq!(p[2], p.z());
456
457            p[2] += 1.0;
458            assert_eq!(p[2], 4.0);
459        }
460        #[test]
461        #[should_panic]
462        fn point2_index_oob() {
463            _ = pt2(1.0, 2.0)[2];
464        }
465        #[test]
466        fn point2_lerp() {
467            assert_eq!(
468                pt2(2.0, -1.0).lerp(&pt2(-2.0, 3.0), 0.25),
469                pt2(1.0, 0.0)
470            );
471        }
472    }
473
474    mod u32 {
475        use super::*;
476
477        const pt2: fn(u32, u32) -> Point2u = super::super::pt2;
478
479        #[test]
480        fn vector_addition() {
481            assert_eq!(pt2(1_u32, 2) + vec2(1_i32, -2), pt2(2_u32, 0));
482        }
483
484        #[test]
485        fn vector_subtraction() {
486            assert_eq!(pt2(3_u32, 2) - vec2(3_i32, -1), pt2(0_u32, 3));
487        }
488
489        #[test]
490        fn point_subtraction() {
491            assert_eq!(pt2(3_u32, 2) - pt2(3_u32, 3), vec2(0, -1));
492        }
493
494        #[test]
495        fn indexing() {
496            let mut p = pt2(1u32, 2);
497            assert_eq!(p[1], 2);
498            p[0] = 3;
499            assert_eq!(p.0, [3, 2]);
500        }
501
502        #[test]
503        fn from_array() {
504            assert_eq!(Point2u::from([1, 2]), pt2(1, 2));
505        }
506    }
507}