use serde::{ Serialize, Deserialize }; use std::collections::{ HashMap, HashSet }; #[derive(Serialize, Deserialize, Debug, Default, Clone)] #[serde(default)] pub struct Node { pub x: f32, pub y: f32, pub name: String, pub edges: HashSet, pub labels: HashMap, } #[derive(Serialize, Deserialize, Debug, Default)] #[serde(default)] pub struct Board { pub labels: HashMap>, pub nodes: HashMap, } impl Board { pub fn new() -> Self { let nodes = HashMap::new(); let labels = HashMap::new(); Board { nodes, labels } } pub fn add_node(&mut self, x: f32, y: f32, name: String) { self.nodes.insert(self.next_id(), Node { x, y, name, edges: HashSet::new(), labels: HashMap::new(), }); } pub fn remove_node(&mut self, id: usize) { // We remove this node from the graph, then drop it from each // other nodes edge. self.nodes.remove(&id); for node in self.nodes.values_mut() { node.edges.remove(&id); } } pub fn add_edge(&mut self, from: usize, to: usize) { let node = self.nodes.get_mut(&from).expect("Could not find node"); node.edges.insert(to); } pub fn nearest_node(&self, x: f32, y: f32) -> Option { let f = |n: &Node| dist_sq((n.x, n.y), (x, y)); let mut iter = self.nodes.iter(); if let Some((id, node)) = iter.next() { let mut min = *id; let mut min_val = f(node); for (id, node) in iter { let val = f(node); if val < min_val { min = *id; min_val = val; } } Some(min) } else { None } } fn next_id(&self) -> usize { for i in 0 .. { if !self.nodes.contains_key(&i) { return i; } } unreachable!(); } } fn dist_sq((x0, y0): (f32, f32), (x1, y1): (f32, f32)) -> f32 { (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0) } use std::io; use std::io::{ Write, Read, Cursor }; use std::fs::File; use std::path::Path; use image::io::Reader as ImageReader; use image::{ DynamicImage }; pub fn write_board_to_file(board: &Board, image: Option<&DynamicImage>, path: &Path) -> io::Result<()> { let file = File::create(path)?; let mut ar = zip::ZipWriter::new(file); let options = zip::write::FileOptions::default(); ar.start_file("graph.json", options)?; ar.write_all(&serde_json::to_vec(board)?)?; if let Some(image) = image { ar.start_file("image.png", options)?; let data = encode_png(image); ar.write_all(&data)?; ar.flush()?; } ar.finish()?; Ok(()) } pub fn read_board_from_file(path: &Path) -> io::Result<(Board, Option)> { let file = File::open(path)?; let mut ar = zip::ZipArchive::new(file)?; let json_file = ar.by_name("graph.json")?; let board = serde_json::from_reader(json_file)?; let image = ar.by_name("image.png").ok(); if image.is_none() { return Ok((board, None)) } let mut image_file = image.unwrap(); let mut buf = Vec::new(); image_file.read_to_end(&mut buf)?; let image = decode_png(&buf); Ok((board, Some(image))) } pub fn encode_png(image: &DynamicImage) -> Vec { let mut cursor = Cursor::new(Vec::new()); image.write_to(&mut cursor, image::ImageOutputFormat::Png).unwrap(); cursor.into_inner() } pub fn decode_png(buf: &[u8]) -> DynamicImage { let cursor = Cursor::new(buf); let mut reader = ImageReader::new(cursor); reader.set_format(image::ImageFormat::Png); reader.decode().unwrap() } fn lerp(v0: f32, v1: f32, t: f32) -> f32 { v0 + t * (v1 - v0) } fn inv_lerp(v0: f32, v1: f32, a: f32) -> f32 { (a - v0) / (v1 - v0) } pub trait CoordTransformer + From<(f32, f32)>> { fn origin(&self) -> I; fn extremes(&self) -> I; fn xform(&self, pos: I) -> (f32, f32) { let (sx, sy) = self.origin().into(); let (ex, ey) = self.extremes().into(); let (x, y) = pos.into(); ( inv_lerp(sx, ex, x), inv_lerp(sy, ey, y), ) } fn inv_xform(&self, x: f32, y: f32) -> I { let (sx, sy) = self.origin().into(); let (ex, ey) = self.extremes().into(); (lerp(sx, ex, x), lerp(sy, ey, y)).into() } }