From d2d753c8fac28059e689957463e6099f7f3787d5 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sat, 4 Oct 2025 16:03:24 +0000 Subject: [PATCH 01/10] Add preliminary parsing for incremental --- Cargo.lock | 192 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 + src/bin/run_visualizer.rs | 38 +++++++- src/convex_hull.rs | 36 +++++-- src/vertex.rs | 3 +- 5 files changed, 257 insertions(+), 15 deletions(-) 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/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index fc09621..bded445 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -1,11 +1,16 @@ +use regex::Regex; +use std::fs::File; +use std::io::{BufRead, BufReader}; + use clap::{Parser, ValueEnum}; +use flexi_logger::{FileSpec, FlexiLoggerError, Logger}; use itertools::Itertools; use random_color::RandomColor; use geometer::{ convex_hull::{ ConvexHullComputer, ConvexHullTracer, ConvexHullTracerStep, GrahamScan, Incremental, - QuickHull, + IncrementalStep, QuickHull, }, error::FileError, geometry::Geometry, @@ -43,6 +48,7 @@ struct Args { #[derive(Debug)] pub enum VisualizationError { File(FileError), + FlexiLoggerError(FlexiLoggerError), Rerun(rerun::RecordingStreamError), } @@ -58,6 +64,12 @@ impl From for VisualizationError { } } +impl From for VisualizationError { + fn from(value: flexi_logger::FlexiLoggerError) -> Self { + VisualizationError::FlexiLoggerError(value) + } +} + pub struct RerunVisualizer { rec: rerun::RecordingStream, } @@ -326,11 +338,33 @@ impl RerunVisualizer { Ok(()) } + pub fn parse_logs(&self) -> Result<(), Box> { + let file = File::open("visualizer.log")?; + let reader = BufReader::new(file); + let re = Regex::new(r"DEBUG \[.*\] \w+ (?.*)").unwrap(); + + for line in reader.lines() { + if let Some(caps) = re.captures(&line?) { + if let Ok(step) = + serde_hjson::from_str::(&caps["data"].to_string()) + { + println!("GOOD {step:?}"); + } + } + } + + Ok(()) + } + pub fn visualize_convex_hull_incremental( &self, polygon: &Polygon, name: &String, ) -> Result<(), VisualizationError> { + Logger::try_with_str("debug")? + .log_to_file(FileSpec::default().suppress_timestamp()) + .start()?; + let tracer = &mut Some(ConvexHullTracer::default()); let _final_hull = Incremental.convex_hull(polygon, tracer); @@ -422,6 +456,8 @@ impl RerunVisualizer { self.increment_frame(&mut frame); self.visualize_final_hull(polygon, tracer, name, hull_color)?; + let _ = self.parse_logs(); + Ok(()) } diff --git a/src/convex_hull.rs b/src/convex_hull.rs index 6273808..4e70920 100644 --- a/src/convex_hull.rs +++ b/src/convex_hull.rs @@ -1,6 +1,7 @@ use itertools::Itertools; use log::{debug, info, trace}; use ordered_float::OrderedFloat as OF; +use serde::{Deserialize, Serialize}; use std::fmt; use crate::{ @@ -10,6 +11,15 @@ use crate::{ vertex::VertexId, }; +#[derive(Debug, Deserialize, Serialize)] +pub struct IncrementalStep { + pub idx: usize, + pub new_v: VertexId, + pub ut_v: VertexId, + pub lt_v: VertexId, + pub hull_ids: Vec, +} + #[derive(Default)] pub struct ConvexHullTracerStep { pub hull: Vec, @@ -545,19 +555,27 @@ impl ConvexHullComputer for Incremental { }); } - 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); + for (idx, new_v) in ids.into_iter().enumerate() { + let ut_v = self.upper_tangent_vertex(&hull, new_v, &polygon); + let lt_v = self.lower_tangent_vertex(&hull, new_v, &polygon); + let hull_ids = self.extract_boundary(hull, new_v, ut_v, lt_v); + + debug!( + "{:?}", + IncrementalStep { + idx, + new_v, + ut_v, + lt_v, + hull_ids: hull_ids.clone(), + } + ); - debug!("Current hull: {new_hull_ids:?}"); - hull = polygon.get_polygon(new_hull_ids, false, true); + hull = polygon.get_polygon(hull_ids, false, true); if let Some(t) = tracer.as_mut() { t.steps.push(ConvexHullTracerStep { - next_vertex: Some(id), + next_vertex: Some(new_v), upper_tangent_vertex: Some(ut_v), lower_tangent_vertex: Some(lt_v), hull: hull.vertex_ids(), 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) } } From 87064c146027253e2f5d7405abb1a531432d1728 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sat, 4 Oct 2025 16:07:50 +0000 Subject: [PATCH 02/10] Remove serialization trait for incrementalstep --- src/convex_hull.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/convex_hull.rs b/src/convex_hull.rs index 4e70920..f93b2d5 100644 --- a/src/convex_hull.rs +++ b/src/convex_hull.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use log::{debug, info, trace}; use ordered_float::OrderedFloat as OF; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use std::fmt; use crate::{ @@ -11,7 +11,7 @@ use crate::{ vertex::VertexId, }; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize)] pub struct IncrementalStep { pub idx: usize, pub new_v: VertexId, From 0bad0e0c446cab1b71ee161ac71f10fe420f57c5 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 5 Oct 2025 14:51:25 +0000 Subject: [PATCH 03/10] Remove tracer and update incremental vis to read from logs --- src/bin/run_visualizer.rs | 348 ++++++++++++++++++-------------------- src/convex_hull.rs | 108 +++--------- src/polygon.rs | 8 +- 3 files changed, 203 insertions(+), 261 deletions(-) diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index bded445..bbed638 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -8,10 +8,7 @@ use itertools::Itertools; use random_color::RandomColor; use geometer::{ - convex_hull::{ - ConvexHullComputer, ConvexHullTracer, ConvexHullTracerStep, GrahamScan, Incremental, - IncrementalStep, QuickHull, - }, + convex_hull::{ConvexHullComputer, GrahamScan, Incremental, IncrementalStep, QuickHull}, error::FileError, geometry::Geometry, polygon::Polygon, @@ -167,7 +164,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"), @@ -187,173 +184,176 @@ impl RerunVisualizer { polygon: &Polygon, name: &String, ) -> Result<(), VisualizationError> { - let tracer = &mut Some(ConvexHullTracer::default()); - let _final_hull = GrahamScan.convex_hull(polygon, tracer); - - // TODO will ultimately want a config such that these could - // be specified in some configurable or at the very least - // more interpretable way? For now just hardcoding values - // for color scheme I think looks decent - let init_vertex_color = [255, 255, 255, 255]; - let polygon_color = [132, 90, 109, 255]; - let hull_color = [25, 100, 126, 255]; - let check_color = [242, 192, 53, 255]; - let valid_color = [52, 163, 82, 255]; - let invalid_color = [163, 0, 0, 255]; - - let mut frame: i64 = 0; - self.rec.set_time_sequence("frame", frame); - - self.visualize_nominal_polygon(polygon, name, polygon_color)?; - - // Show initial vertex establishing min angle order - let id_0 = polygon.vertex_ids()[0]; - let v_0 = polygon.get_vertex(&id_0).unwrap(); - self.rec.log( - format!("{name}/alg_init/init_vertex"), - &rerun::Points2D::new([(v_0.x as f32, v_0.y as f32)]) - .with_radii([1.0]) - .with_colors([init_vertex_color]) - .with_draw_order(100.0), - )?; - - let mut prev_step: Option<&ConvexHullTracerStep> = None; - for (i, step) in tracer.as_ref().unwrap().steps.iter().enumerate() { - if i == 0 { - // Show initial edge of hull - self.visualize_vertex_chain( - &polygon.get_vertices(step.hull_tail(2)), - &format!("{name}/hull_{i}"), - Some(0.8), - Some(hull_color), - Some(0.2), - Some(hull_color), - None, - false, - )?; - } else { - self.increment_frame(&mut frame); - - // Show highlighted edge used for angle test - let ids = prev_step - .expect("Prev step should exist i > 0") - .hull_tail(2); - let v_origin = polygon.get_vertex(&ids[0]).unwrap(); - let v_head = polygon.get_vertex(&ids[1]).unwrap(); - self.rec.log( - format!("{name}/alg_{i}/check_edge"), - &rerun::Arrows2D::from_vectors([( - (v_head.x - v_origin.x) as f32, - (v_head.y - v_origin.y) as f32, - )]) - .with_origins([(v_origin.x as f32, v_origin.y as f32)]) - .with_radii([0.3]) - .with_colors([check_color]) - .with_draw_order(100.0), - )?; - - // 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(); - self.rec.log( - format!("{name}/alg_{i}/next_vertex"), - &rerun::Points2D::new([(n_v.x as f32, n_v.y as f32)]) - .with_radii([1.0]) - .with_colors([check_color]) - .with_draw_order(100.0), - )?; - - self.rec.log( - 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), - ]]) - .with_radii([0.1]) - .with_colors([init_vertex_color]), - )?; - - self.increment_frame(&mut frame); - 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 { - // 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 - // since the next vertex was accepted to the hull) - self.visualize_vertex_chain( - &polygon.get_vertices(step.hull_tail(3)), - &format!("{name}/alg_{i}/valid"), - Some(1.0), - Some(valid_color), - Some(0.3), - Some(valid_color), - Some(100.0), - false, - )?; - } else { - // 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); - self.visualize_vertex_chain( - &polygon.get_vertices(ids), - &format!("{name}/alg_{i}/invalid"), - Some(1.0), - Some(invalid_color), - Some(0.3), - Some(invalid_color), - Some(100.0), - false, - )?; - } - - // Show computed hull for this step - self.increment_frame(&mut frame); - self.visualize_vertex_chain( - &polygon.get_vertices(step.hull.clone()), - &format!("{name}/hull_{i}"), - Some(0.8), - Some(hull_color), - Some(0.2), - Some(hull_color), - None, - true, - )?; - } - prev_step = Some(step); - - self.clear_recursive(format!("{name}/alg_{i}"))?; - // Clear out old hull visualizations - if i > 0 { - self.clear_recursive(format!("{name}/hull_{}", i - 1))?; - } - } - - self.increment_frame(&mut frame); - self.visualize_final_hull(polygon, tracer, name, hull_color)?; - + // let _final_hull = GrahamScan.convex_hull(polygon); + // + // // TODO will ultimately want a config such that these could + // // be specified in some configurable or at the very least + // // more interpretable way? For now just hardcoding values + // // for color scheme I think looks decent + // let init_vertex_color = [255, 255, 255, 255]; + // let polygon_color = [132, 90, 109, 255]; + // let hull_color = [25, 100, 126, 255]; + // let check_color = [242, 192, 53, 255]; + // let valid_color = [52, 163, 82, 255]; + // let invalid_color = [163, 0, 0, 255]; + // + // let mut frame: i64 = 0; + // self.rec.set_time_sequence("frame", frame); + // + // self.visualize_nominal_polygon(polygon, name, polygon_color)?; + // + // // Show initial vertex establishing min angle order + // let id_0 = polygon.vertex_ids()[0]; + // let v_0 = polygon.get_vertex(&id_0).unwrap(); + // self.rec.log( + // format!("{name}/alg_init/init_vertex"), + // &rerun::Points2D::new([(v_0.x as f32, v_0.y as f32)]) + // .with_radii([1.0]) + // .with_colors([init_vertex_color]) + // .with_draw_order(100.0), + // )?; + // + // let mut prev_step: Option<&ConvexHullTracerStep> = None; + // for (i, step) in tracer.as_ref().unwrap().steps.iter().enumerate() { + // if i == 0 { + // // Show initial edge of hull + // self.visualize_vertex_chain( + // &polygon.get_vertices(step.hull_tail(2)), + // &format!("{name}/hull_{i}"), + // Some(0.8), + // Some(hull_color), + // Some(0.2), + // Some(hull_color), + // None, + // false, + // )?; + // } else { + // self.increment_frame(&mut frame); + // + // // Show highlighted edge used for angle test + // let ids = prev_step + // .expect("Prev step should exist i > 0") + // .hull_tail(2); + // let v_origin = polygon.get_vertex(&ids[0]).unwrap(); + // let v_head = polygon.get_vertex(&ids[1]).unwrap(); + // self.rec.log( + // format!("{name}/alg_{i}/check_edge"), + // &rerun::Arrows2D::from_vectors([( + // (v_head.x - v_origin.x) as f32, + // (v_head.y - v_origin.y) as f32, + // )]) + // .with_origins([(v_origin.x as f32, v_origin.y as f32)]) + // .with_radii([0.3]) + // .with_colors([check_color]) + // .with_draw_order(100.0), + // )?; + // + // // 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(); + // self.rec.log( + // format!("{name}/alg_{i}/next_vertex"), + // &rerun::Points2D::new([(n_v.x as f32, n_v.y as f32)]) + // .with_radii([1.0]) + // .with_colors([check_color]) + // .with_draw_order(100.0), + // )?; + // + // self.rec.log( + // 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), + // ]]) + // .with_radii([0.1]) + // .with_colors([init_vertex_color]), + // )?; + // + // self.increment_frame(&mut frame); + // 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 { + // // 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 + // // since the next vertex was accepted to the hull) + // self.visualize_vertex_chain( + // &polygon.get_vertices(step.hull_tail(3)), + // &format!("{name}/alg_{i}/valid"), + // Some(1.0), + // Some(valid_color), + // Some(0.3), + // Some(valid_color), + // Some(100.0), + // false, + // )?; + // } else { + // // 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); + // self.visualize_vertex_chain( + // &polygon.get_vertices(ids), + // &format!("{name}/alg_{i}/invalid"), + // Some(1.0), + // Some(invalid_color), + // Some(0.3), + // Some(invalid_color), + // Some(100.0), + // false, + // )?; + // } + // + // // Show computed hull for this step + // self.increment_frame(&mut frame); + // self.visualize_vertex_chain( + // &polygon.get_vertices(step.hull.clone()), + // &format!("{name}/hull_{i}"), + // Some(0.8), + // Some(hull_color), + // Some(0.2), + // Some(hull_color), + // None, + // true, + // )?; + // } + // prev_step = Some(step); + // + // self.clear_recursive(format!("{name}/alg_{i}"))?; + // // Clear out old hull visualizations + // if i > 0 { + // self.clear_recursive(format!("{name}/hull_{}", i - 1))?; + // } + // } + // + // self.increment_frame(&mut frame); + // self.visualize_final_hull(polygon, tracer, name, hull_color)?; + // Ok(()) } - pub fn parse_logs(&self) -> Result<(), Box> { + // TODO will need to make this more general beyond incrmental + pub fn parse_logs(&self) -> Result, Box> { + // TODO will need to figure out how to handle the filename logs go to let file = File::open("visualizer.log")?; let reader = BufReader::new(file); let re = Regex::new(r"DEBUG \[.*\] \w+ (?.*)").unwrap(); + 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"].to_string()) { println!("GOOD {step:?}"); + steps.push(step); } } } - Ok(()) + Ok(steps) } pub fn visualize_convex_hull_incremental( @@ -361,12 +361,13 @@ impl RerunVisualizer { polygon: &Polygon, name: &String, ) -> Result<(), VisualizationError> { + // TODO need to set different log filename Logger::try_with_str("debug")? .log_to_file(FileSpec::default().suppress_timestamp()) .start()?; - let tracer = &mut Some(ConvexHullTracer::default()); - let _final_hull = Incremental.convex_hull(polygon, tracer); + let final_hull = Incremental.convex_hull(polygon); + let steps = self.parse_logs().unwrap(); let mut frame: i64 = 0; self.rec.set_time_sequence("frame", frame); @@ -383,15 +384,14 @@ impl RerunVisualizer { 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 n_v = polygon.get_vertex(&step.new_v).unwrap(); self.rec.log( format!("{name}/alg_{i}/next_vertex"), &rerun::Points2D::new([(n_v.x as f32, n_v.y as f32)]) @@ -404,11 +404,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![step.ut_v, step.new_v]), &format!("{name}/alg_{i}/upper_tangent"), Some(1.0), Some(ut_color), @@ -418,11 +415,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![step.lt_v, step.new_v]), &format!("{name}/alg_{i}/lower_tangent"), Some(1.0), Some(lt_color), @@ -436,7 +430,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), @@ -454,9 +448,7 @@ impl RerunVisualizer { } self.increment_frame(&mut frame); - self.visualize_final_hull(polygon, tracer, name, hull_color)?; - - let _ = self.parse_logs(); + self.visualize_final_hull(&final_hull, name, hull_color)?; Ok(()) } @@ -481,14 +473,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 f93b2d5..71dca9f 100644 --- a/src/convex_hull.rs +++ b/src/convex_hull.rs @@ -2,7 +2,6 @@ use itertools::Itertools; use log::{debug, info, trace}; use ordered_float::OrderedFloat as OF; use serde::Deserialize; -use std::fmt; use crate::{ data_structure::{HullSet, Stack}, @@ -20,59 +19,15 @@ pub struct IncrementalStep { pub hull_ids: Vec, } -#[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(); @@ -103,7 +58,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(); @@ -164,7 +119,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(); @@ -176,12 +131,12 @@ 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(), - ..Default::default() - }); - } + // if let Some(t) = tracer.as_mut() { + // t.steps.push(ConvexHullTracerStep { + // hull: stack.clone(), + // ..Default::default() + // }); + // } for v in vertices.iter() { debug!("Current vertex: {}", v.id); @@ -202,13 +157,13 @@ impl ConvexHullComputer for GrahamScan { stack.pop(); } - if let Some(t) = tracer.as_mut() { - t.steps.push(ConvexHullTracerStep { - hull: stack.clone(), - next_vertex: Some(v.id), - ..Default::default() - }); - } + // 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 { debug!("Current hull is valid, continue to next vertex"); @@ -412,7 +367,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 { @@ -543,18 +498,18 @@ impl Incremental { } 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() - }); - } - + // if let Some(t) = tracer.as_mut() { + // t.steps.push(ConvexHullTracerStep { + // hull: hull.vertex_ids(), + // ..Default::default() + // }); + // } + // for (idx, new_v) in ids.into_iter().enumerate() { let ut_v = self.upper_tangent_vertex(&hull, new_v, &polygon); let lt_v = self.lower_tangent_vertex(&hull, new_v, &polygon); @@ -572,15 +527,6 @@ impl ConvexHullComputer for Incremental { ); hull = polygon.get_polygon(hull_ids, false, true); - - if let Some(t) = tracer.as_mut() { - t.steps.push(ConvexHullTracerStep { - next_vertex: Some(new_v), - upper_tangent_vertex: Some(ut_v), - lower_tangent_vertex: Some(lt_v), - hull: hull.vertex_ids(), - }); - } } info!("Computed convex hull with {} vertices", hull.num_vertices()); @@ -603,7 +549,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/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 { From d4aedd60bd7770dd6c80f1c4452ff8ebbb9a46ae Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 5 Oct 2025 19:13:47 +0000 Subject: [PATCH 04/10] Add optional values to support init step --- src/bin/run_visualizer.rs | 11 +++++--- src/convex_hull.rs | 53 +++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index bbed638..31c319e 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -391,7 +391,12 @@ impl RerunVisualizer { if i > 0 { self.increment_frame(&mut frame); - let n_v = polygon.get_vertex(&step.new_v).unwrap(); + // TODO maybe rename these on step to be about ids + let new_v = step.new_v.expect("Should exist i > 0"); + let ut_v = step.ut_v.expect("Should exist i > 0"); + let lt_v = step.lt_v.expect("Should exist i > 0"); + + let n_v = polygon.get_vertex(&new_v).unwrap(); self.rec.log( format!("{name}/alg_{i}/next_vertex"), &rerun::Points2D::new([(n_v.x as f32, n_v.y as f32)]) @@ -405,7 +410,7 @@ impl RerunVisualizer { // Show upper/lower tangent vertices and their connection // to the current hull self.visualize_vertex_chain( - &polygon.get_vertices(vec![step.ut_v, step.new_v]), + &polygon.get_vertices(vec![ut_v, new_v]), &format!("{name}/alg_{i}/upper_tangent"), Some(1.0), Some(ut_color), @@ -416,7 +421,7 @@ impl RerunVisualizer { )?; self.visualize_vertex_chain( - &polygon.get_vertices(vec![step.lt_v, step.new_v]), + &polygon.get_vertices(vec![lt_v, new_v]), &format!("{name}/alg_{i}/lower_tangent"), Some(1.0), Some(lt_color), diff --git a/src/convex_hull.rs b/src/convex_hull.rs index 71dca9f..43d7079 100644 --- a/src/convex_hull.rs +++ b/src/convex_hull.rs @@ -2,6 +2,7 @@ use itertools::Itertools; use log::{debug, info, trace}; use ordered_float::OrderedFloat as OF; use serde::Deserialize; +use std::fmt; use crate::{ data_structure::{HullSet, Stack}, @@ -10,15 +11,32 @@ use crate::{ vertex::VertexId, }; -#[derive(Debug, Deserialize)] +#[derive(Debug, Default, Deserialize)] pub struct IncrementalStep { pub idx: usize, - pub new_v: VertexId, - pub ut_v: VertexId, - pub lt_v: VertexId, + pub new_v: Option, + pub ut_v: Option, + pub lt_v: 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_v) = self.new_v { + write!(f, ", new_v: {new_v}")?; + } + if let Some(ut_v) = self.ut_v { + write!(f, ", ut_v: {ut_v}")?; + } + if let Some(lt_v) = self.lt_v { + write!(f, ", lt_v: {lt_v}")?; + } + write!(f, ", hull_ids: {:?} }}", self.hull_ids)?; + Ok(()) + } +} + pub trait ConvexHullComputer { fn convex_hull(&self, polygon: &Polygon) -> Polygon; } @@ -503,25 +521,28 @@ impl ConvexHullComputer for Incremental { 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() - // }); - // } - // + + debug!( + "{}", + IncrementalStep { + idx: 0, + hull_ids: hull.vertex_ids(), + ..Default::default() + } + ); + for (idx, new_v) in ids.into_iter().enumerate() { let ut_v = self.upper_tangent_vertex(&hull, new_v, &polygon); let lt_v = self.lower_tangent_vertex(&hull, new_v, &polygon); let hull_ids = self.extract_boundary(hull, new_v, ut_v, lt_v); debug!( - "{:?}", + "{}", IncrementalStep { - idx, - new_v, - ut_v, - lt_v, + idx: idx + 1, + new_v: Some(new_v), + ut_v: Some(ut_v), + lt_v: Some(lt_v), hull_ids: hull_ids.clone(), } ); From 1f2f6ee382e42c623857b191f212c831278332a3 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 12 Oct 2025 09:59:10 -0400 Subject: [PATCH 05/10] Rename for ids --- src/bin/run_visualizer.rs | 19 ++++----- src/convex_hull.rs | 90 +++++++++++++++++++-------------------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index 31c319e..3d97be9 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -378,7 +378,7 @@ 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]; @@ -391,17 +391,16 @@ impl RerunVisualizer { if i > 0 { self.increment_frame(&mut frame); - // TODO maybe rename these on step to be about ids - let new_v = step.new_v.expect("Should exist i > 0"); - let ut_v = step.ut_v.expect("Should exist i > 0"); - let lt_v = step.lt_v.expect("Should exist i > 0"); + 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 n_v = polygon.get_vertex(&new_v).unwrap(); + 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), )?; @@ -410,7 +409,7 @@ impl RerunVisualizer { // Show upper/lower tangent vertices and their connection // to the current hull self.visualize_vertex_chain( - &polygon.get_vertices(vec![ut_v, new_v]), + &polygon.get_vertices(vec![ut_id, new_id]), &format!("{name}/alg_{i}/upper_tangent"), Some(1.0), Some(ut_color), @@ -421,7 +420,7 @@ impl RerunVisualizer { )?; self.visualize_vertex_chain( - &polygon.get_vertices(vec![lt_v, new_v]), + &polygon.get_vertices(vec![lt_id, new_id]), &format!("{name}/alg_{i}/lower_tangent"), Some(1.0), Some(lt_color), diff --git a/src/convex_hull.rs b/src/convex_hull.rs index 43d7079..039652d 100644 --- a/src/convex_hull.rs +++ b/src/convex_hull.rs @@ -14,23 +14,23 @@ use crate::{ #[derive(Debug, Default, Deserialize)] pub struct IncrementalStep { pub idx: usize, - pub new_v: Option, - pub ut_v: Option, - pub lt_v: Option, + 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_v) = self.new_v { - write!(f, ", new_v: {new_v}")?; + if let Some(new_id) = self.new_id { + write!(f, ", new_id: {new_id}")?; } - if let Some(ut_v) = self.ut_v { - write!(f, ", ut_v: {ut_v}")?; + if let Some(ut_id) = self.ut_id { + write!(f, ", ut_id: {ut_id}")?; } - if let Some(lt_v) = self.lt_v { - write!(f, ", lt_v: {lt_v}")?; + if let Some(lt_id) = self.lt_id { + write!(f, ", lt_id: {lt_id}")?; } write!(f, ", hull_ids: {:?} }}", self.hull_ids)?; Ok(()) @@ -453,64 +453,64 @@ 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 } } @@ -531,18 +531,18 @@ impl ConvexHullComputer for Incremental { } ); - for (idx, new_v) in ids.into_iter().enumerate() { - let ut_v = self.upper_tangent_vertex(&hull, new_v, &polygon); - let lt_v = self.lower_tangent_vertex(&hull, new_v, &polygon); - let hull_ids = self.extract_boundary(hull, new_v, ut_v, lt_v); + 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_v: Some(new_v), - ut_v: Some(ut_v), - lt_v: Some(lt_v), + new_id: Some(new_id), + ut_id: Some(ut_id), + lt_id: Some(lt_id), hull_ids: hull_ids.clone(), } ); From 64e94f5dcd11844b6ff667a2885ac23c829a26a3 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 12 Oct 2025 10:39:34 -0400 Subject: [PATCH 06/10] Move alg steps to their own file, start porting Graham (incomplete) --- src/alg_step.rs | 48 ++++++ src/bin/run_visualizer.rs | 330 +++++++++++++++++++++----------------- src/convex_hull.rs | 81 ++++------ src/lib.rs | 1 + 4 files changed, 257 insertions(+), 203 deletions(-) create mode 100644 src/alg_step.rs diff --git a/src/alg_step.rs b/src/alg_step.rs new file mode 100644 index 0000000..23e5372 --- /dev/null +++ b/src/alg_step.rs @@ -0,0 +1,48 @@ +use serde::Deserialize; +use std::fmt; + +use crate::vertex::VertexId; + +#[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(()) + } +} + +#[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 3d97be9..6dc782b 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -184,158 +184,187 @@ impl RerunVisualizer { polygon: &Polygon, name: &String, ) -> Result<(), VisualizationError> { - // let _final_hull = GrahamScan.convex_hull(polygon); - // - // // TODO will ultimately want a config such that these could - // // be specified in some configurable or at the very least - // // more interpretable way? For now just hardcoding values - // // for color scheme I think looks decent - // let init_vertex_color = [255, 255, 255, 255]; - // let polygon_color = [132, 90, 109, 255]; - // let hull_color = [25, 100, 126, 255]; - // let check_color = [242, 192, 53, 255]; - // let valid_color = [52, 163, 82, 255]; - // let invalid_color = [163, 0, 0, 255]; - // - // let mut frame: i64 = 0; - // self.rec.set_time_sequence("frame", frame); - // - // self.visualize_nominal_polygon(polygon, name, polygon_color)?; - // - // // Show initial vertex establishing min angle order - // let id_0 = polygon.vertex_ids()[0]; - // let v_0 = polygon.get_vertex(&id_0).unwrap(); - // self.rec.log( - // format!("{name}/alg_init/init_vertex"), - // &rerun::Points2D::new([(v_0.x as f32, v_0.y as f32)]) - // .with_radii([1.0]) - // .with_colors([init_vertex_color]) - // .with_draw_order(100.0), - // )?; - // - // let mut prev_step: Option<&ConvexHullTracerStep> = None; - // for (i, step) in tracer.as_ref().unwrap().steps.iter().enumerate() { - // if i == 0 { - // // Show initial edge of hull - // self.visualize_vertex_chain( - // &polygon.get_vertices(step.hull_tail(2)), - // &format!("{name}/hull_{i}"), - // Some(0.8), - // Some(hull_color), - // Some(0.2), - // Some(hull_color), - // None, - // false, - // )?; - // } else { - // self.increment_frame(&mut frame); - // - // // Show highlighted edge used for angle test - // let ids = prev_step - // .expect("Prev step should exist i > 0") - // .hull_tail(2); - // let v_origin = polygon.get_vertex(&ids[0]).unwrap(); - // let v_head = polygon.get_vertex(&ids[1]).unwrap(); - // self.rec.log( - // format!("{name}/alg_{i}/check_edge"), - // &rerun::Arrows2D::from_vectors([( - // (v_head.x - v_origin.x) as f32, - // (v_head.y - v_origin.y) as f32, - // )]) - // .with_origins([(v_origin.x as f32, v_origin.y as f32)]) - // .with_radii([0.3]) - // .with_colors([check_color]) - // .with_draw_order(100.0), - // )?; - // - // // 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(); - // self.rec.log( - // format!("{name}/alg_{i}/next_vertex"), - // &rerun::Points2D::new([(n_v.x as f32, n_v.y as f32)]) - // .with_radii([1.0]) - // .with_colors([check_color]) - // .with_draw_order(100.0), - // )?; - // - // self.rec.log( - // 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), - // ]]) - // .with_radii([0.1]) - // .with_colors([init_vertex_color]), - // )?; - // - // self.increment_frame(&mut frame); - // 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 { - // // 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 - // // since the next vertex was accepted to the hull) - // self.visualize_vertex_chain( - // &polygon.get_vertices(step.hull_tail(3)), - // &format!("{name}/alg_{i}/valid"), - // Some(1.0), - // Some(valid_color), - // Some(0.3), - // Some(valid_color), - // Some(100.0), - // false, - // )?; - // } else { - // // 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); - // self.visualize_vertex_chain( - // &polygon.get_vertices(ids), - // &format!("{name}/alg_{i}/invalid"), - // Some(1.0), - // Some(invalid_color), - // Some(0.3), - // Some(invalid_color), - // Some(100.0), - // false, - // )?; - // } - // - // // Show computed hull for this step - // self.increment_frame(&mut frame); - // self.visualize_vertex_chain( - // &polygon.get_vertices(step.hull.clone()), - // &format!("{name}/hull_{i}"), - // Some(0.8), - // Some(hull_color), - // Some(0.2), - // Some(hull_color), - // None, - // true, - // )?; - // } - // prev_step = Some(step); - // - // self.clear_recursive(format!("{name}/alg_{i}"))?; - // // Clear out old hull visualizations - // if i > 0 { - // self.clear_recursive(format!("{name}/hull_{}", i - 1))?; - // } - // } - // - // self.increment_frame(&mut frame); - // self.visualize_final_hull(polygon, tracer, name, hull_color)?; - // + // TODO need to set different log filename + Logger::try_with_str("debug")? + .log_to_file(FileSpec::default().suppress_timestamp()) + .start()?; + + let final_hull = GrahamScan.convex_hull(polygon); + let steps = self.parse_logs_graham_scan().unwrap(); + + // TODO will ultimately want a config such that these could + // be specified in some configurable or at the very least + // more interpretable way? For now just hardcoding values + // for color scheme I think looks decent + let init_vertex_color = [255, 255, 255, 255]; + let polygon_color = [132, 90, 109, 255]; + let hull_color = [25, 100, 126, 255]; + let check_color = [242, 192, 53, 255]; + let valid_color = [52, 163, 82, 255]; + let invalid_color = [163, 0, 0, 255]; + + let mut frame: i64 = 0; + self.rec.set_time_sequence("frame", frame); + + self.visualize_nominal_polygon(polygon, name, polygon_color)?; + + // Show initial vertex establishing min angle order + let id_0 = polygon.vertex_ids()[0]; + let v_0 = polygon.get_vertex(&id_0).unwrap(); + self.rec.log( + format!("{name}/alg_init/init_vertex"), + &rerun::Points2D::new([(v_0.x as f32, v_0.y as f32)]) + .with_radii([1.0]) + .with_colors([init_vertex_color]) + .with_draw_order(100.0), + )?; + + let mut prev_step: Option<&ConvexHullTracerStep> = None; + for (i, step) in tracer.as_ref().unwrap().steps.iter().enumerate() { + if i == 0 { + // Show initial edge of hull + self.visualize_vertex_chain( + &polygon.get_vertices(step.hull_tail(2)), + &format!("{name}/hull_{i}"), + Some(0.8), + Some(hull_color), + Some(0.2), + Some(hull_color), + None, + false, + )?; + } else { + self.increment_frame(&mut frame); + + // Show highlighted edge used for angle test + let ids = prev_step + .expect("Prev step should exist i > 0") + .hull_tail(2); + let v_origin = polygon.get_vertex(&ids[0]).unwrap(); + let v_head = polygon.get_vertex(&ids[1]).unwrap(); + self.rec.log( + format!("{name}/alg_{i}/check_edge"), + &rerun::Arrows2D::from_vectors([( + (v_head.x - v_origin.x) as f32, + (v_head.y - v_origin.y) as f32, + )]) + .with_origins([(v_origin.x as f32, v_origin.y as f32)]) + .with_radii([0.3]) + .with_colors([check_color]) + .with_draw_order(100.0), + )?; + + // 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(); + self.rec.log( + format!("{name}/alg_{i}/next_vertex"), + &rerun::Points2D::new([(n_v.x as f32, n_v.y as f32)]) + .with_radii([1.0]) + .with_colors([check_color]) + .with_draw_order(100.0), + )?; + + self.rec.log( + 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), + ]]) + .with_radii([0.1]) + .with_colors([init_vertex_color]), + )?; + + self.increment_frame(&mut frame); + 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 { + // 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 + // since the next vertex was accepted to the hull) + self.visualize_vertex_chain( + &polygon.get_vertices(step.hull_tail(3)), + &format!("{name}/alg_{i}/valid"), + Some(1.0), + Some(valid_color), + Some(0.3), + Some(valid_color), + Some(100.0), + false, + )?; + } else { + // 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); + self.visualize_vertex_chain( + &polygon.get_vertices(ids), + &format!("{name}/alg_{i}/invalid"), + Some(1.0), + Some(invalid_color), + Some(0.3), + Some(invalid_color), + Some(100.0), + false, + )?; + } + + // Show computed hull for this step + self.increment_frame(&mut frame); + self.visualize_vertex_chain( + &polygon.get_vertices(step.hull.clone()), + &format!("{name}/hull_{i}"), + Some(0.8), + Some(hull_color), + Some(0.2), + Some(hull_color), + None, + true, + )?; + } + prev_step = Some(step); + + self.clear_recursive(format!("{name}/alg_{i}"))?; + // Clear out old hull visualizations + if i > 0 { + self.clear_recursive(format!("{name}/hull_{}", i - 1))?; + } + } + + self.increment_frame(&mut frame); + self.visualize_final_hull(&final_hull, name, hull_color)?; + Ok(()) } - // TODO will need to make this more general beyond incrmental - pub fn parse_logs(&self) -> Result, Box> { + pub fn parse_logs_graham_scan( + &self, + ) -> Result, Box> { + // TODO will need to figure out how to handle the filename logs go to + let file = File::open("visualizer.log")?; + let reader = BufReader::new(file); + let re = Regex::new(r"DEBUG \[.*\] \w+ (?.*)").unwrap(); + + 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"].to_string()) + { + steps.push(step); + } + } + } + + Ok(steps) + } + + pub fn parse_logs_incremental( + &self, + ) -> Result, Box> { // TODO will need to figure out how to handle the filename logs go to let file = File::open("visualizer.log")?; let reader = BufReader::new(file); @@ -347,7 +376,6 @@ impl RerunVisualizer { if let Ok(step) = serde_hjson::from_str::(&caps["data"].to_string()) { - println!("GOOD {step:?}"); steps.push(step); } } @@ -367,7 +395,7 @@ impl RerunVisualizer { .start()?; let final_hull = Incremental.convex_hull(polygon); - let steps = self.parse_logs().unwrap(); + let steps = self.parse_logs_incremental().unwrap(); let mut frame: i64 = 0; self.rec.set_time_sequence("frame", frame); diff --git a/src/convex_hull.rs b/src/convex_hull.rs index 039652d..c1965c4 100644 --- a/src/convex_hull.rs +++ b/src/convex_hull.rs @@ -1,42 +1,15 @@ use itertools::Itertools; use log::{debug, info, trace}; use ordered_float::OrderedFloat as OF; -use serde::Deserialize; -use std::fmt; use crate::{ + alg_step::{GrahamScanStep, IncrementalStep}, data_structure::{HullSet, Stack}, geometry::Geometry, polygon::Polygon, vertex::VertexId, }; -#[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(()) - } -} - pub trait ConvexHullComputer { fn convex_hull(&self, polygon: &Polygon) -> Polygon; } @@ -149,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(), - // ..Default::default() - // }); - // } + 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; } 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; From 9b532a97b4227a4694731c2d8693e4259fb035ed Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 12 Oct 2025 15:05:57 -0400 Subject: [PATCH 07/10] Get graham scan vis working from log parsing --- src/alg_step.rs | 10 ++++++++++ src/bin/run_visualizer.rs | 29 ++++++++++++++--------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/alg_step.rs b/src/alg_step.rs index 23e5372..a5b106f 100644 --- a/src/alg_step.rs +++ b/src/alg_step.rs @@ -21,6 +21,16 @@ impl fmt::Display for GrahamScanStep { } } +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, diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index 6dc782b..bb92506 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -8,7 +8,8 @@ use itertools::Itertools; use random_color::RandomColor; use geometer::{ - convex_hull::{ConvexHullComputer, GrahamScan, Incremental, IncrementalStep, QuickHull}, + alg_step::{GrahamScanStep, IncrementalStep}, + convex_hull::{ConvexHullComputer, GrahamScan, Incremental, QuickHull}, error::FileError, geometry::Geometry, polygon::Polygon, @@ -219,8 +220,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( @@ -255,11 +256,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), @@ -269,7 +270,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]), @@ -279,8 +280,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 @@ -299,7 +299,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"), @@ -315,7 +315,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), @@ -342,17 +342,16 @@ impl RerunVisualizer { pub fn parse_logs_graham_scan( &self, - ) -> Result, Box> { + ) -> Result, Box> { // TODO will need to figure out how to handle the filename logs go to let file = File::open("visualizer.log")?; let reader = BufReader::new(file); let re = Regex::new(r"DEBUG \[.*\] \w+ (?.*)").unwrap(); - let mut steps = Vec::::new(); + 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"].to_string()) + if let Ok(step) = serde_hjson::from_str::(&caps["data"].to_string()) { steps.push(step); } From adc0b651b88fc1911536308b261b2674cc697f52 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 12 Oct 2025 16:03:24 -0400 Subject: [PATCH 08/10] Add parse error type --- src/alg_step.rs | 43 ++++++++++++++++++++ src/bin/run_visualizer.rs | 85 ++++++++++++--------------------------- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/src/alg_step.rs b/src/alg_step.rs index a5b106f..673327a 100644 --- a/src/alg_step.rs +++ b/src/alg_step.rs @@ -1,8 +1,51 @@ +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"].to_string()) { + steps.push(step); + } + } + } + + Ok(steps) +} + #[derive(Debug, Default, Deserialize)] pub struct GrahamScanStep { pub idx: usize, diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index bb92506..53000e4 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -1,14 +1,10 @@ -use regex::Regex; -use std::fs::File; -use std::io::{BufRead, BufReader}; - use clap::{Parser, ValueEnum}; -use flexi_logger::{FileSpec, FlexiLoggerError, Logger}; +use flexi_logger::{FileSpec, Logger}; use itertools::Itertools; use random_color::RandomColor; use geometer::{ - alg_step::{GrahamScanStep, IncrementalStep}, + alg_step::{parse_steps_from_logs, GrahamScanStep, IncrementalStep, StepParseError}, convex_hull::{ConvexHullComputer, GrahamScan, Incremental, QuickHull}, error::FileError, geometry::Geometry, @@ -26,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 { @@ -46,8 +42,9 @@ struct Args { #[derive(Debug)] pub enum VisualizationError { File(FileError), - FlexiLoggerError(FlexiLoggerError), + FlexiLogger(flexi_logger::FlexiLoggerError), Rerun(rerun::RecordingStreamError), + StepParse(StepParseError), } impl From for VisualizationError { @@ -56,15 +53,21 @@ 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: flexi_logger::FlexiLoggerError) -> Self { - VisualizationError::FlexiLoggerError(value) +impl From for VisualizationError { + fn from(value: StepParseError) -> Self { + VisualizationError::StepParse(value) } } @@ -185,13 +188,15 @@ impl RerunVisualizer { polygon: &Polygon, name: &String, ) -> Result<(), VisualizationError> { - // TODO need to set different log filename + let file_spec = FileSpec::default() + .directory("/tmp") + .basename("visualizer_graham_scan"); Logger::try_with_str("debug")? - .log_to_file(FileSpec::default().suppress_timestamp()) + .log_to_file(file_spec.clone()) .start()?; let final_hull = GrahamScan.convex_hull(polygon); - let steps = self.parse_logs_graham_scan().unwrap(); + 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 @@ -340,61 +345,21 @@ impl RerunVisualizer { Ok(()) } - pub fn parse_logs_graham_scan( - &self, - ) -> Result, Box> { - // TODO will need to figure out how to handle the filename logs go to - let file = File::open("visualizer.log")?; - let reader = BufReader::new(file); - let re = Regex::new(r"DEBUG \[.*\] \w+ (?.*)").unwrap(); - - 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"].to_string()) - { - steps.push(step); - } - } - } - - Ok(steps) - } - - pub fn parse_logs_incremental( - &self, - ) -> Result, Box> { - // TODO will need to figure out how to handle the filename logs go to - let file = File::open("visualizer.log")?; - let reader = BufReader::new(file); - let re = Regex::new(r"DEBUG \[.*\] \w+ (?.*)").unwrap(); - - 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"].to_string()) - { - steps.push(step); - } - } - } - - Ok(steps) - } - pub fn visualize_convex_hull_incremental( &self, polygon: &Polygon, name: &String, ) -> Result<(), VisualizationError> { - // TODO need to set different log filename + let file_spec = FileSpec::default() + .directory("/tmp") + .basename("visualizer_incremental"); Logger::try_with_str("debug")? - .log_to_file(FileSpec::default().suppress_timestamp()) + .log_to_file(file_spec.clone()) .start()?; let final_hull = Incremental.convex_hull(polygon); - let steps = self.parse_logs_incremental().unwrap(); + 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); From 042706e7db28811c7e2e3480f00771050b397c4d Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 12 Oct 2025 16:12:25 -0400 Subject: [PATCH 09/10] Fixes for clippy --- src/alg_step.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alg_step.rs b/src/alg_step.rs index 673327a..51d82c2 100644 --- a/src/alg_step.rs +++ b/src/alg_step.rs @@ -37,7 +37,7 @@ where 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"].to_string()) { + if let Ok(step) = serde_hjson::from_str::(&caps["data"]) { steps.push(step); } } From 5f509fe265e0a0676db1993bb89c3c243f832bb1 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 12 Oct 2025 16:12:44 -0400 Subject: [PATCH 10/10] Fix benchmark calls for updated API --- benches/convex_hull.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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();