Skip to main content

symtropy_math/
point.rs

1// Copyright (C) 2024-2026 Tristan Stoltz / Luminous Dynamics
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Commercial licensing: see COMMERCIAL_LICENSE.md at repository root
4use nalgebra::SVector;
5use std::ops::{Add, Sub};
6
7/// A point in D-dimensional space. Stack-allocated via nalgebra::SVector.
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct Point<const D: usize>(pub SVector<f64, D>);
10
11impl<const D: usize> Point<D> {
12    /// Point at the origin.
13    pub fn origin() -> Self {
14        Self(SVector::zeros())
15    }
16
17    /// Create from a fixed-size array.
18    pub fn new(coords: [f64; D]) -> Self {
19        Self(SVector::from(coords))
20    }
21
22    /// Euclidean distance to another point.
23    #[inline]
24    pub fn distance(&self, other: &Self) -> f64 {
25        (self.0 - other.0).norm()
26    }
27
28    /// Squared distance (avoids sqrt — use for comparisons).
29    #[inline]
30    pub fn distance_squared(&self, other: &Self) -> f64 {
31        (self.0 - other.0).norm_squared()
32    }
33
34    /// Linear interpolation: self*(1-t) + other*t.
35    #[inline]
36    pub fn lerp(&self, other: &Self, t: f64) -> Self {
37        Self(self.0 * (1.0 - t) + other.0 * t)
38    }
39
40    /// Access coordinate by index.
41    #[inline]
42    pub fn coord(&self, i: usize) -> f64 {
43        self.0[i]
44    }
45
46    /// Mutable coordinate access.
47    #[inline]
48    pub fn coord_mut(&mut self, i: usize) -> &mut f64 {
49        &mut self.0[i]
50    }
51
52    /// Convert to direction vector.
53    #[inline]
54    pub fn to_vector(&self) -> SVector<f64, D> {
55        self.0
56    }
57}
58
59impl<const D: usize> std::ops::Index<usize> for Point<D> {
60    type Output = f64;
61
62    #[inline]
63    fn index(&self, index: usize) -> &Self::Output {
64        &self.0[index]
65    }
66}
67
68impl<const D: usize> std::ops::IndexMut<usize> for Point<D> {
69    #[inline]
70    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
71        &mut self.0[index]
72    }
73}
74
75impl<const D: usize> Add<SVector<f64, D>> for Point<D> {
76    type Output = Point<D>;
77    #[inline]
78    fn add(self, rhs: SVector<f64, D>) -> Point<D> {
79        Point(self.0 + rhs)
80    }
81}
82
83impl<const D: usize> Sub for Point<D> {
84    type Output = SVector<f64, D>;
85    #[inline]
86    fn sub(self, rhs: Point<D>) -> SVector<f64, D> {
87        self.0 - rhs.0
88    }
89}
90
91impl<const D: usize> Default for Point<D> {
92    fn default() -> Self {
93        Self::origin()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn origin_is_zero() {
103        let p = Point::<4>::origin();
104        for i in 0..4 {
105            assert_eq!(p.coord(i), 0.0);
106        }
107    }
108
109    #[test]
110    fn distance_2d() {
111        let a = Point::new([0.0, 0.0]);
112        let b = Point::new([3.0, 4.0]);
113        assert!((a.distance(&b) - 5.0).abs() < 1e-12);
114    }
115
116    #[test]
117    fn distance_4d() {
118        let a = Point::new([1.0, 2.0, 3.0, 4.0]);
119        let b = Point::new([5.0, 6.0, 7.0, 8.0]);
120        assert!((a.distance(&b) - 8.0).abs() < 1e-12);
121    }
122
123    #[test]
124    fn lerp_midpoint() {
125        let a = Point::new([0.0, 0.0, 0.0]);
126        let b = Point::new([10.0, 20.0, 30.0]);
127        let mid = a.lerp(&b, 0.5);
128        assert!((mid.coord(0) - 5.0).abs() < 1e-12);
129        assert!((mid.coord(1) - 10.0).abs() < 1e-12);
130    }
131
132    #[test]
133    fn subtraction() {
134        let a = Point::new([1.0, 2.0]);
135        let b = Point::new([4.0, 6.0]);
136        let v = b - a;
137        assert!((v[0] - 3.0).abs() < 1e-12);
138        assert!((v[1] - 4.0).abs() < 1e-12);
139    }
140
141    #[test]
142    fn is_copy() {
143        let a = Point::new([1.0, 2.0]);
144        let b = a;
145        assert_eq!(a, b);
146    }
147}