From d22473f4d408d4b3ddf93b987fdb7d05286e0cd6 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Mon, 13 Oct 2025 10:45:45 -0400 Subject: [PATCH 1/7] Incorporate logs into visualizer for graham scan --- src/bin/run_visualizer.rs | 44 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index 53000e4..c5cfa85 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -271,6 +271,16 @@ impl RerunVisualizer { .with_draw_order(100.0), )?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!( + "Checking angle between vector {} -> {} and vertex {}", + v_origin.id, v_head.id, new_id + )) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(check_color), + )?; + self.rec.log( format!("{name}/alg/next_vertex_marker"), &rerun::LineStrips2D::new([[ @@ -300,13 +310,23 @@ impl RerunVisualizer { Some(100.0), false, )?; + + self.rec.log( + "logs", + &rerun::TextLog::new(format!( + "Pushing valid vertex to hull stack: {}", + new_id + )) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(valid_color), + )?; } 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(new_id); self.visualize_vertex_chain( - &polygon.get_vertices(ids), + &polygon.get_vertices(ids.clone()), &format!("{name}/alg_{i}/invalid"), Some(1.0), Some(invalid_color), @@ -315,6 +335,16 @@ impl RerunVisualizer { Some(100.0), false, )?; + + self.rec.log( + "logs", + &rerun::TextLog::new(format!( + "Popping invalid vertex from hull stack: {}", + ids[1] + )) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(invalid_color), + )?; } // Show computed hull for this step @@ -329,6 +359,12 @@ impl RerunVisualizer { None, true, )?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!("Current hull stack: {:?}", step.hull_ids)) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(hull_color), + )?; } prev_step = Some(step); @@ -341,6 +377,12 @@ impl RerunVisualizer { self.increment_frame(&mut frame); self.visualize_final_hull(&final_hull, name, hull_color)?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!("Final hull: {:?}", final_hull.vertex_ids())) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(hull_color), + )?; Ok(()) } From e4b6e6dcfbfc40688cb5af3654f23c92ef5df407 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Mon, 13 Oct 2025 11:37:48 -0400 Subject: [PATCH 2/7] Get graham visualization working with labels and tweaked colors --- src/bin/run_visualizer.rs | 57 ++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index c5cfa85..0594f85 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -92,18 +92,23 @@ impl RerunVisualizer { edge_color: Option<[u8; 4]>, draw_order: Option, close_chain: bool, + show_labels: bool, ) -> Result<(), VisualizationError> { let vertex_radius = vertex_radius.unwrap_or(1.0); let vertex_color = vertex_color.unwrap_or(RandomColor::new().to_rgba_array()); let draw_order = draw_order.unwrap_or(30.0); - self.rec.log( - format!("{name}/vertices"), - &rerun::Points2D::new(vertices.iter().map(|v| (v.x as f32, v.y as f32))) - .with_radii([vertex_radius]) - .with_colors([vertex_color]) - .with_draw_order(draw_order), - )?; + let mut points = rerun::Points2D::new(vertices.iter().map(|v| (v.x as f32, v.y as f32))) + .with_radii([vertex_radius]) + .with_colors([vertex_color]) + .with_draw_order(draw_order); + + if show_labels { + let vertex_ids = vertices.iter().map(|v| v.id.to_string()).collect_vec(); + points = points.with_labels(vertex_ids); + } + + self.rec.log(format!("{name}/vertices"), &points)?; let edge_radius = edge_radius.unwrap_or(0.1); let edge_color = edge_color.unwrap_or(RandomColor::new().to_rgba_array()); @@ -145,6 +150,7 @@ impl RerunVisualizer { Some(polygon_color), None, true, + false, )?; for (i, mesh) in rerun_meshes.iter().enumerate() { @@ -178,6 +184,7 @@ impl RerunVisualizer { Some(hull_color), Some(100.0), true, + false, )?; Ok(()) @@ -204,10 +211,10 @@ impl RerunVisualizer { // 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 hull_color = [72, 125, 219, 255]; let check_color = [242, 192, 53, 255]; let valid_color = [52, 163, 82, 255]; - let invalid_color = [163, 0, 0, 255]; + let invalid_color = [235, 64, 52, 255]; let mut frame: i64 = 0; self.rec.set_time_sequence("frame", frame); @@ -222,7 +229,8 @@ impl RerunVisualizer { &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), + .with_draw_order(100.0) + .with_labels([id_0.to_string()]), )?; let mut prev_step: Option<&GrahamScanStep> = None; @@ -238,6 +246,7 @@ impl RerunVisualizer { Some(hull_color), None, false, + false, )?; } else { self.increment_frame(&mut frame); @@ -257,7 +266,8 @@ impl RerunVisualizer { .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), + .with_draw_order(100.0) + .with_labels([format!("{} -> {}", v_origin.id, v_head.id)]), )?; // Show next vertex used for angle test @@ -268,7 +278,8 @@ impl RerunVisualizer { &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), + .with_draw_order(100.0) + .with_labels([new_id.to_string()]), )?; self.rec.log( @@ -309,6 +320,7 @@ impl RerunVisualizer { Some(valid_color), Some(100.0), false, + true, )?; self.rec.log( @@ -320,6 +332,8 @@ impl RerunVisualizer { .with_level(rerun::TextLogLevel::DEBUG) .with_color(valid_color), )?; + + self.increment_frame(&mut frame); } else { // Render final edge on stack to next vertex as invalid // right turn @@ -334,6 +348,7 @@ impl RerunVisualizer { Some(invalid_color), Some(100.0), false, + true, )?; self.rec.log( @@ -345,10 +360,20 @@ impl RerunVisualizer { .with_level(rerun::TextLogLevel::DEBUG) .with_color(invalid_color), )?; + + self.increment_frame(&mut frame); + // Keep visualization of vertex being checked for next iter + self.rec.log( + format!("{name}/alg_{}/next_vertex", i + 1), + &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) + .with_labels([new_id.to_string()]), + )?; } // Show computed hull for this step - self.increment_frame(&mut frame); self.visualize_vertex_chain( &polygon.get_vertices(step.hull_ids.clone()), &format!("{name}/hull_{i}"), @@ -358,6 +383,7 @@ impl RerunVisualizer { Some(hull_color), None, true, + true, )?; self.rec.log( "logs", @@ -451,6 +477,7 @@ impl RerunVisualizer { Some(ut_color), Some(90.0), false, + false, )?; self.visualize_vertex_chain( @@ -462,6 +489,7 @@ impl RerunVisualizer { Some(lt_color), Some(90.0), false, + false, )?; } @@ -476,6 +504,7 @@ impl RerunVisualizer { Some(hull_color), Some(50.0), true, + false, )?; self.clear_recursive(format!("{name}/alg_{i}"))?; @@ -506,6 +535,7 @@ impl RerunVisualizer { Some(polygon_color), Some(10.0), true, + false, ) } @@ -524,6 +554,7 @@ impl RerunVisualizer { Some(hull_color), Some(200.0), true, + true, ) } From ea665a192b27f7e1befa1ea287c29178102d8483 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Mon, 13 Oct 2025 13:28:22 -0400 Subject: [PATCH 3/7] Add logs for incremental vis and update colors and labels --- src/bin/run_visualizer.rs | 42 +++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index 0594f85..29d0856 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -437,10 +437,10 @@ impl RerunVisualizer { // more interpretable way? For now just hardcoding values // for color scheme I think looks decent let polygon_color = [132, 90, 109, 255]; - let hull_color = [25, 100, 126, 255]; + let hull_color = [72, 125, 219, 255]; let new_vertex_color = [242, 192, 53, 255]; - let ut_color = [52, 163, 82, 255]; - let lt_color = [163, 0, 0, 255]; + let ut_color = [212, 70, 110, 255]; + let lt_color = [242, 138, 27, 255]; self.visualize_nominal_polygon(polygon, name, polygon_color)?; @@ -461,7 +461,16 @@ impl RerunVisualizer { &rerun::Points2D::new([(new_v.x as f32, new_v.y as f32)]) .with_radii([1.0]) .with_colors([new_vertex_color]) - .with_draw_order(100.0), + .with_draw_order(100.0) + .with_labels([new_id.to_string()]), + )?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!( + "Connecting vertex {new_id} to hull with upper/lower tangents" + )) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(new_vertex_color), )?; self.increment_frame(&mut frame); @@ -477,9 +486,14 @@ impl RerunVisualizer { Some(ut_color), Some(90.0), false, - false, + true, + )?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!("Upper tangent: {new_id} -> {ut_id}")) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(ut_color), )?; - self.visualize_vertex_chain( &polygon.get_vertices(vec![lt_id, new_id]), &format!("{name}/alg_{i}/lower_tangent"), @@ -489,7 +503,13 @@ impl RerunVisualizer { Some(lt_color), Some(90.0), false, - false, + true, + )?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!("Lower tangent: {new_id} -> {lt_id}")) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(lt_color), )?; } @@ -504,7 +524,13 @@ impl RerunVisualizer { Some(hull_color), Some(50.0), true, - false, + true, + )?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!("Current hull stack: {:?}", step.hull_ids)) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(hull_color), )?; self.clear_recursive(format!("{name}/alg_{i}"))?; From d328d76be27bb205b198e832e41ec72601dd1830 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 2 Nov 2025 09:17:50 -0500 Subject: [PATCH 4/7] Add blueprint --- resources/geometer.rbl | Bin 0 -> 49279 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/geometer.rbl diff --git a/resources/geometer.rbl b/resources/geometer.rbl new file mode 100644 index 0000000000000000000000000000000000000000..1d30f757a103152b37afc36ac53d741db4751633 GIT binary patch literal 49279 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%0DdwZPC+KcPi{p_3mT~BFN1Nve^(XlU+7@#$f6J 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 U{K+p6`THNY540>^ge(940ogmtasU7T literal 0 HcmV?d00001 From 0bf44842efb9cd310ab332094847f2eed33da2ec Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 2 Nov 2025 09:22:08 -0500 Subject: [PATCH 5/7] Update blueprint --- resources/geometer.rbl | Bin 49279 -> 49385 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/geometer.rbl b/resources/geometer.rbl index 1d30f757a103152b37afc36ac53d741db4751633..2b4b59b835e83e0542203315a0c2d08fb3c7bb7c 100644 GIT binary patch delta 5521 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 RA=-9=ZOyqywI3cU{a^Agh}-}G delta 5475 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 OpVY(InO!@UE&o5{9*ar< From 7daf94258908fbb0fcbc9a941ec5c8c6683cc44f Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 2 Nov 2025 09:53:01 -0500 Subject: [PATCH 6/7] Add better initial vis corresponding with logs --- src/bin/run_visualizer.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index 29d0856..d4d6ecb 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -221,6 +221,15 @@ impl RerunVisualizer { self.visualize_nominal_polygon(polygon, name, polygon_color)?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!("Polygon to compute convex hull for",)) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(polygon_color), + )?; + + self.increment_frame(&mut frame); + // Show initial vertex establishing min angle order let id_0 = polygon.vertex_ids()[0]; let v_0 = polygon.get_vertex(&id_0).unwrap(); @@ -246,7 +255,17 @@ impl RerunVisualizer { Some(hull_color), None, false, - false, + true, + )?; + + self.rec.log( + "logs", + &rerun::TextLog::new(format!( + "Initialized with hull edge {} -> {}", + step.hull_ids[0], step.hull_ids[1] + )) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(hull_color), )?; } else { self.increment_frame(&mut frame); @@ -444,6 +463,13 @@ impl RerunVisualizer { self.visualize_nominal_polygon(polygon, name, polygon_color)?; + self.rec.log( + "logs", + &rerun::TextLog::new(format!("Polygon to compute convex hull for",)) + .with_level(rerun::TextLogLevel::DEBUG) + .with_color(polygon_color), + )?; + // 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 From e5d3da1f22b5306a299af6f784ab10c737272422 Mon Sep 17 00:00:00 2001 From: Adam Conkey Date: Sun, 2 Nov 2025 14:12:33 -0500 Subject: [PATCH 7/7] Fix for clippy --- src/bin/run_visualizer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/run_visualizer.rs b/src/bin/run_visualizer.rs index d4d6ecb..aafefdc 100644 --- a/src/bin/run_visualizer.rs +++ b/src/bin/run_visualizer.rs @@ -223,7 +223,7 @@ impl RerunVisualizer { self.rec.log( "logs", - &rerun::TextLog::new(format!("Polygon to compute convex hull for",)) + &rerun::TextLog::new("Polygon to compute convex hull for") .with_level(rerun::TextLogLevel::DEBUG) .with_color(polygon_color), )?; @@ -465,7 +465,7 @@ impl RerunVisualizer { self.rec.log( "logs", - &rerun::TextLog::new(format!("Polygon to compute convex hull for",)) + &rerun::TextLog::new("Polygon to compute convex hull for") .with_level(rerun::TextLogLevel::DEBUG) .with_color(polygon_color), )?;