1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
13pub struct WorldCoordinate {
14 pub x: f64,
15 pub y: f64,
16 pub z: f64,
17}
18
19impl WorldCoordinate {
20 pub fn new(x: f64, y: f64, z: f64) -> Self {
22 Self { x, y, z }
23 }
24
25 pub fn zero() -> Self {
27 Self::new(0.0, 0.0, 0.0)
28 }
29
30 pub fn distance_to(&self, other: &WorldCoordinate) -> f64 {
32 let dx = self.x - other.x;
33 let dy = self.y - other.y;
34 let dz = self.z - other.z;
35 (dx * dx + dy * dy + dz * dz).sqrt()
36 }
37
38 pub fn vector_to(&self, other: &WorldCoordinate) -> WorldCoordinate {
40 WorldCoordinate {
41 x: other.x - self.x,
42 y: other.y - self.y,
43 z: other.z - self.z,
44 }
45 }
46
47 pub fn magnitude(&self) -> f64 {
49 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
50 }
51
52 pub fn normalized(&self) -> WorldCoordinate {
54 let mag = self.magnitude();
55 if mag == 0.0 {
56 WorldCoordinate::zero()
57 } else {
58 WorldCoordinate {
59 x: self.x / mag,
60 y: self.y / mag,
61 z: self.z / mag,
62 }
63 }
64 }
65
66 pub fn add(&self, other: &WorldCoordinate) -> WorldCoordinate {
68 WorldCoordinate {
69 x: self.x + other.x,
70 y: self.y + other.y,
71 z: self.z + other.z,
72 }
73 }
74
75 pub fn scale(&self, factor: f64) -> WorldCoordinate {
77 WorldCoordinate {
78 x: self.x * factor,
79 y: self.y * factor,
80 z: self.z * factor,
81 }
82 }
83
84 pub fn from_env() -> Self {
86 let x = std::env::var("HORIZON_CENTER_X")
87 .ok()
88 .and_then(|s| s.parse().ok())
89 .unwrap_or(0.0);
90 let y = std::env::var("HORIZON_CENTER_Y")
91 .ok()
92 .and_then(|s| s.parse().ok())
93 .unwrap_or(0.0);
94 let z = std::env::var("HORIZON_CENTER_Z")
95 .ok()
96 .and_then(|s| s.parse().ok())
97 .unwrap_or(0.0);
98 Self { x, y, z }
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
107pub struct RegionCoordinate {
108 pub x: i64,
109 pub y: i64,
110 pub z: i64,
111}
112
113impl RegionCoordinate {
114 pub fn new(x: i64, y: i64, z: i64) -> Self {
116 Self { x, y, z }
117 }
118
119 pub fn center() -> Self {
121 Self::new(0, 0, 0)
122 }
123
124 pub fn manhattan_distance(&self, other: &RegionCoordinate) -> i64 {
126 (self.x - other.x).abs() + (self.y - other.y).abs() + (self.z - other.z).abs()
127 }
128
129 pub fn adjacent_regions(&self) -> Vec<RegionCoordinate> {
131 vec![
132 RegionCoordinate::new(self.x + 1, self.y, self.z),
133 RegionCoordinate::new(self.x - 1, self.y, self.z),
134 RegionCoordinate::new(self.x, self.y + 1, self.z),
135 RegionCoordinate::new(self.x, self.y - 1, self.z),
136 RegionCoordinate::new(self.x, self.y, self.z + 1),
137 RegionCoordinate::new(self.x, self.y, self.z - 1),
138 ]
139 }
140
141 pub fn to_world_center(&self, region_size: f64) -> WorldCoordinate {
145 WorldCoordinate::new(
146 self.x as f64 * region_size,
147 self.y as f64 * region_size,
148 self.z as f64 * region_size,
149 )
150 }
151
152 pub fn from_world_coordinate(coord: &WorldCoordinate, region_size: f64) -> Self {
154 Self {
155 x: (coord.x / region_size).floor() as i64,
156 y: (coord.y / region_size).floor() as i64,
157 z: (coord.z / region_size).floor() as i64,
158 }
159 }
160
161 pub fn from_env() -> Self {
163 let x = std::env::var("HORIZON_REGION_X")
164 .ok()
165 .and_then(|s| s.parse().ok())
166 .unwrap_or(0);
167 let y = std::env::var("HORIZON_REGION_Y")
168 .ok()
169 .and_then(|s| s.parse().ok())
170 .unwrap_or(0);
171 let z = std::env::var("HORIZON_REGION_Z")
172 .ok()
173 .and_then(|s| s.parse().ok())
174 .unwrap_or(0);
175 Self { x, y, z }
176 }
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
184pub struct RegionBounds {
185 pub min_x: f64,
187 pub max_x: f64,
189 pub min_y: f64,
191 pub max_y: f64,
193 pub min_z: f64,
195 pub max_z: f64,
197}
198
199impl Default for RegionBounds {
200 fn default() -> Self {
201 Self {
202 min_x: -1000.0,
203 max_x: 1000.0,
204 min_y: -1000.0,
205 max_y: 1000.0,
206 min_z: -1000.0,
207 max_z: 1000.0,
208 }
209 }
210}
211
212impl RegionBounds {
213 pub fn new(min_x: f64, max_x: f64, min_y: f64, max_y: f64, min_z: f64, max_z: f64) -> Self {
215 Self { min_x, max_x, min_y, max_y, min_z, max_z }
216 }
217
218 pub fn from_center(center: WorldCoordinate, half_extent: f64) -> Self {
220 Self {
221 min_x: center.x - half_extent,
222 max_x: center.x + half_extent,
223 min_y: center.y - half_extent,
224 max_y: center.y + half_extent,
225 min_z: center.z - half_extent,
226 max_z: center.z + half_extent,
227 }
228 }
229
230 pub fn center(&self) -> WorldCoordinate {
232 WorldCoordinate::new(
233 (self.min_x + self.max_x) / 2.0,
234 (self.min_y + self.max_y) / 2.0,
235 (self.min_z + self.max_z) / 2.0,
236 )
237 }
238
239 pub fn half_extent(&self) -> f64 {
241 (self.max_x - self.min_x) / 2.0
242 }
243
244 pub fn contains(&self, coord: &WorldCoordinate) -> bool {
246 coord.x >= self.min_x && coord.x <= self.max_x &&
247 coord.y >= self.min_y && coord.y <= self.max_y &&
248 coord.z >= self.min_z && coord.z <= self.max_z
249 }
250
251 pub fn distance_to_boundary(&self, coord: &WorldCoordinate) -> f64 {
254 let dx = (coord.x - self.min_x).min(self.max_x - coord.x);
255 let dy = (coord.y - self.min_y).min(self.max_y - coord.y);
256 let dz = (coord.z - self.min_z).min(self.max_z - coord.z);
257 dx.min(dy).min(dz)
258 }
259
260 pub fn overlaps(&self, other: &RegionBounds) -> bool {
262 self.min_x <= other.max_x && self.max_x >= other.min_x &&
263 self.min_y <= other.max_y && self.max_y >= other.min_y &&
264 self.min_z <= other.max_z && self.max_z >= other.min_z
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_world_coordinate_distance() {
274 let a = WorldCoordinate::new(0.0, 0.0, 0.0);
275 let b = WorldCoordinate::new(3.0, 4.0, 0.0);
276 assert!((a.distance_to(&b) - 5.0).abs() < 0.0001);
277 }
278
279 #[test]
280 fn test_region_bounds_contains() {
281 let bounds = RegionBounds::from_center(WorldCoordinate::zero(), 100.0);
282 assert!(bounds.contains(&WorldCoordinate::new(0.0, 0.0, 0.0)));
283 assert!(bounds.contains(&WorldCoordinate::new(99.0, 0.0, 0.0)));
284 assert!(!bounds.contains(&WorldCoordinate::new(101.0, 0.0, 0.0)));
285 }
286
287 #[test]
288 fn test_region_coordinate_conversion() {
289 let world = WorldCoordinate::new(150.0, 50.0, -25.0);
290 let region = RegionCoordinate::from_world_coordinate(&world, 100.0);
291 assert_eq!(region, RegionCoordinate::new(1, 0, -1));
292 }
293}