feat(autorouter/invoker): Create trait to display debug information on navmesh

This commit is contained in:
Mikolaj Wielgus 2025-05-01 01:58:03 +02:00
parent 83d9fce38c
commit f7cd817457
13 changed files with 233 additions and 59 deletions

View File

@ -114,4 +114,25 @@ impl<'a> Painter<'a> {
stroke, stroke,
)); ));
} }
pub fn paint_text(
&mut self,
pos: Point,
anchor: egui::Align2,
text: &str,
color: egui::epaint::Color32,
) {
let text = self.ui.painter().fonts(|fonts| {
egui::Shape::text(
fonts,
self.transform
.mul_pos([pos.x() as f32, -pos.y() as f32].into()),
anchor,
text,
egui::FontId::default(),
color,
)
});
self.ui.painter().add(text);
}
} }

View File

@ -11,7 +11,9 @@ use rstar::{Envelope, AABB};
use topola::{ use topola::{
autorouter::{ autorouter::{
execution::Command, execution::Command,
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles}, invoker::{
GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles,
},
}, },
board::AccessMesadata, board::AccessMesadata,
drawing::{ drawing::{
@ -22,6 +24,7 @@ use topola::{
graph::MakeRef, graph::MakeRef,
layout::{poly::MakePolygon, via::ViaWeight}, layout::{poly::MakePolygon, via::ViaWeight},
math::{Circle, RotationSense}, math::{Circle, RotationSense},
router::navmesh::NavvertexIndex,
}; };
use crate::{config::Config, menu_bar::MenuBar, painter::Painter, workspace::Workspace}; use crate::{config::Config, menu_bar::MenuBar, painter::Painter, workspace::Workspace};
@ -265,6 +268,50 @@ impl Viewport {
}; };
painter.paint_edge(from, to, stroke); painter.paint_edge(from, to, stroke);
if let Some(text) = activity
.navedge_debug_text((edge.source(), edge.target()))
{
painter.paint_text(
(from + to) / 2.0,
egui::Align2::LEFT_BOTTOM,
text,
egui::Color32::from_rgb(255, 255, 255),
);
}
}
for index in navmesh.graph().node_indices() {
let navvertex = NavvertexIndex(index);
if let Some(text) = activity.navvertex_debug_text(navvertex)
{
let mut pos = PrimitiveIndex::from(
navmesh.node_weight(navvertex).unwrap().node,
)
.primitive(board.layout().drawing())
.shape()
.center();
pos += match navmesh
.node_weight(navvertex)
.unwrap()
.maybe_sense
{
Some(RotationSense::Counterclockwise) => {
[0.0, 150.0].into()
}
Some(RotationSense::Clockwise) => {
[-0.0, -150.0].into()
}
None => [0.0, 0.0].into(),
};
painter.paint_text(
pos,
egui::Align2::LEFT_BOTTOM,
text,
egui::Color32::from_rgb(255, 255, 255),
);
}
} }
} }
} }

View File

@ -19,7 +19,7 @@ use crate::{
translator::Translator, translator::Translator,
}; };
/// A loaded design and associated structures /// A loaded design and associated structures.
pub struct Workspace { pub struct Workspace {
pub design: SpecctraDesign, pub design: SpecctraDesign,
pub appearance_panel: AppearancePanel, pub appearance_panel: AppearancePanel,
@ -50,7 +50,7 @@ impl Workspace {
interactor: Interactor::new(board).map_err(|err| { interactor: Interactor::new(board).map_err(|err| {
format!( format!(
"{}; {}", "{}; {}",
tr.text("tr-error_unable-to-initialize-overlay"), tr.text("tr-error-unable-to-initialize-overlay"),
err err
) )
})?, })?,

View File

@ -14,12 +14,16 @@ use crate::{
drawing::{band::BandTermsegIndex, graph::PrimitiveIndex, Collect}, drawing::{band::BandTermsegIndex, graph::PrimitiveIndex, Collect},
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
layout::LayoutEdit, layout::LayoutEdit,
router::{navcord::NavcordStepper, navmesh::Navmesh, RouteStepper, Router}, router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
RouteStepper, Router,
},
stepper::Step, stepper::Step,
}; };
use super::{ use super::{
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles}, invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
Autorouter, AutorouterError, AutorouterOptions, Autorouter, AutorouterError, AutorouterOptions,
}; };
@ -159,29 +163,37 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinu
} }
impl GetMaybeNavmesh for AutorouteExecutionStepper { impl GetMaybeNavmesh for AutorouteExecutionStepper {
/// Retrieves an optional reference to the navigation mesh from the current route.
fn maybe_navmesh(&self) -> Option<&Navmesh> { fn maybe_navmesh(&self) -> Option<&Navmesh> {
self.route.as_ref().map(|route| route.navmesh()) self.route.as_ref().map(|route| route.navmesh())
} }
} }
impl GetMaybeNavcord for AutorouteExecutionStepper { impl GetMaybeNavcord for AutorouteExecutionStepper {
/// Retrieves an optional reference to the navcord from the current route.
fn maybe_navcord(&self) -> Option<&NavcordStepper> { fn maybe_navcord(&self) -> Option<&NavcordStepper> {
self.route.as_ref().map(|route| route.navcord()) self.route.as_ref().map(|route| route.navcord())
} }
} }
impl GetGhosts for AutorouteExecutionStepper { impl GetGhosts for AutorouteExecutionStepper {
/// Retrieves ghost shapes from the current route.
fn ghosts(&self) -> &[PrimitiveShape] { fn ghosts(&self) -> &[PrimitiveShape] {
self.route.as_ref().map_or(&[], |route| route.ghosts()) self.route.as_ref().map_or(&[], |route| route.ghosts())
} }
} }
impl GetObstacles for AutorouteExecutionStepper { impl GetObstacles for AutorouteExecutionStepper {
/// Retrieves obstacles encountered during routing.
fn obstacles(&self) -> &[PrimitiveIndex] { fn obstacles(&self) -> &[PrimitiveIndex] {
self.route.as_ref().map_or(&[], |route| route.obstacles()) self.route.as_ref().map_or(&[], |route| route.obstacles())
} }
} }
impl GetNavmeshDebugTexts for AutorouteExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
// Add debug text here.
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
// Add debug text here.
None
}
}

View File

@ -14,13 +14,17 @@ use crate::{
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
geometry::{primitive::PrimitiveShape, shape::MeasureLength}, geometry::{primitive::PrimitiveShape, shape::MeasureLength},
graph::MakeRef, graph::MakeRef,
router::{navcord::NavcordStepper, navmesh::Navmesh}, router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
stepper::Step, stepper::Step,
}; };
use super::{ use super::{
autoroute::{AutorouteContinueStatus, AutorouteExecutionStepper}, autoroute::{AutorouteContinueStatus, AutorouteExecutionStepper},
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles}, invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
remove_bands::RemoveBandsExecutionStepper,
Autorouter, AutorouterError, AutorouterOptions, Autorouter, AutorouterError, AutorouterOptions,
}; };
@ -123,3 +127,13 @@ impl GetObstacles for CompareDetoursExecutionStepper {
self.autoroute.obstacles() self.autoroute.obstacles()
} }
} }
impl GetNavmeshDebugTexts for CompareDetoursExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
None
}
}

View File

@ -35,7 +35,13 @@ pub enum Command {
MeasureLength(BandSelection), MeasureLength(BandSelection),
} }
#[enum_dispatch(GetMaybeNavmesh, GetMaybeNavcord, GetGhosts, GetObstacles)] #[enum_dispatch(
GetMaybeNavmesh,
GetMaybeNavcord,
GetGhosts,
GetObstacles,
GetNavmeshDebugTexts
)]
pub enum ExecutionStepper { pub enum ExecutionStepper {
Autoroute(AutorouteExecutionStepper), Autoroute(AutorouteExecutionStepper),
PlaceVia(PlaceViaExecutionStepper), PlaceVia(PlaceViaExecutionStepper),

View File

@ -15,7 +15,10 @@ use crate::{
board::AccessMesadata, board::AccessMesadata,
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
geometry::{edit::ApplyGeometryEdit, primitive::PrimitiveShape}, geometry::{edit::ApplyGeometryEdit, primitive::PrimitiveShape},
router::{navcord::NavcordStepper, navmesh::Navmesh}, router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
stepper::Step, stepper::Step,
}; };
@ -31,63 +34,59 @@ use super::{
}; };
#[enum_dispatch] #[enum_dispatch]
/// Getter trait to obtain Navigation Mesh /// Trait for getting the navmesh to display it on the debug overlay.
///
/// Navigation Mesh is possible routes between
/// two points
pub trait GetMaybeNavmesh { pub trait GetMaybeNavmesh {
/// Returns Navigation Mesh if possible
fn maybe_navmesh(&self) -> Option<&Navmesh>; fn maybe_navmesh(&self) -> Option<&Navmesh>;
} }
#[enum_dispatch] #[enum_dispatch]
/// Getter for Navigation Cord /// Trait for getting the navcord to display it on the debug overlay.
///
/// Navigation Cord is the possible path of
/// ongoing autorouting process
pub trait GetMaybeNavcord { pub trait GetMaybeNavcord {
/// Gets the Navigation Cord if possible
fn maybe_navcord(&self) -> Option<&NavcordStepper>; fn maybe_navcord(&self) -> Option<&NavcordStepper>;
} }
#[enum_dispatch] #[enum_dispatch]
/// Requires Ghosts implementations /// Trait for getting ghosts to display on the debug overlay. Ghosts are the
/// /// shapes that Topola attempted to create but failed due to them infringing on
/// Ghosts are possible shapes of routing /// other shapes.
/// bands
pub trait GetGhosts { pub trait GetGhosts {
/// Retrieves the ghosts associated with the execution.
fn ghosts(&self) -> &[PrimitiveShape]; fn ghosts(&self) -> &[PrimitiveShape];
} }
#[enum_dispatch] #[enum_dispatch]
/// Getter for the Obstacles /// Trait for getting the obstacles that prevented Topola from creating
/// /// new objects (the shapes of these objects can be obtained with the above
/// Obstacles are shapes of existing bands /// `GetGhosts` trait), for the purpose of displaying these obstacles on the
/// to be avoided by the new band /// debug overlay.
pub trait GetObstacles { pub trait GetObstacles {
/// Returns possible Obstacles
fn obstacles(&self) -> &[PrimitiveIndex]; fn obstacles(&self) -> &[PrimitiveIndex];
} }
/// Error types that can occur during the invocation of commands #[enum_dispatch]
/// Trait for getting text strings with debug information attached to navmesh
/// edges and vertices.
pub trait GetNavmeshDebugTexts {
fn navvertex_debug_text(&self, navvertex: NavvertexIndex) -> Option<&str>;
fn navedge_debug_text(&self, navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str>;
}
/// Error types that can occur during the invocation of commands.
#[derive(Error, Debug, Clone)] #[derive(Error, Debug, Clone)]
pub enum InvokerError { pub enum InvokerError {
/// Wraps errors related to command history operations /// Wraps errors related to command history operations.
#[error(transparent)] #[error(transparent)]
History(#[from] HistoryError), History(#[from] HistoryError),
/// Wraps errors related to autorouter operations /// Wraps errors related to autorouter operations.
#[error(transparent)] #[error(transparent)]
Autorouter(#[from] AutorouterError), Autorouter(#[from] AutorouterError),
} }
#[derive(Getters, Dissolve)] #[derive(Getters, Dissolve)]
/// Structure that manages the execution and history of commands within the autorouting system
pub struct Invoker<M> { pub struct Invoker<M> {
/// Instance for executing desired autorouting commands /// Instance for executing desired autorouting commands.
pub(super) autorouter: Autorouter<M>, pub(super) autorouter: Autorouter<M>,
/// History of executed commands /// History of executed commands.
pub(super) history: History, pub(super) history: History,
/// Currently ongoing command type. /// Currently ongoing command type.
pub(super) ongoing_command: Option<Command>, pub(super) ongoing_command: Option<Command>,
@ -109,10 +108,10 @@ impl<M: AccessMesadata> Invoker<M> {
} }
//#[debug_requires(self.ongoing_command.is_none())] //#[debug_requires(self.ongoing_command.is_none())]
/// Executes a command, managing the command status and history /// Executes a command, managing the command status and history.
/// ///
/// This function is used to pass the [`Command`] to [`Invoker::execute_stepper`] /// This function is used to pass the [`Command`] to [`Invoker::execute_stepper`]
/// function, and control its execution status /// function, and control its execution status.
pub fn execute(&mut self, command: Command) -> Result<(), InvokerError> { pub fn execute(&mut self, command: Command) -> Result<(), InvokerError> {
let mut execute = self.execute_stepper(command)?; let mut execute = self.execute_stepper(command)?;
@ -127,9 +126,9 @@ impl<M: AccessMesadata> Invoker<M> {
} }
#[debug_requires(self.ongoing_command.is_none())] #[debug_requires(self.ongoing_command.is_none())]
/// Pass given command to be executed /// Pass given command to be executed.
/// ///
/// Function used to set given [`Command`] to ongoing state, dispatch and execute it /// Function used to set given [`Command`] to ongoing state, dispatch and execute it.
pub fn execute_stepper(&mut self, command: Command) -> Result<ExecutionStepper, InvokerError> { pub fn execute_stepper(&mut self, command: Command) -> Result<ExecutionStepper, InvokerError> {
let execute = self.dispatch_command(&command); let execute = self.dispatch_command(&command);
self.ongoing_command = Some(command); self.ongoing_command = Some(command);
@ -174,7 +173,7 @@ impl<M: AccessMesadata> Invoker<M> {
} }
#[debug_requires(self.ongoing_command.is_none())] #[debug_requires(self.ongoing_command.is_none())]
/// Undo last command /// Undo last command.
pub fn undo(&mut self) -> Result<(), InvokerError> { pub fn undo(&mut self) -> Result<(), InvokerError> {
let last_done = self.history.last_done()?; let last_done = self.history.last_done()?;
@ -186,7 +185,7 @@ impl<M: AccessMesadata> Invoker<M> {
} }
//#[debug_requires(self.ongoing_command.is_none())] //#[debug_requires(self.ongoing_command.is_none())]
/// Redo last command /// Redo last command.
pub fn redo(&mut self) -> Result<(), InvokerError> { pub fn redo(&mut self) -> Result<(), InvokerError> {
let last_undone = self.history.last_undone()?; let last_undone = self.history.last_undone()?;
@ -198,7 +197,7 @@ impl<M: AccessMesadata> Invoker<M> {
} }
#[debug_requires(self.ongoing_command.is_none())] #[debug_requires(self.ongoing_command.is_none())]
/// Replay last command /// Replay last command.
pub fn replay(&mut self, history: History) { pub fn replay(&mut self, history: History) {
let (done, undone) = history.dissolve(); let (done, undone) = history.dissolve();

View File

@ -11,11 +11,15 @@ use crate::{
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
geometry::{primitive::PrimitiveShape, shape::MeasureLength as MeasureLengthTrait}, geometry::{primitive::PrimitiveShape, shape::MeasureLength as MeasureLengthTrait},
graph::MakeRef, graph::MakeRef,
router::{navcord::NavcordStepper, navmesh::Navmesh}, router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
}; };
use super::{ use super::{
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles}, invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
remove_bands::RemoveBandsExecutionStepper,
selection::BandSelection, selection::BandSelection,
Autorouter, AutorouterError, Autorouter, AutorouterError,
}; };
@ -78,3 +82,13 @@ impl GetObstacles for MeasureLengthExecutionStepper {
&[] &[]
} }
} }
impl GetNavmeshDebugTexts for MeasureLengthExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
None
}
}

View File

@ -11,11 +11,14 @@ use crate::{
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
layout::{via::ViaWeight, LayoutEdit}, layout::{via::ViaWeight, LayoutEdit},
router::{navcord::NavcordStepper, navmesh::Navmesh}, router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
}; };
use super::{ use super::{
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles}, invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
Autorouter, AutorouterError, Autorouter, AutorouterError,
}; };
@ -75,3 +78,13 @@ impl GetObstacles for PlaceViaExecutionStepper {
&[] &[]
} }
} }
impl GetNavmeshDebugTexts for PlaceViaExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
None
}
}

View File

@ -9,11 +9,14 @@ use crate::{
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
layout::LayoutEdit, layout::LayoutEdit,
router::{navcord::NavcordStepper, navmesh::Navmesh}, router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
}; };
use super::{ use super::{
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles}, invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
selection::BandSelection, selection::BandSelection,
Autorouter, AutorouterError, Autorouter, AutorouterError,
}; };
@ -74,3 +77,13 @@ impl GetObstacles for RemoveBandsExecutionStepper {
&[] &[]
} }
} }
impl GetNavmeshDebugTexts for RemoveBandsExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
None
}
}

View File

@ -12,14 +12,18 @@ use crate::{
autorouter::{ autorouter::{
execution::ExecutionStepper, execution::ExecutionStepper,
invoker::{ invoker::{
GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles, Invoker, InvokerError, GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles,
Invoker, InvokerError,
}, },
}, },
board::AccessMesadata, board::AccessMesadata,
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
interactor::interaction::{InteractionError, InteractionStepper}, interactor::interaction::{InteractionError, InteractionStepper},
router::{navcord::NavcordStepper, navmesh::Navmesh}, router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
stepper::{Abort, Step}, stepper::{Abort, Step},
}; };
@ -44,7 +48,13 @@ pub enum ActivityError {
} }
/// An activity is either an interaction or an execution /// An activity is either an interaction or an execution
#[enum_dispatch(GetMaybeNavmesh, GetMaybeNavcord, GetGhosts, GetObstacles)] #[enum_dispatch(
GetMaybeNavmesh,
GetMaybeNavcord,
GetGhosts,
GetObstacles,
GetNavmeshDebugTexts
)]
pub enum ActivityStepper { pub enum ActivityStepper {
Interaction(InteractionStepper), Interaction(InteractionStepper),
Execution(ExecutionStepper), Execution(ExecutionStepper),
@ -137,3 +147,13 @@ impl GetObstacles for ActivityStepperWithStatus {
self.activity.obstacles() self.activity.obstacles()
} }
} }
impl GetNavmeshDebugTexts for ActivityStepperWithStatus {
fn navvertex_debug_text(&self, navvertex: NavvertexIndex) -> Option<&str> {
self.activity.navvertex_debug_text(navvertex)
}
fn navedge_debug_text(&self, navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
self.activity.navedge_debug_text(navedge)
}
}

View File

@ -7,11 +7,16 @@ use std::ops::ControlFlow;
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
autorouter::invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles}, autorouter::invoker::{
GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles,
},
board::AccessMesadata, board::AccessMesadata,
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
router::{navcord::NavcordStepper, navmesh::Navmesh}, router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
stepper::{Abort, Step}, stepper::{Abort, Step},
}; };
@ -70,3 +75,13 @@ impl GetObstacles for InteractionStepper {
todo!() todo!()
} }
} }
impl GetNavmeshDebugTexts for InteractionStepper {
fn navvertex_debug_text(&self, navvertex: NavvertexIndex) -> Option<&str> {
todo!()
}
fn navedge_debug_text(&self, navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
todo!()
}
}

View File

@ -39,7 +39,7 @@ use crate::{
use super::RouterOptions; use super::RouterOptions;
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
pub struct NavvertexIndex(NodeIndex<usize>); pub struct NavvertexIndex(pub NodeIndex<usize>);
impl core::fmt::Debug for NavvertexIndex { impl core::fmt::Debug for NavvertexIndex {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
@ -152,7 +152,7 @@ pub enum NavmeshError {
/// when going directly to destination) on the layout for each leap and /// when going directly to destination) on the layout for each leap and
/// along-edge crossing. /// along-edge crossing.
/// ///
/// The name "navmesh" is a shortening of "navigation mesh". /// The name "navmesh" is a blend of "navigation mesh".
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Navmesh { pub struct Navmesh {
graph: UnGraph<NavvertexWeight, (), usize>, graph: UnGraph<NavvertexWeight, (), usize>,