use rstar::{AABB, PointDistance, RTreeObject};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ProjectedAxis {
X,
#[default]
Y,
Z,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SdePoint {
pub coords: [f64; 3],
pub id: Option<usize>,
pub name: Option<String>,
pub connections: Vec<(usize, usize)>,
pub color: Option<String>,
}
impl SdePoint {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self {
coords: [x, y, z],
id: None,
name: None,
connections: Vec::new(),
color: None,
}
}
pub fn x(&self) -> f64 {
self.coords[0]
}
pub fn y(&self) -> f64 {
self.coords[1]
}
pub fn z(&self) -> f64 {
self.coords[2]
}
pub fn to_2d(&self, axis: ProjectedAxis) -> [f32; 2] {
match axis {
ProjectedAxis::X => [self.coords[1] as f32, self.coords[2] as f32],
ProjectedAxis::Y => [self.coords[0] as f32, self.coords[2] as f32],
ProjectedAxis::Z => [self.coords[0] as f32, self.coords[1] as f32],
}
}
}
impl Default for SdePoint {
fn default() -> Self {
Self::new(0.0, 0.0, 0.0)
}
}
impl From<[i64; 3]> for SdePoint {
fn from(value: [i64; 3]) -> Self {
Self::new(value[0] as f64, value[1] as f64, value[2] as f64)
}
}
impl From<[f32; 3]> for SdePoint {
fn from(value: [f32; 3]) -> Self {
Self::new(value[0] as f64, value[1] as f64, value[2] as f64)
}
}
impl From<[f64; 3]> for SdePoint {
fn from(value: [f64; 3]) -> Self {
Self::new(value[0], value[1], value[2])
}
}
impl From<SdePoint> for [i64; 3] {
fn from(val: SdePoint) -> Self {
[
val.coords[0].round() as i64,
val.coords[1].round() as i64,
val.coords[2].round() as i64,
]
}
}
impl From<SdePoint> for [f64; 3] {
fn from(val: SdePoint) -> Self {
val.coords
}
}
impl DivAssign<f64> for SdePoint {
fn div_assign(&mut self, rhs: f64) {
self.coords[0] /= rhs;
self.coords[1] /= rhs;
self.coords[2] /= rhs;
}
}
impl MulAssign<f64> for SdePoint {
fn mul_assign(&mut self, rhs: f64) {
self.coords[0] *= rhs;
self.coords[1] *= rhs;
self.coords[2] *= rhs;
}
}
impl Mul<f64> for SdePoint {
type Output = Self;
fn mul(mut self, rhs: f64) -> Self::Output {
self.coords[0] *= rhs;
self.coords[1] *= rhs;
self.coords[2] *= rhs;
self
}
}
impl Div<f64> for SdePoint {
type Output = Self;
fn div(mut self, rhs: f64) -> Self::Output {
self.coords[0] /= rhs;
self.coords[1] /= rhs;
self.coords[2] /= rhs;
self
}
}
impl Add<SdePoint> for SdePoint {
type Output = SdePoint;
fn add(self, rhs: SdePoint) -> Self::Output {
SdePoint::new(
self.coords[0] + rhs.coords[0],
self.coords[1] + rhs.coords[1],
self.coords[2] + rhs.coords[2],
)
}
}
impl Sub<SdePoint> for SdePoint {
type Output = SdePoint;
fn sub(self, rhs: SdePoint) -> Self::Output {
SdePoint::new(
self.coords[0] - rhs.coords[0],
self.coords[1] - rhs.coords[1],
self.coords[2] - rhs.coords[2],
)
}
}
impl Add<&SdePoint> for SdePoint {
type Output = SdePoint;
fn add(self, rhs: &SdePoint) -> Self::Output {
SdePoint::new(
self.coords[0] + rhs.coords[0],
self.coords[1] + rhs.coords[1],
self.coords[2] + rhs.coords[2],
)
}
}
impl Sub<&SdePoint> for SdePoint {
type Output = SdePoint;
fn sub(self, rhs: &SdePoint) -> Self::Output {
SdePoint::new(
self.coords[0] - rhs.coords[0],
self.coords[1] - rhs.coords[1],
self.coords[2] - rhs.coords[2],
)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SdeSegment {
pub id: (usize, usize),
pub point1: [f64; 2],
pub point2: [f64; 2],
}
impl RTreeObject for SdeSegment {
type Envelope = AABB<[f64; 2]>;
fn envelope(&self) -> Self::Envelope {
AABB::from_corners(self.point1, self.point2)
}
}
impl PointDistance for SdeSegment {
fn distance_2(&self, point: &[f64; 2]) -> f64 {
rstar::primitives::Line::new(self.point1, self.point2).distance_2(point)
}
}
pub fn map_segments_to_vec(tree: &rstar::RTree<SdeSegment>) -> Vec<&SdeSegment> {
tree.iter().collect()
}
#[derive(PartialEq, Clone, Debug)]
pub struct EveRegionArea {
pub region_id: i64,
pub name: String,
pub min: SdePoint,
pub max: SdePoint,
}
impl Default for EveRegionArea {
fn default() -> Self {
Self::new()
}
}
impl EveRegionArea {
pub fn new() -> Self {
EveRegionArea {
region_id: 0,
name: String::new(),
min: SdePoint::default(),
max: SdePoint::default(),
}
}
}
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
pub struct Moon {
pub id: u32,
pub planet: u32,
pub index: u8,
pub solar_system: u32,
}
impl Moon {
pub fn new() -> Self {
Moon {
id: 0,
planet: 0,
index: 0,
solar_system: 0,
}
}
}
impl Default for Moon {
fn default() -> Self {
Self::new()
}
}
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
pub struct Planet {
pub id: u32,
pub solar_system: u32,
pub index: u8,
}
impl Planet {
pub fn new() -> Self {
Planet {
id: 0,
solar_system: 0,
index: 0,
}
}
}
impl Default for Planet {
fn default() -> Self {
Self::new()
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct Star {
pub id: u32,
pub locked: Option<bool>,
pub radius: Option<u32>,
pub spectral_class: String,
pub color: String,
}
#[derive(PartialEq, Clone, Debug)]
pub struct SolarSystem {
pub id: u32,
pub name: String,
pub region: u32,
pub constellation: u32,
pub planets: Vec<u32>,
pub connections: Vec<u32>,
pub real_coords: SdePoint,
pub projected_coords: SdePoint,
pub disallowed_anchor_categories: Vec<u32>,
pub disallowed_anchor_groups: Vec<u32>,
pub star: Option<Star>,
pub factor: f64,
}
impl SolarSystem {
pub fn new(factor: f64) -> Self {
SolarSystem {
id: 0,
name: String::new(),
region: 0,
constellation: 0,
planets: Vec::new(),
connections: Vec::new(),
real_coords: SdePoint::default(),
projected_coords: SdePoint::default(),
disallowed_anchor_categories: Vec::new(),
disallowed_anchor_groups: Vec::new(),
star: None,
factor,
}
}
}
impl Default for SolarSystem {
fn default() -> Self {
Self::new(1.0)
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct Constellation {
pub id: u32,
pub name: String,
pub region: u32,
pub solar_systems: Vec<u32>,
pub projected_coords: SdePoint,
}
impl Constellation {
pub fn new() -> Self {
Constellation {
id: 0,
name: String::new(),
region: 0,
solar_systems: Vec::new(),
projected_coords: SdePoint::default(),
}
}
}
impl Default for Constellation {
fn default() -> Self {
Self::new()
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct Region {
pub id: u32,
pub name: String,
pub constellations: Vec<u32>,
pub projected_coords: SdePoint,
}
impl Region {
pub fn new() -> Self {
Region {
id: 0,
name: String::new(),
constellations: Vec::new(),
projected_coords: SdePoint::default(),
}
}
}
impl Default for Region {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct Universe {
pub regions: HashMap<u32, Region>,
pub constellations: HashMap<u32, Constellation>,
pub solar_systems: HashMap<u32, SolarSystem>,
pub planets: HashMap<u32, Planet>,
pub moons: HashMap<u32, Moon>,
pub factor: f64,
}
impl Universe {
pub fn new(factor: f64) -> Universe {
Universe {
regions: HashMap::new(),
constellations: HashMap::new(),
solar_systems: HashMap::new(),
planets: HashMap::new(),
moons: HashMap::new(),
factor,
}
}
}
impl Default for Universe {
fn default() -> Self {
Self::new(1.0)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SdeFingerprint {
pub sde_build: Option<String>,
pub language: String,
pub force_isometric_position_2d: bool,
pub isometric_projected_axis: ProjectedAxis,
pub map_kspace: bool,
pub map_wspace: bool,
pub map_abyssal: bool,
pub map_void: bool,
pub with_gates: bool,
pub with_moons: bool,
pub with_third_party: bool,
pub with_icebelts: Option<bool>,
pub with_triglavian_status: Option<bool>,
pub with_jove_observatories: Option<bool>,
pub with_special_ore: Option<bool>,
}
impl SdeFingerprint {
fn to_hash_input(&self) -> String {
format!(
"{}|{}|{}|{:?}|{}|{}|{}|{}|{}|{}|{}|{:?}|{:?}|{:?}|{:?}",
self.sde_build.as_deref().unwrap_or(""),
self.language,
self.force_isometric_position_2d,
self.isometric_projected_axis,
self.map_kspace,
self.map_wspace,
self.map_abyssal,
self.map_void,
self.with_gates,
self.with_moons,
self.with_third_party,
self.with_icebelts,
self.with_triglavian_status,
self.with_jove_observatories,
self.with_special_ore,
)
}
pub fn hash(&self) -> String {
let digest = Sha256::digest(self.to_hash_input().as_bytes());
digest
.iter()
.map(|byte| format!("{:02x}", byte))
.collect::<String>()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sdepoint_new_sets_coordinates() {
let point = SdePoint::new(10.0, -20.0, 30.0);
assert_eq!(point.x(), 10.0);
assert_eq!(point.y(), -20.0);
assert_eq!(point.z(), 30.0);
assert_eq!(point.id, None);
assert_eq!(point.name, None);
assert!(point.connections.is_empty());
}
#[test]
fn sdepoint_default_is_origin() {
let point = SdePoint::default();
assert_eq!(point.coords, [0.0, 0.0, 0.0]);
assert_eq!(point, SdePoint::new(0.0, 0.0, 0.0));
}
#[test]
fn sdepoint_from_i64_array() {
let point = SdePoint::from([1i64, 2, 3]);
assert_eq!(point, SdePoint::new(1.0, 2.0, 3.0));
}
#[test]
fn sdepoint_from_f32_array() {
let point = SdePoint::from([1.4f32, 1.5, -1.5]);
assert_eq!(point, SdePoint::new(1.4f32 as f64, 1.5, -1.5));
}
#[test]
fn sdepoint_from_f64_array() {
let point = SdePoint::from([1.4, 1.5, -1.5]);
assert_eq!(point, SdePoint::new(1.4, 1.5, -1.5));
}
#[test]
fn sdepoint_into_i64_array_rounds() {
let values: [i64; 3] = SdePoint::new(7.4, 7.5, -7.5).into();
assert_eq!(values, [7, 8, -8]);
}
#[test]
fn sdepoint_into_f64_array() {
let values: [f64; 3] = SdePoint::new(7.0, 8.0, 9.0).into();
assert_eq!(values, [7.0, 8.0, 9.0]);
}
#[test]
fn sdepoint_to_2d_drops_x() {
let point = SdePoint::new(10.0, 20.0, 30.0);
assert_eq!(point.to_2d(ProjectedAxis::X), [20.0, 30.0]);
}
#[test]
fn sdepoint_to_2d_drops_y() {
let point = SdePoint::new(10.0, 20.0, 30.0);
assert_eq!(point.to_2d(ProjectedAxis::Y), [10.0, 30.0]);
}
#[test]
fn sdepoint_to_2d_drops_z() {
let point = SdePoint::new(10.0, 20.0, 30.0);
assert_eq!(point.to_2d(ProjectedAxis::Z), [10.0, 20.0]);
}
#[test]
fn sdepoint_to_2d_never_fails_even_at_the_pivot_values_that_used_to_panic() {
let point = SdePoint::new(
1_003_094_336_444_825.0,
-2_005_029_375_317_114.0,
3_001_839_229_715_087.0,
);
let _ = point.to_2d(ProjectedAxis::Y); }
#[test]
fn sdepoint_add_owned() {
let sum = SdePoint::new(1.0, 2.0, 3.0) + SdePoint::new(10.0, 20.0, 30.0);
assert_eq!(sum, SdePoint::new(11.0, 22.0, 33.0));
}
#[test]
fn sdepoint_add_reference() {
let sum = SdePoint::new(1.0, 2.0, 3.0) + &SdePoint::new(-1.0, -2.0, -3.0);
assert_eq!(sum, SdePoint::new(0.0, 0.0, 0.0));
}
#[test]
fn sdepoint_sub_owned() {
let diff = SdePoint::new(10.0, 20.0, 30.0) - SdePoint::new(1.0, 2.0, 3.0);
assert_eq!(diff, SdePoint::new(9.0, 18.0, 27.0));
}
#[test]
fn sdepoint_sub_reference() {
let diff = SdePoint::new(10.0, 20.0, 30.0) - &SdePoint::new(10.0, 20.0, 30.0);
assert_eq!(diff, SdePoint::new(0.0, 0.0, 0.0));
}
#[test]
fn sdepoint_mul_assign_i64() {
let mut point = SdePoint::new(1.0, 2.0, 3.0);
point *= 3.0;
assert_eq!(point, SdePoint::new(3.0, 6.0, 9.0));
}
#[test]
fn sdepoint_div_assign_i64() {
let mut point = SdePoint::new(24.0, 48.0, 96.0);
point /= 2.0;
assert_eq!(point, SdePoint::new(12.0, 24.0, 48.0));
}
#[test]
fn sdepoint_mul_f64() {
let product = SdePoint::new(1.0, -2.0, 3.0) * 2.5;
assert_eq!(product, SdePoint::new(2.5, -5.0, 7.5));
}
#[test]
fn sdepoint_div_f64() {
let quotient = SdePoint::new(10.0, -20.0, 30.0) / 4.0;
assert_eq!(quotient, SdePoint::new(2.5, -5.0, 7.5));
}
#[test]
fn everegionarea_new_is_empty() {
let area = EveRegionArea::new();
assert_eq!(area.region_id, 0);
assert_eq!(area.name, String::new());
assert_eq!(area.min, SdePoint::default());
assert_eq!(area.max, SdePoint::default());
assert_eq!(area, EveRegionArea::default());
}
#[test]
fn moon_new_is_zeroed() {
let moon = Moon::new();
assert_eq!(moon.id, 0);
assert_eq!(moon.planet, 0);
assert_eq!(moon.index, 0);
assert_eq!(moon.solar_system, 0);
assert_eq!(moon, Moon::default());
}
#[test]
fn planet_new_is_zeroed() {
let planet = Planet::new();
assert_eq!(planet.id, 0);
assert_eq!(planet.solar_system, 0);
assert_eq!(planet.index, 0);
assert_eq!(planet, Planet::default());
}
#[test]
fn solarsystem_new_initializes_with_factor() {
let system = SolarSystem::new(1000.0);
assert_eq!(system.id, 0);
assert_eq!(system.name, String::new());
assert_eq!(system.region, 0);
assert_eq!(system.constellation, 0);
assert!(system.planets.is_empty());
assert!(system.connections.is_empty());
assert_eq!(system.real_coords, SdePoint::default());
assert_eq!(system.projected_coords, SdePoint::default());
assert_eq!(system.factor, 1000.0);
}
#[test]
fn solarsystem_default_factor_is_one() {
assert_eq!(SolarSystem::default().factor, 1.0);
}
#[test]
fn constellation_new_is_empty() {
let constellation = Constellation::new();
assert_eq!(constellation.id, 0);
assert_eq!(constellation.name, String::new());
assert_eq!(constellation.region, 0);
assert!(constellation.solar_systems.is_empty());
assert_eq!(constellation.projected_coords, SdePoint::default());
assert_eq!(constellation, Constellation::default());
}
#[test]
fn region_new_is_empty() {
let region = Region::new();
assert_eq!(region.id, 0);
assert_eq!(region.name, String::new());
assert!(region.constellations.is_empty());
assert_eq!(region.projected_coords, SdePoint::default());
assert_eq!(region, Region::default());
}
#[test]
fn universe_new_initializes_with_factor() {
let universe = Universe::new(42.0);
assert!(universe.regions.is_empty());
assert!(universe.constellations.is_empty());
assert!(universe.solar_systems.is_empty());
assert!(universe.planets.is_empty());
assert!(universe.moons.is_empty());
assert_eq!(universe.factor, 42.0);
}
#[test]
fn universe_default_factor_is_one() {
assert_eq!(Universe::default().factor, 1.0);
}
fn sample_fingerprint() -> SdeFingerprint {
SdeFingerprint {
sde_build: Some("3458726".to_string()),
language: "en".to_string(),
force_isometric_position_2d: false,
isometric_projected_axis: ProjectedAxis::Y,
map_kspace: true,
map_wspace: true,
map_abyssal: true,
map_void: false,
with_gates: true,
with_moons: true,
with_third_party: false,
with_icebelts: None,
with_triglavian_status: None,
with_jove_observatories: None,
with_special_ore: None,
}
}
#[test]
fn fingerprint_hash_is_deterministic() {
assert_eq!(sample_fingerprint().hash(), sample_fingerprint().hash());
}
#[test]
fn fingerprint_hash_is_a_64_char_hex_string() {
let hash = sample_fingerprint().hash();
assert_eq!(hash.len(), 64);
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn fingerprint_hash_changes_when_a_single_flag_changes() {
let base = sample_fingerprint();
let mut changed = sample_fingerprint();
changed.with_moons = false;
assert_ne!(base.hash(), changed.hash());
}
#[test]
fn fingerprint_hash_changes_when_sde_build_changes() {
let base = sample_fingerprint();
let mut changed = sample_fingerprint();
changed.sde_build = Some("9999999".to_string());
assert_ne!(base.hash(), changed.hash());
}
#[test]
fn fingerprint_hash_distinguishes_none_from_explicit_values() {
let mut none_case = sample_fingerprint();
none_case.with_third_party = true;
let mut false_case = sample_fingerprint();
false_case.with_third_party = true;
false_case.with_icebelts = Some(false);
false_case.with_triglavian_status = Some(false);
false_case.with_jove_observatories = Some(false);
false_case.with_special_ore = Some(false);
assert_ne!(none_case.hash(), false_case.hash());
}
}