Skip to main content

nms_graph/
spatial.rs

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