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 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"),
|
||||
|
|
|
|||
|
|
@ -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::<Vec<_>>()
|
||||
.into_boxed_slice();
|
||||
Self { visible }
|
||||
Self {
|
||||
visible,
|
||||
active_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, ctx: &egui::Context, board: &Board<impl AccessMesadata>) {
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<NodeIndex>;
|
||||
type GapComment = ();
|
||||
type Scalar = f64;
|
||||
}
|
||||
|
||||
pub type PieNavmesh = planar_incr_embed::navmesh::Navmesh<PieNavmeshBase>;
|
||||
|
||||
#[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<PieNavmesh>,
|
||||
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<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(
|
||||
&mut self,
|
||||
_board: &Board<impl AccessMesadata>,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -12,3 +12,5 @@ mod router;
|
|||
|
||||
pub use route::RouteStepper;
|
||||
pub use router::*;
|
||||
|
||||
pub use planar_incr_embed;
|
||||
|
|
|
|||
Loading…
Reference in New Issue