From da3be763c643ece093df50a22dbc49f25219aed6 Mon Sep 17 00:00:00 2001 From: Ellen Emilia Anna Zscheile Date: Wed, 8 Jan 2025 18:46:09 +0100 Subject: [PATCH] feat(overlay,viewport): generation and rendering of topological navmesh --- crates/topola-egui/src/actions.rs | 10 +++ crates/topola-egui/src/appearance_panel.rs | 11 +++- crates/topola-egui/src/menu_bar.rs | 11 ++++ crates/topola-egui/src/overlay.rs | 72 ++++++++++++++++++++++ crates/topola-egui/src/viewport.rs | 72 ++++++++++++++++++++++ locales/en-US/main.ftl | 2 + src/router/mod.rs | 2 + 7 files changed, 179 insertions(+), 1 deletion(-) diff --git a/crates/topola-egui/src/actions.rs b/crates/topola-egui/src/actions.rs index 3b01152..29bd17f 100644 --- a/crates/topola-egui/src/actions.rs +++ b/crates/topola-egui/src/actions.rs @@ -82,6 +82,7 @@ pub struct EditActions { pub abort: Trigger, pub select_all: Trigger, pub unselect_all: Trigger, + pub recalculate_topo_navmesh: Trigger, pub remove_bands: Trigger, } @@ -118,6 +119,10 @@ impl EditActions { egui::Key::A, ) .into_trigger(), + recalculate_topo_navmesh: Action::new_keyless( + tr.text("tr-menu-edit-recalculate-topo-navmesh"), + ) + .into_trigger(), remove_bands: Action::new( tr.text("tr-menu-edit-remove-bands"), egui::Modifiers::NONE, @@ -146,6 +151,7 @@ impl EditActions { self.select_all.button(ctx, ui); self.unselect_all.button(ctx, ui); + self.recalculate_topo_navmesh.button(ctx, ui); ui.separator(); @@ -205,6 +211,10 @@ impl ViewActions { &mut menu_bar.show_navmesh, tr.text("tr-menu-view-show-navmesh"), ); + ui.checkbox( + &mut menu_bar.show_topo_navmesh, + tr.text("tr-menu-view-show-topo-navmesh"), + ); ui.checkbox( &mut menu_bar.show_bboxes, tr.text("tr-menu-view-show-bboxes"), diff --git a/crates/topola-egui/src/appearance_panel.rs b/crates/topola-egui/src/appearance_panel.rs index ca4051d..c4b03d3 100644 --- a/crates/topola-egui/src/appearance_panel.rs +++ b/crates/topola-egui/src/appearance_panel.rs @@ -9,6 +9,8 @@ pub struct AppearancePanel { // In1.Cu shall be #7fc87f (#d5ecd5 when selected). // In2.Cu shall be #ce7d2c (#e8c39e when selected). pub visible: Box<[bool]>, + + pub active_layer: usize, } impl AppearancePanel { @@ -18,7 +20,10 @@ impl AppearancePanel { .take(layer_count) .collect::>() .into_boxed_slice(); - Self { visible } + Self { + visible, + active_layer: 0, + } } pub fn update(&mut self, ctx: &egui::Context, board: &Board) { @@ -33,7 +38,11 @@ impl AppearancePanel { .layer_layername(layer) .unwrap_or("Unnamed layer"); + let old = *visible; ui.checkbox(visible, layername); + if old != *visible { + self.active_layer = layer; + } } }); } diff --git a/crates/topola-egui/src/menu_bar.rs b/crates/topola-egui/src/menu_bar.rs index 05c62c7..eb81831 100644 --- a/crates/topola-egui/src/menu_bar.rs +++ b/crates/topola-egui/src/menu_bar.rs @@ -25,6 +25,7 @@ pub struct MenuBar { pub is_placing_via: bool, pub show_ratsnest: bool, pub show_navmesh: bool, + pub show_topo_navmesh: bool, pub show_bboxes: bool, pub show_origin_destination: bool, pub show_appearance_panel: bool, @@ -45,6 +46,7 @@ impl MenuBar { is_placing_via: false, show_ratsnest: true, show_navmesh: false, + show_topo_navmesh: false, show_bboxes: false, show_origin_destination: false, show_appearance_panel: true, @@ -249,6 +251,15 @@ impl MenuBar { workspace .overlay .select_all(board, &workspace.appearance_panel); + } else if actions + .edit + .recalculate_topo_navmesh + .consume_key_triggered(ctx, ui) + { + let board = workspace.interactor.invoker().autorouter().board(); + workspace + .overlay + .recalculate_topo_navmesh(board, &workspace.appearance_panel); } else if actions.place.place_via.consume_key_enabled( ctx, ui, diff --git a/crates/topola-egui/src/overlay.rs b/crates/topola-egui/src/overlay.rs index e8b4b62..067c5bb 100644 --- a/crates/topola-egui/src/overlay.rs +++ b/crates/topola-egui/src/overlay.rs @@ -13,10 +13,24 @@ use topola::{ }, board::{AccessMesadata, Board}, geometry::shape::AccessShape, + layout::NodeIndex, + router::planar_incr_embed, }; use crate::appearance_panel::AppearancePanel; +#[derive(Clone, Copy, Debug)] +pub struct PieNavmeshBase; + +impl planar_incr_embed::NavmeshBase for PieNavmeshBase { + type PrimalNodeIndex = NodeIndex; + type EtchedPath = planar_incr_embed::navmesh::EdgeIndex; + type GapComment = (); + type Scalar = f64; +} + +pub type PieNavmesh = planar_incr_embed::navmesh::Navmesh; + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SelectionMode { Addition, @@ -27,6 +41,7 @@ pub enum SelectionMode { pub struct Overlay { ratsnest: Ratsnest, selection: Selection, + planar_incr_navmesh: Option, reselect_bbox: Option<(SelectionMode, Point)>, } @@ -37,6 +52,7 @@ impl Overlay { Ok(Self { ratsnest: Ratsnest::new(board.layout())?, selection: Selection::new(), + planar_incr_navmesh: None, reselect_bbox: None, }) } @@ -63,6 +79,58 @@ impl Overlay { self.reselect_bbox = None; } + pub fn recalculate_topo_navmesh( + &mut self, + board: &Board, + appearance_panel: &AppearancePanel, + ) { + use spade::Triangulation; + use topola::router::planar_incr_embed::navmesh::TrianVertex; + + if let Ok(triangulation) = + spade::DelaunayTriangulation::>::bulk_load( + board + .layout() + .drawing() + .rtree() + .locate_in_envelope_intersecting(&AABB::<[f64; 3]>::from_corners( + [ + -f64::INFINITY, + -f64::INFINITY, + appearance_panel.active_layer as f64, + ], + [ + f64::INFINITY, + f64::INFINITY, + appearance_panel.active_layer as f64, + ], + )) + .map(|&geom| geom.data) + .filter_map(|node| { + board + .layout() + .center_of_compoundless_node(node) + .map(|pos| (node, pos)) + }) + .map(|(idx, pos)| TrianVertex { + idx, + pos: spade::mitigate_underflow(spade::Point2 { + x: pos.x(), + y: pos.y(), + }), + }) + .collect(), + ) + { + self.planar_incr_navmesh = Some( + planar_incr_embed::navmesh::NavmeshSer::::from_triangulation( + &triangulation, + ) + .into(), + ); + } + } + pub fn drag_start( &mut self, _board: &Board, @@ -167,6 +235,10 @@ impl Overlay { &self.selection } + pub fn planar_incr_navmesh(&self) -> Option<&PieNavmesh> { + self.planar_incr_navmesh.as_ref() + } + /// Returns the currently selected bounding box of a bounding-box reselect pub fn get_bbox_reselect( &self, diff --git a/crates/topola-egui/src/viewport.rs b/crates/topola-egui/src/viewport.rs index 6917c19..7247d16 100644 --- a/crates/topola-egui/src/viewport.rs +++ b/crates/topola-egui/src/viewport.rs @@ -274,6 +274,78 @@ impl Viewport { } } + if menu_bar.show_topo_navmesh { + if let Some(navmesh) = workspace.overlay.planar_incr_navmesh() { + // calculate dual node position approximations + use std::collections::BTreeMap; + use topola::geometry::shape::AccessShape; + use topola::router::planar_incr_embed::NavmeshIndex; + let mut map = BTreeMap::new(); + let resolve_primal = |p: &topola::layout::NodeIndex| { + board.layout().node_shape(*p).center() + }; + let root_bbox3d = + board.layout().drawing().rtree().root().envelope(); + + let root_bbox = AABB::<[f64; 2]>::from_corners( + [root_bbox3d.lower()[0], root_bbox3d.lower()[1]].into(), + [root_bbox3d.upper()[0], root_bbox3d.upper()[1]].into(), + ); + + for (nidx, node) in &*navmesh.nodes { + if let NavmeshIndex::Dual(didx) = nidx { + map.insert( + didx, + geo::point! { x: node.pos.x, y: node.pos.y }, + ); + } + } + for (eidx, edge) in &*navmesh.edges { + // TODO: display edge contents, too + let (a, b) = eidx.clone().into(); + let mut got_primal = false; + let a_pos = match a { + NavmeshIndex::Primal(p) => { + got_primal = true; + resolve_primal(&p) + } + NavmeshIndex::Dual(d) => match map.get(&d) { + None => continue, + Some(x) => x.clone(), + }, + }; + let b_pos = match b { + NavmeshIndex::Primal(p) => { + got_primal = true; + resolve_primal(&p) + } + NavmeshIndex::Dual(d) => match map.get(&d) { + None => continue, + Some(x) => x.clone(), + }, + }; + let edge_len = navmesh.edge_paths[edge.1].len(); + use egui::Color32; + let stroke = if edge_len == 0 { + egui::Stroke::new( + 1.0, + if got_primal { + Color32::from_rgb(255, 175, 0) + } else { + Color32::from_rgb(159, 255, 33) + }, + ) + } else { + egui::Stroke::new( + 1.5 + (edge_len as f32).atan(), + Color32::from_rgb(250, 250, 0), + ) + }; + painter.paint_edge(a_pos, b_pos, stroke); + } + } + } + if menu_bar.show_bboxes { let root_bbox3d = board.layout().drawing().rtree().root().envelope(); diff --git a/locales/en-US/main.ftl b/locales/en-US/main.ftl index 63e6cbe..e61160d 100644 --- a/locales/en-US/main.ftl +++ b/locales/en-US/main.ftl @@ -15,12 +15,14 @@ tr-menu-edit-redo = Redo tr-menu-edit-abort = Abort tr-menu-edit-select-all = Select All tr-menu-edit-unselect-all = Unselect All +tr-menu-edit-recalculate-topo-navmesh = Recalculate Topological Navmesh tr-menu-edit-remove-bands = Remove Bands tr-menu-view = View tr-menu-view-zoom-to-fit = Zoom to Fit tr-menu-view-show-ratsnest = Show Ratsnest tr-menu-view-show-navmesh = Show Navmesh +tr-menu-view-show-topo-navmesh = Show Topological Navmesh tr-menu-view-show-bboxes = Show BBoxes tr-menu-view-show-origin-destination = Show Origin–Destination tr-menu-view-show-layer-manager = Show Layer Manager diff --git a/src/router/mod.rs b/src/router/mod.rs index 5ac13f5..b864800 100644 --- a/src/router/mod.rs +++ b/src/router/mod.rs @@ -12,3 +12,5 @@ mod router; pub use route::RouteStepper; pub use router::*; + +pub use planar_incr_embed;