diff --git a/Cargo.lock b/Cargo.lock index cf72ce2..3ffb825 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2827,6 +2827,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.10" @@ -2963,6 +2973,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flexi_logger" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff38b61724dd492b5171d5dbb0921dfc8e859022c5993b22f80f74e9afe6d573" +dependencies = [ + "chrono", + "log", + "nu-ansi-term", + "regex", + "thiserror 2.0.11", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -3172,15 +3195,18 @@ dependencies = [ "clap", "criterion", "env_logger", + "flexi_logger", "itertools 0.14.0", "log", "ordered-float 5.0.0", "paste", "random_color", + "regex", "rerun", "rstest", "rstest_reuse", "serde", + "serde-hjson", "serde_json", "tempfile", "walkdir", @@ -4312,6 +4338,9 @@ name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +dependencies = [ + "serde", +] [[package]] name = "linux-raw-sys" @@ -4353,6 +4382,10 @@ name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "serde", + "value-bag", +] [[package]] name = "log-once" @@ -4715,6 +4748,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num" version = "0.4.3" @@ -7616,9 +7658,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -7628,9 +7670,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -8106,6 +8148,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hjson" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00962f7686acc7ab668cb70932997c078876fd4adcf4cb951cade6784e6d89ee" +dependencies = [ + "lazy_static", + "linked-hash-map", + "num-traits", + "regex", + "serde", +] + [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -8140,6 +8195,15 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "serde_fmt" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" +dependencies = [ + "serde", +] + [[package]] name = "serde_json" version = "1.0.140" @@ -8537,6 +8601,84 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "sval" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9739f56c5d0c44a5ed45473ec868af02eb896af8c05f616673a31e1d1bb09" + +[[package]] +name = "sval_buffer" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f39b07436a8c271b34dad5070c634d1d3d76d6776e938ee97b4a66a5e8003d0b" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffcb072d857431bf885580dacecf05ed987bac931230736739a79051dbf3499b" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f214f427ad94a553e5ca5514c95c6be84667cbc5568cce957f03f3477d03d5c" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ed34b32e638dec9a99c8ac92d0aa1220d40041026b625474c2b6a4d6f4feb" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14bae8fcb2f24fee2c42c1f19037707f7c9a29a0cda936d2188d48a961c4bb2a" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4eaea3821d3046dcba81d4b8489421da42961889902342691fb7eab491d79e" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172dd4aa8cb3b45c8ac8f3b4111d644cd26938b0643ede8f93070812b87fb339" +dependencies = [ + "serde", + "sval", + "sval_nested", +] + [[package]] name = "svgtypes" version = "0.15.3" @@ -9113,6 +9255,12 @@ dependencies = [ "rustc-hash 1.1.0", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.17.0" @@ -9278,6 +9426,42 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35540706617d373b118d550d41f5dfe0b78a0c195dc13c6815e92e2638432306" +dependencies = [ + "erased-serde", + "serde", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe7e140a2658cc16f7ee7a86e413e803fc8f9b5127adc8755c19f9fefa63a52" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + [[package]] name = "vec1" version = "1.12.1" diff --git a/Cargo.toml b/Cargo.toml index b6db565..67d366d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,12 +7,15 @@ default-run = "visualizer" [dependencies] clap = { version = "4.5.31", features = ["derive"] } +flexi_logger = { version = "0.31", features = ["kv"] } itertools = "0.14.0" log = { version = "0.4", features = ["kv"] } ordered-float = "5.0.0" random_color = { version = "1.0.0", optional = true } +regex = "1.11.3" rerun = { version = "0.24.1", optional = true } serde = { version = "1.0.218", features = ["derive"] } +serde-hjson = "1.1.0" serde_json = "1.0.140" walkdir = "2.5.0" diff --git a/benches/convex_hull.rs b/benches/convex_hull.rs index 2902f01..c545eed 100644 --- a/benches/convex_hull.rs +++ b/benches/convex_hull.rs @@ -16,27 +16,27 @@ fn benchmark_convex_hull(c: &mut Criterion) { group.bench_with_input( BenchmarkId::new("divide_conquer", name), polygon, - |b, polygon| b.iter(|| DivideConquer.convex_hull(polygon, &mut None)), + |b, polygon| b.iter(|| DivideConquer.convex_hull(polygon)), ); group.bench_with_input( BenchmarkId::new("gift_wrapping", name), polygon, - |b, polygon| b.iter(|| GiftWrapping.convex_hull(polygon, &mut None)), + |b, polygon| b.iter(|| GiftWrapping.convex_hull(polygon)), ); group.bench_with_input( BenchmarkId::new("graham_scan", name), polygon, - |b, polygon| b.iter(|| GrahamScan.convex_hull(polygon, &mut None)), + |b, polygon| b.iter(|| GrahamScan.convex_hull(polygon)), ); group.bench_with_input( BenchmarkId::new("incremental", name), polygon, - |b, polygon| b.iter(|| Incremental.convex_hull(polygon, &mut None)), + |b, polygon| b.iter(|| Incremental.convex_hull(polygon)), ); group.bench_with_input( BenchmarkId::new("quick_hull", name), polygon, - |b, polygon| b.iter(|| QuickHull.convex_hull(polygon, &mut None)), + |b, polygon| b.iter(|| QuickHull.convex_hull(polygon)), ); } group.finish(); diff --git a/src/alg_step.rs b/src/alg_step.rs new file mode 100644 index 0000000..51d82c2 --- /dev/null +++ b/src/alg_step.rs @@ -0,0 +1,101 @@ +use regex::Regex; +use serde::de::DeserializeOwned; +use serde::Deserialize; +use std::fmt; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; + +use crate::vertex::VertexId; + +#[derive(Debug)] +pub enum StepParseError { + IO(std::io::Error), + Regex(regex::Error), +} + +impl From for StepParseError { + fn from(value: std::io::Error) -> Self { + StepParseError::IO(value) + } +} + +impl From for StepParseError { + fn from(value: regex::Error) -> Self { + StepParseError::Regex(value) + } +} + +pub fn parse_steps_from_logs(path: PathBuf) -> Result, StepParseError> +where + T: DeserializeOwned, +{ + let file = File::open(path)?; + let reader = BufReader::new(file); + let re = Regex::new(r"DEBUG \[.*\] \w+ (?.*)")?; + + let mut steps = Vec::::new(); + for line in reader.lines() { + if let Some(caps) = re.captures(&line?) { + if let Ok(step) = serde_hjson::from_str::(&caps["data"]) { + steps.push(step); + } + } + } + + Ok(steps) +} + +#[derive(Debug, Default, Deserialize)] +pub struct GrahamScanStep { + pub idx: usize, + pub new_id: Option, + pub hull_ids: Vec, +} + +impl fmt::Display for GrahamScanStep { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "GrahamScanStep {{ idx: {}", self.idx)?; + if let Some(new_id) = self.new_id { + write!(f, ", new_id: {new_id}")?; + } + write!(f, ", hull_ids: {:?} }}", self.hull_ids)?; + Ok(()) + } +} + +impl GrahamScanStep { + pub fn hull_tail(&self, num_elements: usize) -> Vec { + self.hull_ids[self.hull_ids.len() - num_elements..].to_vec() + } + + pub fn hull_top(&self) -> VertexId { + self.hull_ids[self.hull_ids.len() - 1] + } +} + +#[derive(Debug, Default, Deserialize)] +pub struct IncrementalStep { + pub idx: usize, + pub new_id: Option, + pub ut_id: Option, + pub lt_id: Option, + pub hull_ids: Vec, +} + +impl fmt::Display for IncrementalStep { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "IncrementalStep {{ idx: {}", self.idx)?; + if let Some(new_id) = self.new_id { + write!(f, ", new_id: {new_id}")?; + } + if let Some(ut_id) = self.ut_id { + write!(f, ", ut_id: {ut_id}")?; + } + if let Some(lt_id) = self.lt_id { + write!(f, ", lt_id: {lt_id}")?; + } + write!(f, ", hull_ids: {:?} }}", self.hull_ids)?; + Ok(()) + } +} diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index fc09621..53000e4 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -1,12 +1,11 @@ use clap::{Parser, ValueEnum}; +use flexi_logger::{FileSpec, Logger}; use itertools::Itertools; use random_color::RandomColor; use geometer::{ - convex_hull::{ - ConvexHullComputer, ConvexHullTracer, ConvexHullTracerStep, GrahamScan, Incremental, - QuickHull, - }, + alg_step::{parse_steps_from_logs, GrahamScanStep, IncrementalStep, StepParseError}, + convex_hull::{ConvexHullComputer, GrahamScan, Incremental, QuickHull}, error::FileError, geometry::Geometry, polygon::Polygon, @@ -23,7 +22,7 @@ enum Visualization { Triangulation, } -/// Visualize polygons and algorithms using Rerun.io`` +/// Visualize polygons and algorithms using Rerun.io #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { @@ -43,7 +42,9 @@ struct Args { #[derive(Debug)] pub enum VisualizationError { File(FileError), + FlexiLogger(flexi_logger::FlexiLoggerError), Rerun(rerun::RecordingStreamError), + StepParse(StepParseError), } impl From for VisualizationError { @@ -52,12 +53,24 @@ impl From for VisualizationError { } } +impl From for VisualizationError { + fn from(value: flexi_logger::FlexiLoggerError) -> Self { + VisualizationError::FlexiLogger(value) + } +} + impl From for VisualizationError { fn from(value: rerun::RecordingStreamError) -> Self { VisualizationError::Rerun(value) } } +impl From for VisualizationError { + fn from(value: StepParseError) -> Self { + VisualizationError::StepParse(value) + } +} + pub struct RerunVisualizer { rec: rerun::RecordingStream, } @@ -155,7 +168,7 @@ impl RerunVisualizer { self.visualize_nominal_polygon(polygon, name, polygon_color)?; self.increment_frame(&mut frame); - let hull = QuickHull.convex_hull(polygon, &mut None); + let hull = QuickHull.convex_hull(polygon); self.visualize_vertex_chain( &hull.vertices().into_iter().cloned().collect_vec(), &format!("{name}/convex_hull"), @@ -175,8 +188,15 @@ impl RerunVisualizer { polygon: &Polygon, name: &String, ) -> Result<(), VisualizationError> { - let tracer = &mut Some(ConvexHullTracer::default()); - let _final_hull = GrahamScan.convex_hull(polygon, tracer); + let file_spec = FileSpec::default() + .directory("/tmp") + .basename("visualizer_graham_scan"); + Logger::try_with_str("debug")? + .log_to_file(file_spec.clone()) + .start()?; + + let final_hull = GrahamScan.convex_hull(polygon); + let steps: Vec = parse_steps_from_logs(file_spec.as_pathbuf(None))?; // TODO will ultimately want a config such that these could // be specified in some configurable or at the very least @@ -205,8 +225,8 @@ impl RerunVisualizer { .with_draw_order(100.0), )?; - let mut prev_step: Option<&ConvexHullTracerStep> = None; - for (i, step) in tracer.as_ref().unwrap().steps.iter().enumerate() { + let mut prev_step: Option<&GrahamScanStep> = None; + for (i, step) in steps.iter().enumerate() { if i == 0 { // Show initial edge of hull self.visualize_vertex_chain( @@ -241,11 +261,11 @@ impl RerunVisualizer { )?; // Show next vertex used for angle test - let n_id = step.next_vertex.expect("Next vertex should exist i > 0"); - let n_v = polygon.get_vertex(&n_id).unwrap(); + let new_id = step.new_id.expect("Should exist i > 0"); + let new_v = polygon.get_vertex(&new_id).unwrap(); self.rec.log( format!("{name}/alg_{i}/next_vertex"), - &rerun::Points2D::new([(n_v.x as f32, n_v.y as f32)]) + &rerun::Points2D::new([(new_v.x as f32, new_v.y as f32)]) .with_radii([1.0]) .with_colors([check_color]) .with_draw_order(100.0), @@ -255,7 +275,7 @@ impl RerunVisualizer { format!("{name}/alg/next_vertex_marker"), &rerun::LineStrips2D::new([[ (v_0.x as f32, v_0.y as f32), - (n_v.x as f32, n_v.y as f32), + (new_v.x as f32, new_v.y as f32), ]]) .with_radii([0.1]) .with_colors([init_vertex_color]), @@ -265,8 +285,7 @@ impl RerunVisualizer { self.clear(format!("{name}/alg_{i}/check_edge"))?; self.clear(format!("{name}/alg_{i}/next_vertex"))?; - let top_id = step.hull[step.hull.len() - 1]; - if n_id == top_id { + if new_id == step.hull_top() { // Hull is fully repaired at this point, show final edge // on stack connected to next vertex is a left turn (this // will just be last 3 vertices in hull vertex chain @@ -285,7 +304,7 @@ impl RerunVisualizer { // Render final edge on stack to next vertex as invalid // right turn let mut ids = prev_step.expect("Prev step exists for i > 0").hull_tail(2); - ids.push(n_id); + ids.push(new_id); self.visualize_vertex_chain( &polygon.get_vertices(ids), &format!("{name}/alg_{i}/invalid"), @@ -301,7 +320,7 @@ impl RerunVisualizer { // Show computed hull for this step self.increment_frame(&mut frame); self.visualize_vertex_chain( - &polygon.get_vertices(step.hull.clone()), + &polygon.get_vertices(step.hull_ids.clone()), &format!("{name}/hull_{i}"), Some(0.8), Some(hull_color), @@ -321,7 +340,7 @@ impl RerunVisualizer { } self.increment_frame(&mut frame); - self.visualize_final_hull(polygon, tracer, name, hull_color)?; + self.visualize_final_hull(&final_hull, name, hull_color)?; Ok(()) } @@ -331,8 +350,16 @@ impl RerunVisualizer { polygon: &Polygon, name: &String, ) -> Result<(), VisualizationError> { - let tracer = &mut Some(ConvexHullTracer::default()); - let _final_hull = Incremental.convex_hull(polygon, tracer); + let file_spec = FileSpec::default() + .directory("/tmp") + .basename("visualizer_incremental"); + Logger::try_with_str("debug")? + .log_to_file(file_spec.clone()) + .start()?; + + let final_hull = Incremental.convex_hull(polygon); + let steps: Vec = + parse_steps_from_logs(file_spec.as_pathbuf(None)).unwrap(); let mut frame: i64 = 0; self.rec.set_time_sequence("frame", frame); @@ -343,26 +370,29 @@ impl RerunVisualizer { // for color scheme I think looks decent let polygon_color = [132, 90, 109, 255]; let hull_color = [25, 100, 126, 255]; - let next_vertex_color = [242, 192, 53, 255]; + let new_vertex_color = [242, 192, 53, 255]; let ut_color = [52, 163, 82, 255]; let lt_color = [163, 0, 0, 255]; self.visualize_nominal_polygon(polygon, name, polygon_color)?; - // For each step will show upper/lower tangent vertex selection and - // how they connect to the current hull, followed by the resulting - // hull computed at that step - for (i, step) in tracer.as_ref().unwrap().steps.iter().enumerate() { + // For each step, show upper/lower tangent vertex selection and + // how they connect to the current hull, followed by the + // resulting hull computed at that step + for (i, step) in steps.iter().enumerate() { if i > 0 { self.increment_frame(&mut frame); - let n_id = step.next_vertex.expect("Next vertex should exist i > 0"); - let n_v = polygon.get_vertex(&n_id).unwrap(); + let new_id = step.new_id.expect("Should exist i > 0"); + let ut_id = step.ut_id.expect("Should exist i > 0"); + let lt_id = step.lt_id.expect("Should exist i > 0"); + + let new_v = polygon.get_vertex(&new_id).unwrap(); self.rec.log( format!("{name}/alg_{i}/next_vertex"), - &rerun::Points2D::new([(n_v.x as f32, n_v.y as f32)]) + &rerun::Points2D::new([(new_v.x as f32, new_v.y as f32)]) .with_radii([1.0]) - .with_colors([next_vertex_color]) + .with_colors([new_vertex_color]) .with_draw_order(100.0), )?; @@ -370,11 +400,8 @@ impl RerunVisualizer { // Show upper/lower tangent vertices and their connection // to the current hull - let ut_id = step - .upper_tangent_vertex - .expect("Upper tangent vertex should exist i > 0"); self.visualize_vertex_chain( - &polygon.get_vertices(vec![ut_id, n_id]), + &polygon.get_vertices(vec![ut_id, new_id]), &format!("{name}/alg_{i}/upper_tangent"), Some(1.0), Some(ut_color), @@ -384,11 +411,8 @@ impl RerunVisualizer { false, )?; - let lt_id = step - .lower_tangent_vertex - .expect("Lower tangent vertex should exist i > 0"); self.visualize_vertex_chain( - &polygon.get_vertices(vec![lt_id, n_id]), + &polygon.get_vertices(vec![lt_id, new_id]), &format!("{name}/alg_{i}/lower_tangent"), Some(1.0), Some(lt_color), @@ -402,7 +426,7 @@ impl RerunVisualizer { // Show computed hull for this step self.increment_frame(&mut frame); self.visualize_vertex_chain( - &polygon.get_vertices(step.hull.clone()), + &polygon.get_vertices(step.hull_ids.clone()), &format!("{name}/hull_{i}"), Some(0.8), Some(hull_color), @@ -420,7 +444,7 @@ impl RerunVisualizer { } self.increment_frame(&mut frame); - self.visualize_final_hull(polygon, tracer, name, hull_color)?; + self.visualize_final_hull(&final_hull, name, hull_color)?; Ok(()) } @@ -445,14 +469,12 @@ impl RerunVisualizer { fn visualize_final_hull( &self, - polygon: &Polygon, - tracer: &mut Option, + final_hull: &Polygon, name: &String, hull_color: [u8; 4], ) -> Result<(), VisualizationError> { - let final_step = tracer.as_ref().unwrap().steps.last().unwrap(); self.visualize_vertex_chain( - &polygon.get_vertices(final_step.hull.clone()), + &final_hull.get_vertices(final_hull.vertex_ids()), &format!("{name}/hull_final"), Some(1.0), Some(hull_color), diff --git a/src/convex_hull.rs b/src/convex_hull.rs index 6273808..c1965c4 100644 --- a/src/convex_hull.rs +++ b/src/convex_hull.rs @@ -1,68 +1,24 @@ use itertools::Itertools; use log::{debug, info, trace}; use ordered_float::OrderedFloat as OF; -use std::fmt; use crate::{ + alg_step::{GrahamScanStep, IncrementalStep}, data_structure::{HullSet, Stack}, geometry::Geometry, polygon::Polygon, vertex::VertexId, }; -#[derive(Default)] -pub struct ConvexHullTracerStep { - pub hull: Vec, - pub next_vertex: Option, - pub upper_tangent_vertex: Option, - pub lower_tangent_vertex: Option, -} - -impl fmt::Display for ConvexHullTracerStep { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "\tHull Vertices: {:?}", self.hull)?; - if let Some(n_v) = self.next_vertex { - writeln!(f, "\tNext Vertex: {:?}", n_v)?; - } - if let Some(ut_v) = self.upper_tangent_vertex { - writeln!(f, "\tUpper Tangent Vertex: {:?}", ut_v)?; - } - if let Some(lt_v) = self.lower_tangent_vertex { - writeln!(f, "\tLower Tangent Vertex: {:?}", lt_v)?; - } - Ok(()) - } -} - -impl ConvexHullTracerStep { - pub fn hull_tail(&self, num_elements: usize) -> Vec { - self.hull[self.hull.len() - num_elements..].to_vec() - } -} - -#[derive(Default)] -pub struct ConvexHullTracer { - pub steps: Vec, -} - -impl fmt::Debug for ConvexHullTracer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for (i, step) in self.steps.iter().enumerate() { - write!(f, "STEP {}:\n{}", i, step)?; - } - Ok(()) - } -} - pub trait ConvexHullComputer { - fn convex_hull(&self, polygon: &Polygon, tracer: &mut Option) -> Polygon; + fn convex_hull(&self, polygon: &Polygon) -> Polygon; } #[derive(Default)] pub struct GiftWrapping; impl ConvexHullComputer for GiftWrapping { - fn convex_hull(&self, polygon: &Polygon, _tracer: &mut Option) -> Polygon { + fn convex_hull(&self, polygon: &Polygon) -> Polygon { info!("Computing convex hull with the GiftWrapping algorithm"); let mut hull_ids = HullSet::default(); @@ -93,7 +49,7 @@ impl ConvexHullComputer for GiftWrapping { pub struct QuickHull; impl ConvexHullComputer for QuickHull { - fn convex_hull(&self, polygon: &Polygon, _tracer: &mut Option) -> Polygon { + fn convex_hull(&self, polygon: &Polygon) -> Polygon { info!("Computing convex hull with the QuickHull algorithm"); let mut hull_ids = HullSet::default(); @@ -154,7 +110,7 @@ impl ConvexHullComputer for QuickHull { pub struct GrahamScan; impl ConvexHullComputer for GrahamScan { - fn convex_hull(&self, polygon: &Polygon, tracer: &mut Option) -> Polygon { + fn convex_hull(&self, polygon: &Polygon) -> Polygon { info!("Computing convex hull with the GrahamScan algorithm"); let mut stack = Stack::default(); @@ -166,41 +122,45 @@ impl ConvexHullComputer for GrahamScan { stack.push(polygon.rightmost_lowest_vertex().id); stack.push(vertices.remove(0).id); - if let Some(t) = tracer.as_mut() { - t.steps.push(ConvexHullTracerStep { - hull: stack.clone(), + debug!( + "{}", + GrahamScanStep { + idx: 0, + hull_ids: stack.clone(), ..Default::default() - }); - } + } + ); - for v in vertices.iter() { - debug!("Current vertex: {}", v.id); + for (idx, new_v) in vertices.iter().enumerate() { + debug!("Current vertex: {}", new_v.id); // If current vertex is a left turn from current segment off // top of stack, add vertex to incremental hull on stack and // continue to next vertex. Otherwise the current hull on // stack is wrong, continue popping until it's corrected. loop { assert!(stack.len() >= 2); - let v_top = stack[stack.len() - 1]; - let v_prev = stack[stack.len() - 2]; - let ls = polygon.get_line_segment(&v_prev, &v_top).unwrap(); - if v.left(&ls) { - debug!(v:?, ls:?; "Valid, push to stack"); - stack.push(v.id); + let top_id = stack[stack.len() - 1]; + let prev_id = stack[stack.len() - 2]; + let ls = polygon.get_line_segment(&prev_id, &top_id).unwrap(); + if new_v.left(&ls) { + debug!(new_v:?, ls:?; "Valid, push to stack"); + stack.push(new_v.id); } else { - debug!(v:?, ls:?; "Invalid, pop from stack"); + debug!(new_v:?, ls:?; "Invalid, pop from stack"); stack.pop(); } - if let Some(t) = tracer.as_mut() { - t.steps.push(ConvexHullTracerStep { - hull: stack.clone(), - next_vertex: Some(v.id), - ..Default::default() - }); - } - - if stack[stack.len() - 1] == v.id { + // TODO add macro for this + debug!( + "{}", + GrahamScanStep { + idx: idx + 1, + new_id: Some(new_v.id), + hull_ids: stack.clone(), + } + ); + + if stack[stack.len() - 1] == new_v.id { debug!("Current hull is valid, continue to next vertex"); break; } @@ -402,7 +362,7 @@ impl DivideConquer { } impl ConvexHullComputer for DivideConquer { - fn convex_hull(&self, polygon: &Polygon, _tracer: &mut Option) -> Polygon { + fn convex_hull(&self, polygon: &Polygon) -> Polygon { info!("Computing convex hull with the DivideConquer algorithm"); if polygon.num_vertices() == 3 { @@ -470,99 +430,101 @@ impl Incremental { (hull, other_ids) } - fn upper_tangent_vertex(&self, hull: &Polygon, v: VertexId, polygon: &Polygon) -> VertexId { - let mut ut_v_id = hull.highest_rightmost_vertex().id; - let mut ut = polygon.get_line_segment(&ut_v_id, &v).unwrap(); + fn upper_tangent_vertex(&self, hull: &Polygon, id: VertexId, polygon: &Polygon) -> VertexId { + let mut ut_id = hull.highest_rightmost_vertex().id; + let mut ut = polygon.get_line_segment(&ut_id, &id).unwrap(); trace!( - v:?=polygon.get_vertex(&ut_v_id).unwrap(), ut:?; + ut_v:?=polygon.get_vertex(&ut_id).unwrap(), ut:?; "Starting upper tangent vertex search" ); let mut step = 1; - while !ut.is_upper_tangent(&ut_v_id, &hull) { - ut_v_id = hull.next_vertex_id(&ut_v_id).unwrap(); // Move up ccw - ut = polygon.get_line_segment(&ut_v_id, &v).unwrap(); - trace!(v:?=polygon.get_vertex(&ut_v_id).unwrap(), ut:?; "Step {step}"); + while !ut.is_upper_tangent(&ut_id, &hull) { + ut_id = hull.next_vertex_id(&ut_id).unwrap(); // Move up ccw + ut = polygon.get_line_segment(&ut_id, &id).unwrap(); + trace!(ut_v:?=polygon.get_vertex(&ut_id).unwrap(), ut:?; "Step {step}"); step += 1; } - ut_v_id + ut_id } - fn lower_tangent_vertex(&self, hull: &Polygon, v: VertexId, polygon: &Polygon) -> VertexId { - let mut lt_v_id = hull.lowest_rightmost_vertex().id; - let mut lt = polygon.get_line_segment(<_v_id, &v).unwrap(); + fn lower_tangent_vertex(&self, hull: &Polygon, id: VertexId, polygon: &Polygon) -> VertexId { + let mut lt_id = hull.lowest_rightmost_vertex().id; + let mut lt = polygon.get_line_segment(<_id, &id).unwrap(); trace!( - v:?=polygon.get_vertex(<_v_id).unwrap(), lt:?; + lt_v:?=polygon.get_vertex(<_id).unwrap(), lt:?; "Starting lower tangent vertex search" ); let mut step = 1; - while !lt.is_lower_tangent(<_v_id, &hull) { - lt_v_id = hull.prev_vertex_id(<_v_id).unwrap(); // Move down cw - lt = polygon.get_line_segment(<_v_id, &v).unwrap(); - trace!(v:?=polygon.get_vertex(<_v_id).unwrap(), lt:?; "Step {step}"); + while !lt.is_lower_tangent(<_id, &hull) { + lt_id = hull.prev_vertex_id(<_id).unwrap(); // Move down cw + lt = polygon.get_line_segment(<_id, &id).unwrap(); + trace!(lt_v:?=polygon.get_vertex(<_id).unwrap(), lt:?; "Step {step}"); step += 1; } - lt_v_id + lt_id } fn extract_boundary( &self, hull: Polygon, - new_v: VertexId, - hull_ut_v: VertexId, - hull_lt_v: VertexId, + new_id: VertexId, + hull_ut_id: VertexId, + hull_lt_id: VertexId, ) -> Vec { - let mut boundary = vec![new_v]; - let mut v = hull_ut_v; + let mut boundary = vec![new_id]; + let mut id = hull_ut_id; - trace!(new_v:?, hull_ut_v:?, hull_lt_v:?; "Extracting boundary"); + trace!(new_id:?, hull_ut_id:?, hull_lt_id:?; "Extracting boundary"); - while v != hull_lt_v { - boundary.push(v); - v = hull.next_vertex_id(&v).unwrap(); - trace!(v:?; "Boundary vertex"); + while id != hull_lt_id { + boundary.push(id); + id = hull.next_vertex_id(&id).unwrap(); + trace!(id:?; "Boundary vertex"); } - boundary.push(hull_lt_v); + boundary.push(hull_lt_id); boundary } } impl ConvexHullComputer for Incremental { - fn convex_hull(&self, polygon: &Polygon, tracer: &mut Option) -> Polygon { + fn convex_hull(&self, polygon: &Polygon) -> Polygon { info!("Computing convex hull with the Incremental algorithm"); let polygon = polygon.clone_clean_collinear(); let (mut hull, ids) = self.init_hull_three_leftmost(&polygon); - if let Some(t) = tracer.as_mut() { - t.steps.push(ConvexHullTracerStep { - hull: hull.vertex_ids(), - ..Default::default() - }); - } - for id in ids.into_iter() { - debug!("Current ID: {id}"); - - let ut_v = self.upper_tangent_vertex(&hull, id, &polygon); - let lt_v = self.lower_tangent_vertex(&hull, id, &polygon); - let new_hull_ids = self.extract_boundary(hull, id, ut_v, lt_v); + debug!( + "{}", + IncrementalStep { + idx: 0, + hull_ids: hull.vertex_ids(), + ..Default::default() + } + ); - debug!("Current hull: {new_hull_ids:?}"); - hull = polygon.get_polygon(new_hull_ids, false, true); + for (idx, new_id) in ids.into_iter().enumerate() { + let ut_id = self.upper_tangent_vertex(&hull, new_id, &polygon); + let lt_id = self.lower_tangent_vertex(&hull, new_id, &polygon); + let hull_ids = self.extract_boundary(hull, new_id, ut_id, lt_id); + + debug!( + "{}", + IncrementalStep { + idx: idx + 1, + new_id: Some(new_id), + ut_id: Some(ut_id), + lt_id: Some(lt_id), + hull_ids: hull_ids.clone(), + } + ); - if let Some(t) = tracer.as_mut() { - t.steps.push(ConvexHullTracerStep { - next_vertex: Some(id), - upper_tangent_vertex: Some(ut_v), - lower_tangent_vertex: Some(lt_v), - hull: hull.vertex_ids(), - }); - } + hull = polygon.get_polygon(hull_ids, false, true); } info!("Computed convex hull with {} vertices", hull.num_vertices()); @@ -585,7 +547,7 @@ mod tests { computer: impl ConvexHullComputer, ) { let _ = env_logger::builder().is_test(true).try_init(); - let hull = computer.convex_hull(&case.polygon, &mut None); + let hull = computer.convex_hull(&case.polygon); let hull_ids = hull.vertex_ids().into_iter().sorted().collect_vec(); assert_eq!(hull_ids, case.metadata.extreme_points); } diff --git a/src/lib.rs b/src/lib.rs index 61e7f66..3e39f2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ // empirical precision limit on the entire test suite const F64_ASSERT_PRECISION: f64 = 1e-4f64; +pub mod alg_step; pub mod bounding_box; pub mod convex_hull; pub mod data_structure; diff --git a/src/polygon.rs b/src/polygon.rs index f912931..c5f81d9 100644 --- a/src/polygon.rs +++ b/src/polygon.rs @@ -168,7 +168,13 @@ impl Polygon { } pub fn vertex_ids(&self) -> Vec { - self.vertices().into_iter().map(|v| v.id).collect_vec() + let mut ids = vec![self.anchor]; + let mut current = self.next_vertex_id(&self.anchor).unwrap(); + while current != self.anchor { + ids.push(current); + current = self.next_vertex_id(¤t).unwrap(); + } + ids } pub fn vertex_ids_by_increasing_x(&self) -> Vec { diff --git a/src/vertex.rs b/src/vertex.rs index 465295b..7692557 100644 --- a/src/vertex.rs +++ b/src/vertex.rs @@ -4,6 +4,7 @@ use std::fmt; use crate::{line_segment::LineSegment, triangle::Triangle, vector::Vector}; #[derive(Clone, Copy, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +#[serde(transparent)] pub struct VertexId(u32); impl From for VertexId { @@ -45,7 +46,7 @@ impl fmt::Display for Vertex { impl fmt::Debug for Vertex { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{{ id: {}, x: {}, y: {} }}", self.id, self.x, self.y) + write!(f, "{{id: {}, x: {}, y: {} }}", self.id, self.x, self.y) } }