Skip to main content

nms_graph/
spatial.rs

1use rstar::{AABB, PointDistance, RTreeObject};
2
3// Re-export SystemId from nms-core (canonical definition lives there).
4pub use nms_core::system::SystemId;
5
6/// A system's position in 3D voxel space, stored in the R-tree.
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct SystemPoint {
9    pub id: SystemId,
10    pub point: [f64; 3],
11}
12
13impl SystemPoint {
14    pub fn new(id: SystemId, x: f64, y: f64, z: f64) -> Self {
15        Self {
16            id,
17            point: [x, y, z],
18        }
19    }
20
21    /// Create from a `GalacticAddress`.
22    pub fn from_address(addr: &nms_core::address::GalacticAddress) -> Self {
23        let id = SystemId::from_address(addr);
24        Self::new(
25            id,
26            addr.voxel_x() as f64,
27            addr.voxel_y() as f64,
28            addr.voxel_z() as f64,
29        )
30    }
31}
32
33impl RTreeObject for SystemPoint {
34    type Envelope = AABB<[f64; 3]>;
35
36    fn envelope(&self) -> Self::Envelope {
37        AABB::from_point(self.point)
38    }
39}
40
41impl PointDistance for SystemPoint {
42    fn distance_2(&self, point: &[f64; 3]) -> f64 {
43        let dx = self.point[0] - point[0];
44        let dy = self.point[1] - point[1];
45        let dz = self.point[2] - point[2];
46        dx * dx + dy * dy + dz * dz
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use nms_core::address::GalacticAddress;
54
55    #[test]
56    fn test_system_id_from_address_zeroes_planet() {
57        let addr1 = GalacticAddress::new(100, 50, 200, 0x123, 0, 0);
58        let addr2 = GalacticAddress::new(100, 50, 200, 0x123, 5, 0);
59        assert_eq!(
60            SystemId::from_address(&addr1),
61            SystemId::from_address(&addr2)
62        );
63    }
64
65    #[test]
66    fn test_system_id_different_ssi_differs() {
67        let addr1 = GalacticAddress::new(100, 50, 200, 0x123, 0, 0);
68        let addr2 = GalacticAddress::new(100, 50, 200, 0x456, 0, 0);
69        assert_ne!(
70            SystemId::from_address(&addr1),
71            SystemId::from_address(&addr2)
72        );
73    }
74
75    #[test]
76    fn test_system_point_from_address_coordinates() {
77        let addr = GalacticAddress::new(100, -50, 200, 0x123, 3, 0);
78        let point = SystemPoint::from_address(&addr);
79        assert_eq!(point.point, [100.0, -50.0, 200.0]);
80    }
81
82    #[test]
83    fn test_system_point_distance_squared() {
84        use rstar::PointDistance;
85        let p = SystemPoint::new(SystemId(0), 0.0, 0.0, 0.0);
86        let target = [3.0, 4.0, 0.0];
87        assert!((p.distance_2(&target) - 25.0).abs() < 1e-10);
88    }
89
90    #[test]
91    fn test_rtree_nearest_neighbor() {
92        use rstar::RTree;
93        let points = vec![
94            SystemPoint::new(SystemId(1), 0.0, 0.0, 0.0),
95            SystemPoint::new(SystemId(2), 10.0, 0.0, 0.0),
96            SystemPoint::new(SystemId(3), 100.0, 0.0, 0.0),
97        ];
98        let tree = RTree::bulk_load(points);
99        let nearest = tree.nearest_neighbor(&[1.0, 0.0, 0.0]).unwrap();
100        assert_eq!(nearest.id, SystemId(1));
101    }
102
103    #[test]
104    fn test_rtree_bulk_load_size() {
105        use rstar::RTree;
106        let points = vec![
107            SystemPoint::new(SystemId(1), 0.0, 0.0, 0.0),
108            SystemPoint::new(SystemId(2), 5.0, 5.0, 5.0),
109        ];
110        let tree = RTree::bulk_load(points);
111        assert_eq!(tree.size(), 2);
112    }
113
114    #[test]
115    fn test_system_point_envelope() {
116        use rstar::RTreeObject;
117        let p = SystemPoint::new(SystemId(1), 3.0, 4.0, 5.0);
118        let env = p.envelope();
119        assert_eq!(env.lower(), [3.0, 4.0, 5.0]);
120        assert_eq!(env.upper(), [3.0, 4.0, 5.0]);
121    }
122}