diff --git a/Cargo.lock b/Cargo.lock index bd275f8..4e408ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,7 @@ name = "edalyze" version = "0.1.0" dependencies = [ "anyhow", + "byteorder", "rstar", "simd-json", "structopt", diff --git a/Cargo.toml b/Cargo.toml index 7cb9d98..fb6bb5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ structopt = "0.3" simd-json = "0.10" rstar = "0.11" zstd = "0.11" -anyhow = "1" \ No newline at end of file +anyhow = "1" +byteorder = "1" diff --git a/src/bin/regionfilter.rs b/src/bin/regionfilter.rs index f1917a9..c944c6c 100644 --- a/src/bin/regionfilter.rs +++ b/src/bin/regionfilter.rs @@ -3,18 +3,12 @@ use anyhow::anyhow; use simd_json::Node; use edalyze::tapetool::{TapeCursor, TapeObject, Value}; +use edalyze::bindata::{System, Point, Box}; -#[derive(Copy, Clone, Debug, Default)] -pub struct Point(i32, i32, i32); -#[derive(Copy, Clone, Debug)] -pub struct Box(Point, Point); - -const IS_FUEL: u8 = 1; -const IS_WDRF: u8 = 2; -const IS_NEUT: u8 = 4; fn star_typ(sub_type: &str) -> u8 { + use edalyze::bindata::{IS_FUEL, IS_WDRF, IS_NEUT}; if sub_type.starts_with("Wh") { return IS_WDRF; } else if sub_type.starts_with("N") { @@ -29,38 +23,6 @@ fn star_typ(sub_type: &str) -> u8 { } } -const fn isortp(a: i32, b: i32) -> (i32, i32) { - if a < b { (a,b) } else { (b,a) } -} - -impl Box { - pub const fn from_corners(x: Point, y: Point) -> Box { - let (ax, bx) = isortp(x.0, y.0); - let (ay, by) = isortp(x.1, y.1); - let (az, bz) = isortp(x.2, y.2); - - let pt1 = Point(ax, ay, az); - let pt2 = Point(bx, by, bz); - - Box(pt1, pt2) - } - - pub fn contains(&self, x: Point) -> bool { - return - (self.0 .0 .. self.1 .0).contains(&x.0) && - (self.0 .1 .. self.1 .1).contains(&x.1) && - (self.0 .2 .. self.1 .2).contains(&x.2); - } - -} - -#[derive(Default)] -struct System { - coords: Point, - name: String, - stars: u8, -} - fn parse_coords(c: TapeObject) -> Result { let mut pt = Point::default(); @@ -113,7 +75,7 @@ fn parse_system<'a>(tape: &'a [Node<'a>]) -> Result { "name" => system.name = v.as_str().map(str::to_owned).ok_or("name must be str")?, "coords" => system.coords = parse_coords(v.as_object().ok_or("coords must be object")?)?, "bodies" => { - system.stars = v.as_array().ok_or("bodies should be array")? + system.star_flags = v.as_array().ok_or("bodies should be array")? .filter_map(parse_body) .map(|x| x.1) .fold(0, |x,y| x|y) @@ -149,28 +111,37 @@ static FILENAME: &str = "/space/data/ed-utils/spansh/galaxy.json.zst"; fn main() -> anyhow::Result<()> { let f = edalyze::ioutil::Processor::open_file(FILENAME, processor)?; + // Small region + #[cfg(ignore)] let region = Box::from_corners( Point(11900*32, -3800*32, -11510*32), Point(17400*32, 3800*32, -6500*32), ); + // Everything southeast of Haffner 18 LSS 27 + let region = Box::from_corners( + Point(11900*32, -3800*32, -18510*32), + Point(64400*32, 3800*32, -6500*32), + ); let mut reg_name = std::io::BufWriter::new(std::fs::File::create("region.nam")?); let mut reg_data = std::io::BufWriter::new(std::fs::File::create("region.dat")?); let mut name_pos = 0; - for sys in f.filter_map(id).filter(|sys| region.contains(sys.coords)) { + for mut sys in f.filter_map(id).filter(|sys| region.contains(sys.coords)) { + sys.name_off = name_pos as usize; let hdr = [sys.name.len().min(255) as u8]; if hdr[0] == 255 { eprintln!("WARNING: Long system name {}", sys.name); } reg_name.write(&hdr[..])?; reg_name.write(&sys.name.as_bytes()[..hdr[0] as usize])?; + name_pos += hdr[0] as u32 + 1; + reg_data.write(&sys.coords.0 .to_be_bytes()[..])?; reg_data.write(&sys.coords.1 .to_be_bytes()[..])?; reg_data.write(&sys.coords.2 .to_be_bytes()[..])?; - let rest = (sys.stars as u32) + (name_pos << 4); - name_pos += hdr[0] as u32 + 1; + let rest = (sys.star_flags as u32) + (name_pos << 4); reg_data.write(&rest.to_be_bytes()[..])?; } diff --git a/src/bin/routefinder.rs b/src/bin/routefinder.rs new file mode 100644 index 0000000..df21654 --- /dev/null +++ b/src/bin/routefinder.rs @@ -0,0 +1,197 @@ + +use std::{io::Cursor, cmp::{Ordering, Reverse}, collections::{VecDeque, BinaryHeap}}; + +use edalyze::bindata::*; +use rstar::primitives::{PointWithData, GeomWithData}; + +struct IndexData { + cost: f32, + reachable: bool, + last: usize, + sys_data: System, +} + +impl From for IndexData { + fn from(sys: System) -> Self { + Self { + cost: f32::INFINITY, + reachable: false, + last: 0, + sys_data: sys, + } + } +} + +struct SearchNode { + id: usize, + cost: f32, + heuristic: f32, +} + +trait SearchState { + fn cost(&self) -> f32; + fn heuristic(&self) -> f32; +} + +// ordering tuned for BinaryHeap +impl Ord for SearchNode { + fn cmp(&self, other: &Self) -> Ordering { + f32::total_cmp(&(self.cost + self.heuristic), &(other.cost + other.heuristic)) + } +} + +impl PartialOrd for SearchNode { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for SearchNode { + fn eq(&self, other: &Self) -> bool { + (self.cost + self.heuristic) == (other.cost + other.heuristic) + } +} +impl Eq for SearchNode {} + +trait SearchQueue { + fn q_next(&mut self) -> Option; + fn q_empty(&self) -> bool; + fn q_push(&mut self, item: T); + fn q_clear(&mut self); +} + +struct SearchEnv { + rst: rstar::RTree>, + meta: Vec, + base_jump: f32, + init_system: usize, + target_system: usize, + jumponium_cost_factor: f32, + queue: Q, +} + +impl> SearchEnv { + fn reset(&mut self) { + for item in self.meta.iter_mut() { + item.cost = f32::INFINITY; + item.reachable = false; + item.last = 0; + } + self.queue.q_clear(); + let init_node = SearchNode{ + id: self.init_system, + cost: 0, + heuristic: self.heuristic(self.init_system), + }; + self.queue.q_push(init_node); + } + + fn is_done(&self) -> bool { + self.queue.q_empty() || self.meta[self.target_system].reachable + } + fn search(&mut self) { + // + } + + fn heuristic(&self, sys_id: usize) -> f32 { + let tcoord = self.meta[self.target_system].sys_data.coords; + let scoord = self.meta[sys_id].sys_data.coords; + let dist = tcoord.distance(scoord); + (dist - self.jdist(sys_id)) / (4. * self.base_jump) + } + + fn visit_star(&self, sys_id: usize, base_cost: f32) { + let star_jump = self.jdist(sys_id); + let star_jump_sq = self.boosted_jump * self.boosted_jump; + let base_jump_sq = self.base_jump * self.base_jump; + let augm_jump_sq = base_jump_sq * 4.; + + let max_jump_sq = augm_jump_sq.max(star_jump_sq); + + let cur_pos = self.meta[sys_id].sys_data.coords; + for n_star in self.rst.locate_within_distance(cur_pos, (max_jump_sq * 1024.).ceil() as i32) { + } + } + + fn jdist(&self, sys_id: usize) -> f32 { + self.base_jump * self.meta[sys_id].sys_data.jump_scale() + } +} + +// BFS +struct BFS(VecDeque); +impl SearchQueue for BFS { + fn q_next(&mut self) -> Option { self.0.pop_front() } + fn q_empty(&self) -> bool { self.0.is_empty() } + fn q_push(&mut self, item: T) { self.0.push_back(item); } + fn q_clear(&mut self) { self.0.clear(); } +} +impl Default for BFS { + fn default() -> Self { BFS(VecDeque::new()) } +} + +struct DFS(Vec); +impl SearchQueue for DFS { + fn q_next(&mut self) -> Option { self.0.pop() } + fn q_empty(&self) -> bool { self.0.is_empty() } + fn q_push(&mut self, item: T) { self.0.push(item); } + fn q_clear(&mut self) { self.0.clear(); } +} +impl Default for DFS { + fn default() -> Self { DFS(Vec::new()) } +} + +struct AStar(BinaryHeap>); +impl SearchQueue for AStar { + fn q_next(&mut self) -> Option { self.0.pop().map(|x| x.0) } + fn q_empty(&self) -> bool { self.0.is_empty() } + fn q_push(&mut self, item: T) { self.0.push(Reverse(item)); } + fn q_clear(&mut self) { self.0.clear(); } +} +impl Default for AStar { + fn default() -> Self { AStar(BinaryHeap::new()) } +} + + + +fn main() -> anyhow::Result<()> { + let mut reg_dat = Cursor::new(std::fs::read("region.dat")?); + let mut reg_nam = Cursor::new(std::fs::read("region.nam")?); + + let mut systems = Vec::with_capacity(reg_dat.get_ref().len() / 16); + while let Ok(mut system) = System::read_from(&mut reg_dat) { + system.read_name(&mut reg_nam).ok(); + systems.push(IndexData::from(system)); + } + + + let rst = rstar::RTree::bulk_load( + systems.iter() + .enumerate() + .map(|(i,md)| GeomWithData::new(md.sys_data.coords, i)) + .collect() + ); + + eprintln!("Done reading {} systems", systems.len()); + let init_system = systems.iter().enumerate() + .find(|(_,sys)| sys.sys_data.name == "Haffner 18 LSS 27") + .expect("init system should exist") + .0; + let target_system = systems.iter().enumerate() + .find(|(_,sys)| sys.sys_data.name == "Angosk OM-W d1-0") + .expect("init system should exist") + .0; + + let mut env = SearchEnv { + rst,meta: systems,base_jump: 10., init_system, target_system, + queue: BFS::default() + }; + + env.queue.q_push(SearchNode{ + heuristic: 0., cost: 0., id: 0 + }); + + + + Ok(()) +} diff --git a/src/bindata.rs b/src/bindata.rs new file mode 100644 index 0000000..8ecf028 --- /dev/null +++ b/src/bindata.rs @@ -0,0 +1,146 @@ +use std::io::{Read, Write, self, Seek}; + +use byteorder::{WriteBytesExt, BE, ReadBytesExt}; + +pub const IS_FUEL: u8 = 1; +pub const IS_WDRF: u8 = 2; +pub const IS_NEUT: u8 = 4; + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct Point(pub i32, pub i32, pub i32); +#[derive(Copy, Clone, Debug)] +pub struct Box(pub Point, pub Point); + +const fn isortp(a: i32, b: i32) -> (i32, i32) { + if a < b { (a,b) } else { (b,a) } +} + +impl Box { + pub const fn from_corners(x: Point, y: Point) -> Box { + let (ax, bx) = isortp(x.0, y.0); + let (ay, by) = isortp(x.1, y.1); + let (az, bz) = isortp(x.2, y.2); + + let pt1 = Point(ax, ay, az); + let pt2 = Point(bx, by, bz); + + Box(pt1, pt2) + } + + pub fn contains(&self, x: Point) -> bool { + return + (self.0 .0 .. self.1 .0).contains(&x.0) && + (self.0 .1 .. self.1 .1).contains(&x.1) && + (self.0 .2 .. self.1 .2).contains(&x.2); + } + +} + +#[derive(Default)] +pub struct System { + pub coords: Point, + pub name: String, + pub name_off: usize, + pub star_flags: u8, +} + +impl System { + pub fn write_name(&self, writer: &mut dyn Write) -> io::Result { + let name_len = self.name.len().min(255); + writer.write_u8(name_len as u8)?; + writer.write(&self.name.as_bytes()[..name_len])?; + + return Ok(name_len + 1); + } + pub fn write_to(&self, writer: &mut dyn Write) -> io::Result<()> { + writer.write_i32::(self.coords.0)?; + writer.write_i32::(self.coords.1)?; + writer.write_i32::(self.coords.2)?; + let rest = self.star_flags as u32 + ((self.name_off as u32) << 4); + writer.write_u32::(rest)?; + Ok(()) + } + + pub fn read_from(r: &mut dyn Read) -> io::Result { + let x = r.read_i32::()?; + let y = r.read_i32::()?; + let z = r.read_i32::()?; + let rest = r.read_u32::()?; + let name_off = (rest >> 4) as usize; + let star_flags = (rest & 0xF) as u8; + Ok(System{ + coords: Point(x,y,z), + name: String::new(), + name_off, + star_flags, + }) + } + + pub fn read_name(&mut self, r: &mut R) -> io::Result<()> { + //eprint!("."); + r.seek(io::SeekFrom::Start(self.name_off as u64))?; + let mut buf = vec![]; + let len = r.read_u8()? as usize; + //eprintln!("Reading {len} bytes from {}", self.name_off); + buf.resize(len, 0); + r.read_exact(buf.as_mut_slice())?; + self.name = String::from_utf8(buf) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + + Ok(()) + } + + pub fn jump_scale(&self) -> f32 { + if self.star_flags & IS_NEUT != 0 { + 4.0 + } else if self.star_flags & IS_WDRF != 0 { + 1.5 + } else { + 0. + } + } +} + +impl rstar::Point for Point { + type Scalar = i32; + const DIMENSIONS: usize = 3; + fn generate(mut gen: impl FnMut(usize) -> Self::Scalar) -> Self { + let x = gen(0); + let y = gen(1); + let z = gen(2); + Self(x,y,z) + } + + fn nth(&self, index: usize) -> Self::Scalar { + match index { + 0 => self.0, + 1 => self.1, + 2 => self.2, + _ => 0 as Self::Scalar, + } + } + + fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { + match index { + 0 => &mut self.0, + 1 => &mut self.1, + _ => &mut self.2, + } + } +} + + +impl Point { + pub fn distance_sq(&self, other: Self) -> f32 { + let Point(sx, sy, sz) = *self; + let Point(tx, ty, tz) = other; + let dx = sx as f32 - tx as f32; + let dy = sy as f32 - ty as f32; + let dz = sz as f32 - tz as f32; + (dx*dx) + (dy*dy) + (dz*dz) + } + + pub fn distance(&self, other: Self) -> f32 { + self.distance_sq(other).sqrt() + } +} diff --git a/src/lib.rs b/src/lib.rs index 99d0de5..27c4789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod tapetool; pub mod ioutil; +pub mod bindata;