use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
pub struct WorldCoordinate {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl WorldCoordinate {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
pub fn zero() -> Self {
Self::new(0.0, 0.0, 0.0)
}
pub fn distance_to(&self, other: &WorldCoordinate) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
let dz = self.z - other.z;
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn vector_to(&self, other: &WorldCoordinate) -> WorldCoordinate {
WorldCoordinate {
x: other.x - self.x,
y: other.y - self.y,
z: other.z - self.z,
}
}
pub fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn normalized(&self) -> WorldCoordinate {
let mag = self.magnitude();
if mag == 0.0 {
WorldCoordinate::zero()
} else {
WorldCoordinate {
x: self.x / mag,
y: self.y / mag,
z: self.z / mag,
}
}
}
pub fn add(&self, other: &WorldCoordinate) -> WorldCoordinate {
WorldCoordinate {
x: self.x + other.x,
y: self.y + other.y,
z: self.z + other.z,
}
}
pub fn scale(&self, factor: f64) -> WorldCoordinate {
WorldCoordinate {
x: self.x * factor,
y: self.y * factor,
z: self.z * factor,
}
}
pub fn from_env() -> Self {
let x = std::env::var("HORIZON_CENTER_X")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0.0);
let y = std::env::var("HORIZON_CENTER_Y")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0.0);
let z = std::env::var("HORIZON_CENTER_Z")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0.0);
Self { x, y, z }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct RegionCoordinate {
pub x: i64,
pub y: i64,
pub z: i64,
}
impl RegionCoordinate {
pub fn new(x: i64, y: i64, z: i64) -> Self {
Self { x, y, z }
}
pub fn center() -> Self {
Self::new(0, 0, 0)
}
pub fn manhattan_distance(&self, other: &RegionCoordinate) -> i64 {
(self.x - other.x).abs() + (self.y - other.y).abs() + (self.z - other.z).abs()
}
pub fn adjacent_regions(&self) -> Vec<RegionCoordinate> {
vec![
RegionCoordinate::new(self.x + 1, self.y, self.z),
RegionCoordinate::new(self.x - 1, self.y, self.z),
RegionCoordinate::new(self.x, self.y + 1, self.z),
RegionCoordinate::new(self.x, self.y - 1, self.z),
RegionCoordinate::new(self.x, self.y, self.z + 1),
RegionCoordinate::new(self.x, self.y, self.z - 1),
]
}
pub fn to_world_center(&self, region_size: f64) -> WorldCoordinate {
WorldCoordinate::new(
self.x as f64 * region_size,
self.y as f64 * region_size,
self.z as f64 * region_size,
)
}
pub fn from_world_coordinate(coord: &WorldCoordinate, region_size: f64) -> Self {
Self {
x: (coord.x / region_size).floor() as i64,
y: (coord.y / region_size).floor() as i64,
z: (coord.z / region_size).floor() as i64,
}
}
pub fn from_env() -> Self {
let x = std::env::var("HORIZON_REGION_X")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let y = std::env::var("HORIZON_REGION_Y")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let z = std::env::var("HORIZON_REGION_Z")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
Self { x, y, z }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct RegionBounds {
pub min_x: f64,
pub max_x: f64,
pub min_y: f64,
pub max_y: f64,
pub min_z: f64,
pub max_z: f64,
}
impl Default for RegionBounds {
fn default() -> Self {
Self {
min_x: -1000.0,
max_x: 1000.0,
min_y: -1000.0,
max_y: 1000.0,
min_z: -1000.0,
max_z: 1000.0,
}
}
}
impl RegionBounds {
pub fn new(min_x: f64, max_x: f64, min_y: f64, max_y: f64, min_z: f64, max_z: f64) -> Self {
Self { min_x, max_x, min_y, max_y, min_z, max_z }
}
pub fn from_center(center: WorldCoordinate, half_extent: f64) -> Self {
Self {
min_x: center.x - half_extent,
max_x: center.x + half_extent,
min_y: center.y - half_extent,
max_y: center.y + half_extent,
min_z: center.z - half_extent,
max_z: center.z + half_extent,
}
}
pub fn center(&self) -> WorldCoordinate {
WorldCoordinate::new(
(self.min_x + self.max_x) / 2.0,
(self.min_y + self.max_y) / 2.0,
(self.min_z + self.max_z) / 2.0,
)
}
pub fn half_extent(&self) -> f64 {
(self.max_x - self.min_x) / 2.0
}
pub fn contains(&self, coord: &WorldCoordinate) -> bool {
coord.x >= self.min_x && coord.x <= self.max_x &&
coord.y >= self.min_y && coord.y <= self.max_y &&
coord.z >= self.min_z && coord.z <= self.max_z
}
pub fn distance_to_boundary(&self, coord: &WorldCoordinate) -> f64 {
let dx = (coord.x - self.min_x).min(self.max_x - coord.x);
let dy = (coord.y - self.min_y).min(self.max_y - coord.y);
let dz = (coord.z - self.min_z).min(self.max_z - coord.z);
dx.min(dy).min(dz)
}
pub fn overlaps(&self, other: &RegionBounds) -> bool {
self.min_x <= other.max_x && self.max_x >= other.min_x &&
self.min_y <= other.max_y && self.max_y >= other.min_y &&
self.min_z <= other.max_z && self.max_z >= other.min_z
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_world_coordinate_distance() {
let a = WorldCoordinate::new(0.0, 0.0, 0.0);
let b = WorldCoordinate::new(3.0, 4.0, 0.0);
assert!((a.distance_to(&b) - 5.0).abs() < 0.0001);
}
#[test]
fn test_region_bounds_contains() {
let bounds = RegionBounds::from_center(WorldCoordinate::zero(), 100.0);
assert!(bounds.contains(&WorldCoordinate::new(0.0, 0.0, 0.0)));
assert!(bounds.contains(&WorldCoordinate::new(99.0, 0.0, 0.0)));
assert!(!bounds.contains(&WorldCoordinate::new(101.0, 0.0, 0.0)));
}
#[test]
fn test_region_coordinate_conversion() {
let world = WorldCoordinate::new(150.0, 50.0, -25.0);
let region = RegionCoordinate::from_world_coordinate(&world, 100.0);
assert_eq!(region, RegionCoordinate::new(1, 0, -1));
}
}