From 5165708a03b3654e19e9af54c5d214f06fee8157 Mon Sep 17 00:00:00 2001 From: Dane Johnson <daneallenjohnson@protonmail.com> Date: Sun, 8 May 2022 14:39:30 -0500 Subject: [PATCH] Transformible coordinates are a board trait --- board.zip | Bin 511953 -> 511975 bytes src/board.rs | 304 +++++++++++++++++++++++++++++---------------------- src/main.rs | 34 +----- 3 files changed, 175 insertions(+), 163 deletions(-) diff --git a/board.zip b/board.zip index 18dead62f50f3b18c5a75650e14e7a234a3edbc8..342d503d2fcced620c98fab29c4207b28442da15 100644 GIT binary patch delta 537 zcmcaOU;g=g`T78FW)=|!1_lm>YjakF$X(o_QpL!?aE6(IfeR>_UX)mnp_f&hpI3Xy z-~W(-1lx!8o4)Ls@h^Y7wA5o?;foatGF_{8SnOJIMPpKJ`R<)tQ&}VxB^TPy`+m2W zrT5m0H2zb{6<2QWGF=eU`1{(f?>QSUZ>tMcU_brMcxQaMr9pk}(st(!|9`Nl?r940 z(yHX+`z!m1!;rD_|H@mL0Zp!pzscJ!Tb)~9(t54(&*yWqO#=RXDCiYSWN~z=+z|KW zUS{^D^kD1QRPW_;)Rk?gyG}3^+u$&#&*+&;K_}CnggKi^X9t+1Th6bx@$_O{=HVlk z%JWly(n|ANR;5hyc;k-LR|Gk{Fm9`06<^bFXYDWPKW>ttOILj`e0nGK;?l!M-KHN> zRI@JD*PU`MQzpYpLTiakY9GtziOG*PdgUiiR-16famt#IAH0*4L^3NngFl`&SE#;p z?b^Dn?InW65o|LOd?v`(HVIB|bt&X556ix+5xi9O?0E+f?S9s$GVKprml*BywXmz% zef8K?x4*%@LXUs82mP?}o&Cl0clm{NYk#er-5qlMFB3{6HP2*hpUDWqOhC*G#4JF} z3dC&NXEL(yKg){}lhaervun$PgalHnt7o2NXZU%Joq-b=xiGM#anbbk=h<c0lFqXO F#Q<jl=ZydW delta 516 zcmaDpU;g5J`T78FW)=|!1_llW{=JJs?)HE1OJQVSsAFbe-~x)K7bO;C=w%h>=ha?{ z^=~#1VSBKD)0bU_x__Vf^?vjdUj8xZrmK3XP^9NBskz+$?>1d1)8T4iagsk*S>3nb zqV(lAj+HF@Y0=xorf3&jxB9<r#~sP={TU1gH~l>^@476@w)(YaHgJ61t{<?7TV?5z zNzMf?<{KQJ!Rc~nc~!atr=IlQ`uB#xxxbfc?`eDaJk}s{$)QINWpod5xV3!Bu${fv z{G8sroVUJPBdXpq&wQ~{XHkp@ljfP18sc*}^a@+vuf4u5&$!E(>*gKhi^?5_M^{SN z`U$>X&k<+&H`e_Blw+Hh)vwghn75+IsO@mK&Vk;-e=3e1&*yl}^=b^rFfUuRBSEXs z{FnLR?A+3+tA&g-p6Pk($@-b?T6lY(Pua77Tg}r|#V0OnNmPA0MO92^${a1Z<)2Dr zPI0Of3#3o%nxGI^sMhX0r)z%V46*)WJ8x}&H{oEq;)yM9T^$QDYMTtFdp(j7IkeXJ znAxo`OZIf(`=$0M(b2q~v3)%w2r~gOGZ3=?F)I+WZC}sGzW*#QN}NrvKF_Yr3>2Ea n@;tk=at|;bGjkKuQ}qh+(gVC%*?{rMzyyT(K-%X#JCFnb4|3Oj diff --git a/src/board.rs b/src/board.rs index 76f1830..74ca986 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,133 +1,171 @@ -use serde::{ Serialize, Deserialize }; -use std::collections::{ HashMap, HashSet }; - -#[derive(Serialize, Deserialize, Debug, Default)] -#[serde(default)] -pub struct Node { - pub x: f32, - pub y: f32, - - pub name: String, - pub edges: HashSet<usize>, - pub labels: HashMap<String, String>, -} -#[derive(Serialize, Deserialize, Debug, Default)] -#[serde(default)] -pub struct Board { - pub labels: HashMap<String, Vec<String>>, - pub nodes: HashMap<usize, Node>, -} -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 nearest_node(&self, x: f32, y: f32) -> Option<usize> { - 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<DynamicImage>)> { - 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<u8> { - 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() -} - +use serde::{ Serialize, Deserialize }; +use std::collections::{ HashMap, HashSet }; + +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(default)] +pub struct Node { + pub x: f32, + pub y: f32, + + pub name: String, + pub edges: HashSet<usize>, + pub labels: HashMap<String, String>, +} +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(default)] +pub struct Board { + pub labels: HashMap<String, Vec<String>>, + pub nodes: HashMap<usize, Node>, +} +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 nearest_node(&self, x: f32, y: f32) -> Option<usize> { + 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<DynamicImage>)> { + 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<u8> { + 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<I: CoordtypeConvertible> { + fn origin(&self) -> (I, I); + fn extremes(&self) -> (I, I); + fn to_coords(&self, x: I, y: I) -> (f32, f32) { + let (sx, sy) = self.origin(); + let (ex, ey) = self.extremes(); + ( + inv_lerp(sx.to_coordtype(), ex.to_coordtype(), x.to_coordtype()), + inv_lerp(sy.to_coordtype(), ey.to_coordtype(), y.to_coordtype()), + ) + } + fn from_coords(&self, x: f32, y: f32) -> (I, I) { + let (sx, sy) = self.origin(); + let (ex, ey) = self.extremes(); + ( + CoordtypeConvertible::from_coordtype(lerp(sx.to_coordtype(), ex.to_coordtype(), x)), + CoordtypeConvertible::from_coordtype(lerp(sy.to_coordtype(), ey.to_coordtype(), y)), + ) + } +} + +pub trait CoordtypeConvertible { + fn to_coordtype(self) -> f32; + fn from_coordtype(coordtype: f32) -> Self; +} + +impl CoordtypeConvertible for i32 { + fn to_coordtype(self) -> f32 { self as f32 } + fn from_coordtype(coordtype: f32) -> i32 { coordtype as i32 } +} diff --git a/src/main.rs b/src/main.rs index 1de7d61..9b51d45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use std::sync::Mutex; mod board; use board::Board; -use board::{ encode_png, write_board_to_file, read_board_from_file }; +use board::{ encode_png, write_board_to_file, read_board_from_file, CoordTransformer }; //////////////////// Global State //////////////////// // Don't @ me... @@ -133,35 +133,9 @@ mod dispatch { } } -//////////////////// Utility Functions //////////////////// - -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) -} - -trait CoordTransformer { - fn to_coords(&self, x: i32, y: i32) -> (f32, f32); - fn from_coords(&self, x: f32, y: f32) -> (i32, i32); -} - -impl CoordTransformer for frame::Frame { - fn to_coords(&self, x: i32, y: i32) -> (f32, f32) { - ( - inv_lerp(self.x() as f32, (self.x() + self.w()) as f32, x as f32), - inv_lerp(self.y() as f32, (self.y() + self.h()) as f32, y as f32), - ) - } - - fn from_coords(&self, x: f32, y: f32) -> (i32, i32) { - ( - lerp(self.x() as f32, (self.x() + self.w()) as f32, x) as i32, - lerp(self.y() as f32, (self.y() + self.h()) as f32, y) as i32, - ) - } +impl CoordTransformer<i32> for frame::Frame { + fn origin(&self) -> (i32, i32) { (self.x(), self.y()) } + fn extremes(&self) -> (i32, i32) { (self.x()+self.w(), self.y()+self.h()) } } //////////////////// App State ////////////////////