Transformible coordinates are a board trait

This commit is contained in:
Dane Johnson 2022-05-08 14:39:30 -05:00
parent 69cf7baa45
commit 5165708a03
3 changed files with 175 additions and 163 deletions

BIN
board.zip

Binary file not shown.

View File

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

View File

@ -13,7 +13,7 @@ use std::sync::Mutex;
mod board; mod board;
use board::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 //////////////////// //////////////////// Global State ////////////////////
// Don't @ me... // Don't @ me...
@ -133,35 +133,9 @@ mod dispatch {
} }
} }
//////////////////// Utility Functions //////////////////// impl CoordTransformer<i32> for frame::Frame {
fn origin(&self) -> (i32, i32) { (self.x(), self.y()) }
fn lerp(v0: f32, v1: f32, t: f32) -> f32 { fn extremes(&self) -> (i32, i32) { (self.x()+self.w(), self.y()+self.h()) }
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,
)
}
} }
//////////////////// App State //////////////////// //////////////////// App State ////////////////////