mirror of https://codeberg.org/topola/topola.git
feat(overlay,viewport): generation and rendering of topological navmesh
This commit is contained in:
parent
6eb941a137
commit
da3be763c6
|
|
@ -82,6 +82,7 @@ pub struct EditActions {
|
||||||
pub abort: Trigger,
|
pub abort: Trigger,
|
||||||
pub select_all: Trigger,
|
pub select_all: Trigger,
|
||||||
pub unselect_all: Trigger,
|
pub unselect_all: Trigger,
|
||||||
|
pub recalculate_topo_navmesh: Trigger,
|
||||||
pub remove_bands: Trigger,
|
pub remove_bands: Trigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,6 +119,10 @@ impl EditActions {
|
||||||
egui::Key::A,
|
egui::Key::A,
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
|
recalculate_topo_navmesh: Action::new_keyless(
|
||||||
|
tr.text("tr-menu-edit-recalculate-topo-navmesh"),
|
||||||
|
)
|
||||||
|
.into_trigger(),
|
||||||
remove_bands: Action::new(
|
remove_bands: Action::new(
|
||||||
tr.text("tr-menu-edit-remove-bands"),
|
tr.text("tr-menu-edit-remove-bands"),
|
||||||
egui::Modifiers::NONE,
|
egui::Modifiers::NONE,
|
||||||
|
|
@ -146,6 +151,7 @@ impl EditActions {
|
||||||
|
|
||||||
self.select_all.button(ctx, ui);
|
self.select_all.button(ctx, ui);
|
||||||
self.unselect_all.button(ctx, ui);
|
self.unselect_all.button(ctx, ui);
|
||||||
|
self.recalculate_topo_navmesh.button(ctx, ui);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
|
@ -205,6 +211,10 @@ impl ViewActions {
|
||||||
&mut menu_bar.show_navmesh,
|
&mut menu_bar.show_navmesh,
|
||||||
tr.text("tr-menu-view-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(
|
ui.checkbox(
|
||||||
&mut menu_bar.show_bboxes,
|
&mut menu_bar.show_bboxes,
|
||||||
tr.text("tr-menu-view-show-bboxes"),
|
tr.text("tr-menu-view-show-bboxes"),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ pub struct AppearancePanel {
|
||||||
// In1.Cu shall be #7fc87f (#d5ecd5 when selected).
|
// In1.Cu shall be #7fc87f (#d5ecd5 when selected).
|
||||||
// In2.Cu shall be #ce7d2c (#e8c39e when selected).
|
// In2.Cu shall be #ce7d2c (#e8c39e when selected).
|
||||||
pub visible: Box<[bool]>,
|
pub visible: Box<[bool]>,
|
||||||
|
|
||||||
|
pub active_layer: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppearancePanel {
|
impl AppearancePanel {
|
||||||
|
|
@ -18,7 +20,10 @@ impl AppearancePanel {
|
||||||
.take(layer_count)
|
.take(layer_count)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
Self { visible }
|
Self {
|
||||||
|
visible,
|
||||||
|
active_layer: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, ctx: &egui::Context, board: &Board<impl AccessMesadata>) {
|
pub fn update(&mut self, ctx: &egui::Context, board: &Board<impl AccessMesadata>) {
|
||||||
|
|
@ -33,7 +38,11 @@ impl AppearancePanel {
|
||||||
.layer_layername(layer)
|
.layer_layername(layer)
|
||||||
.unwrap_or("Unnamed layer");
|
.unwrap_or("Unnamed layer");
|
||||||
|
|
||||||
|
let old = *visible;
|
||||||
ui.checkbox(visible, layername);
|
ui.checkbox(visible, layername);
|
||||||
|
if old != *visible {
|
||||||
|
self.active_layer = layer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ pub struct MenuBar {
|
||||||
pub is_placing_via: bool,
|
pub is_placing_via: bool,
|
||||||
pub show_ratsnest: bool,
|
pub show_ratsnest: bool,
|
||||||
pub show_navmesh: bool,
|
pub show_navmesh: bool,
|
||||||
|
pub show_topo_navmesh: bool,
|
||||||
pub show_bboxes: bool,
|
pub show_bboxes: bool,
|
||||||
pub show_origin_destination: bool,
|
pub show_origin_destination: bool,
|
||||||
pub show_appearance_panel: bool,
|
pub show_appearance_panel: bool,
|
||||||
|
|
@ -45,6 +46,7 @@ impl MenuBar {
|
||||||
is_placing_via: false,
|
is_placing_via: false,
|
||||||
show_ratsnest: true,
|
show_ratsnest: true,
|
||||||
show_navmesh: false,
|
show_navmesh: false,
|
||||||
|
show_topo_navmesh: false,
|
||||||
show_bboxes: false,
|
show_bboxes: false,
|
||||||
show_origin_destination: false,
|
show_origin_destination: false,
|
||||||
show_appearance_panel: true,
|
show_appearance_panel: true,
|
||||||
|
|
@ -249,6 +251,15 @@ impl MenuBar {
|
||||||
workspace
|
workspace
|
||||||
.overlay
|
.overlay
|
||||||
.select_all(board, &workspace.appearance_panel);
|
.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(
|
} else if actions.place.place_via.consume_key_enabled(
|
||||||
ctx,
|
ctx,
|
||||||
ui,
|
ui,
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,24 @@ use topola::{
|
||||||
},
|
},
|
||||||
board::{AccessMesadata, Board},
|
board::{AccessMesadata, Board},
|
||||||
geometry::shape::AccessShape,
|
geometry::shape::AccessShape,
|
||||||
|
layout::NodeIndex,
|
||||||
|
router::planar_incr_embed,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::appearance_panel::AppearancePanel;
|
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<NodeIndex>;
|
||||||
|
type GapComment = ();
|
||||||
|
type Scalar = f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PieNavmesh = planar_incr_embed::navmesh::Navmesh<PieNavmeshBase>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum SelectionMode {
|
pub enum SelectionMode {
|
||||||
Addition,
|
Addition,
|
||||||
|
|
@ -27,6 +41,7 @@ pub enum SelectionMode {
|
||||||
pub struct Overlay {
|
pub struct Overlay {
|
||||||
ratsnest: Ratsnest,
|
ratsnest: Ratsnest,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
|
planar_incr_navmesh: Option<PieNavmesh>,
|
||||||
reselect_bbox: Option<(SelectionMode, Point)>,
|
reselect_bbox: Option<(SelectionMode, Point)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,6 +52,7 @@ impl Overlay {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ratsnest: Ratsnest::new(board.layout())?,
|
ratsnest: Ratsnest::new(board.layout())?,
|
||||||
selection: Selection::new(),
|
selection: Selection::new(),
|
||||||
|
planar_incr_navmesh: None,
|
||||||
reselect_bbox: None,
|
reselect_bbox: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -63,6 +79,58 @@ impl Overlay {
|
||||||
self.reselect_bbox = None;
|
self.reselect_bbox = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn recalculate_topo_navmesh(
|
||||||
|
&mut self,
|
||||||
|
board: &Board<impl AccessMesadata>,
|
||||||
|
appearance_panel: &AppearancePanel,
|
||||||
|
) {
|
||||||
|
use spade::Triangulation;
|
||||||
|
use topola::router::planar_incr_embed::navmesh::TrianVertex;
|
||||||
|
|
||||||
|
if let Ok(triangulation) =
|
||||||
|
spade::DelaunayTriangulation::<TrianVertex<NodeIndex, f64>>::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::<PieNavmeshBase>::from_triangulation(
|
||||||
|
&triangulation,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn drag_start(
|
pub fn drag_start(
|
||||||
&mut self,
|
&mut self,
|
||||||
_board: &Board<impl AccessMesadata>,
|
_board: &Board<impl AccessMesadata>,
|
||||||
|
|
@ -167,6 +235,10 @@ impl Overlay {
|
||||||
&self.selection
|
&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
|
/// Returns the currently selected bounding box of a bounding-box reselect
|
||||||
pub fn get_bbox_reselect(
|
pub fn get_bbox_reselect(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
if menu_bar.show_bboxes {
|
||||||
let root_bbox3d = board.layout().drawing().rtree().root().envelope();
|
let root_bbox3d = board.layout().drawing().rtree().root().envelope();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ tr-menu-edit-redo = Redo
|
||||||
tr-menu-edit-abort = Abort
|
tr-menu-edit-abort = Abort
|
||||||
tr-menu-edit-select-all = Select All
|
tr-menu-edit-select-all = Select All
|
||||||
tr-menu-edit-unselect-all = Unselect 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-edit-remove-bands = Remove Bands
|
||||||
|
|
||||||
tr-menu-view = View
|
tr-menu-view = View
|
||||||
tr-menu-view-zoom-to-fit = Zoom to Fit
|
tr-menu-view-zoom-to-fit = Zoom to Fit
|
||||||
tr-menu-view-show-ratsnest = Show Ratsnest
|
tr-menu-view-show-ratsnest = Show Ratsnest
|
||||||
tr-menu-view-show-navmesh = Show Navmesh
|
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-bboxes = Show BBoxes
|
||||||
tr-menu-view-show-origin-destination = Show Origin–Destination
|
tr-menu-view-show-origin-destination = Show Origin–Destination
|
||||||
tr-menu-view-show-layer-manager = Show Layer Manager
|
tr-menu-view-show-layer-manager = Show Layer Manager
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,5 @@ mod router;
|
||||||
|
|
||||||
pub use route::RouteStepper;
|
pub use route::RouteStepper;
|
||||||
pub use router::*;
|
pub use router::*;
|
||||||
|
|
||||||
|
pub use planar_incr_embed;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue