mirror of https://codeberg.org/topola/topola.git
feat(router/navmesh): Add constraint edges for loose segs
This does not work entirely correctly. I will investigate in subsequent commits.
This commit is contained in:
parent
274ad166c1
commit
f3245b9607
|
|
@ -166,6 +166,7 @@ pub struct ViewActions {
|
||||||
pub zoom_to_fit: Switch,
|
pub zoom_to_fit: Switch,
|
||||||
pub show_ratsnest: Switch,
|
pub show_ratsnest: Switch,
|
||||||
pub show_navmesh: Switch,
|
pub show_navmesh: Switch,
|
||||||
|
pub show_triangulation: Switch,
|
||||||
pub show_pathfinding_scores: Switch,
|
pub show_pathfinding_scores: Switch,
|
||||||
pub show_topo_navmesh: Switch,
|
pub show_topo_navmesh: Switch,
|
||||||
pub show_bboxes: Switch,
|
pub show_bboxes: Switch,
|
||||||
|
|
@ -179,6 +180,8 @@ impl ViewActions {
|
||||||
zoom_to_fit: Action::new_keyless(tr.text("tr-menu-view-zoom-to-fit")).into_switch(),
|
zoom_to_fit: Action::new_keyless(tr.text("tr-menu-view-zoom-to-fit")).into_switch(),
|
||||||
show_ratsnest: Action::new_keyless(tr.text("tr-menu-view-show-ratsnest")).into_switch(),
|
show_ratsnest: Action::new_keyless(tr.text("tr-menu-view-show-ratsnest")).into_switch(),
|
||||||
show_navmesh: Action::new_keyless(tr.text("tr-menu-view-show-navmesh")).into_switch(),
|
show_navmesh: Action::new_keyless(tr.text("tr-menu-view-show-navmesh")).into_switch(),
|
||||||
|
show_triangulation: Action::new_keyless(tr.text("tr-menu-view-show-triangulation"))
|
||||||
|
.into_switch(),
|
||||||
show_pathfinding_scores: Action::new_keyless(
|
show_pathfinding_scores: Action::new_keyless(
|
||||||
tr.text("tr-menu-view-show-pathfinding-scores"),
|
tr.text("tr-menu-view-show-pathfinding-scores"),
|
||||||
)
|
)
|
||||||
|
|
@ -211,6 +214,8 @@ impl ViewActions {
|
||||||
ui.add_enabled_ui(have_workspace, |ui| {
|
ui.add_enabled_ui(have_workspace, |ui| {
|
||||||
self.show_ratsnest.checkbox(ui, &mut menu_bar.show_ratsnest);
|
self.show_ratsnest.checkbox(ui, &mut menu_bar.show_ratsnest);
|
||||||
self.show_navmesh.checkbox(ui, &mut menu_bar.show_navmesh);
|
self.show_navmesh.checkbox(ui, &mut menu_bar.show_navmesh);
|
||||||
|
self.show_triangulation
|
||||||
|
.checkbox(ui, &mut menu_bar.show_triangulation);
|
||||||
self.show_pathfinding_scores
|
self.show_pathfinding_scores
|
||||||
.checkbox(ui, &mut menu_bar.show_pathfinding_scores);
|
.checkbox(ui, &mut menu_bar.show_pathfinding_scores);
|
||||||
self.show_topo_navmesh
|
self.show_topo_navmesh
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,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_triangulation: bool,
|
||||||
pub show_pathfinding_scores: bool,
|
pub show_pathfinding_scores: bool,
|
||||||
pub show_topo_navmesh: bool,
|
pub show_topo_navmesh: bool,
|
||||||
pub show_bboxes: bool,
|
pub show_bboxes: bool,
|
||||||
|
|
@ -50,6 +51,7 @@ impl MenuBar {
|
||||||
is_placing_via: false,
|
is_placing_via: false,
|
||||||
show_ratsnest: true,
|
show_ratsnest: true,
|
||||||
show_navmesh: false,
|
show_navmesh: false,
|
||||||
|
show_triangulation: false,
|
||||||
show_pathfinding_scores: false,
|
show_pathfinding_scores: false,
|
||||||
show_topo_navmesh: false,
|
show_topo_navmesh: false,
|
||||||
show_bboxes: false,
|
show_bboxes: false,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,10 @@ use topola::{
|
||||||
},
|
},
|
||||||
layout::poly::MakePolygon,
|
layout::poly::MakePolygon,
|
||||||
math::{Circle, RotationSense},
|
math::{Circle, RotationSense},
|
||||||
router::{navmesh::NavnodeIndex, ng::pie},
|
router::{
|
||||||
|
navmesh::{BinavnodeNodeIndex, NavnodeIndex},
|
||||||
|
ng::pie,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -36,8 +39,6 @@ pub struct Viewport {
|
||||||
/// how much should a single arrow key press scroll
|
/// how much should a single arrow key press scroll
|
||||||
pub kbd_scroll_delta_factor: f32,
|
pub kbd_scroll_delta_factor: f32,
|
||||||
pub scheduled_zoom_to_fit: bool,
|
pub scheduled_zoom_to_fit: bool,
|
||||||
|
|
||||||
update_counter: f32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Viewport {
|
impl Viewport {
|
||||||
|
|
@ -46,7 +47,6 @@ impl Viewport {
|
||||||
transform: egui::emath::TSTransform::new([0.0, 0.0].into(), 0.01),
|
transform: egui::emath::TSTransform::new([0.0, 0.0].into(), 0.01),
|
||||||
kbd_scroll_delta_factor: 5.0,
|
kbd_scroll_delta_factor: 5.0,
|
||||||
scheduled_zoom_to_fit: false,
|
scheduled_zoom_to_fit: false,
|
||||||
update_counter: 0.0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,8 +271,8 @@ impl Viewport {
|
||||||
|
|
||||||
if menu_bar.show_navmesh {
|
if menu_bar.show_navmesh {
|
||||||
if let Some(activity) = workspace.interactor.maybe_activity() {
|
if let Some(activity) = workspace.interactor.maybe_activity() {
|
||||||
if let Some(astar) = activity.maybe_thetastar() {
|
if let Some(thetastar) = activity.maybe_thetastar() {
|
||||||
let navmesh = astar.graph();
|
let navmesh = thetastar.graph();
|
||||||
|
|
||||||
for edge in navmesh.edge_references() {
|
for edge in navmesh.edge_references() {
|
||||||
let mut from = PrimitiveIndex::from(
|
let mut from = PrimitiveIndex::from(
|
||||||
|
|
@ -376,13 +376,13 @@ impl Viewport {
|
||||||
|
|
||||||
if menu_bar.show_pathfinding_scores {
|
if menu_bar.show_pathfinding_scores {
|
||||||
//TODO "{astar.scores[index]} ({astar.estimate_scores[index]}) (...)"
|
//TODO "{astar.scores[index]} ({astar.estimate_scores[index]}) (...)"
|
||||||
let score_text = astar
|
let score_text = thetastar
|
||||||
.scores()
|
.scores()
|
||||||
.get(&navnode)
|
.get(&navnode)
|
||||||
.map_or_else(String::new, |s| {
|
.map_or_else(String::new, |s| {
|
||||||
format!("g={:.2}", s)
|
format!("g={:.2}", s)
|
||||||
});
|
});
|
||||||
let estimate_score_text = astar
|
let estimate_score_text = thetastar
|
||||||
.estimate_scores()
|
.estimate_scores()
|
||||||
.get(&navnode)
|
.get(&navnode)
|
||||||
.map_or_else(String::new, |s| {
|
.map_or_else(String::new, |s| {
|
||||||
|
|
@ -406,6 +406,38 @@ impl Viewport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if menu_bar.show_triangulation {
|
||||||
|
if let Some(activity) = workspace.interactor.maybe_activity() {
|
||||||
|
if let Some(thetastar) = activity.maybe_thetastar() {
|
||||||
|
let navmesh = thetastar.graph();
|
||||||
|
|
||||||
|
for edge in navmesh.triangulation().edge_references() {
|
||||||
|
let from = PrimitiveIndex::from(BinavnodeNodeIndex::from(
|
||||||
|
edge.source(),
|
||||||
|
))
|
||||||
|
.primitive(board.layout().drawing())
|
||||||
|
.shape()
|
||||||
|
.center();
|
||||||
|
let to = PrimitiveIndex::from(BinavnodeNodeIndex::from(
|
||||||
|
edge.target(),
|
||||||
|
))
|
||||||
|
.primitive(board.layout().drawing())
|
||||||
|
.shape()
|
||||||
|
.center();
|
||||||
|
|
||||||
|
painter.paint_edge(
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
egui::Stroke::new(
|
||||||
|
1.0,
|
||||||
|
egui::Color32::from_rgb(255, 255, 255),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if menu_bar.show_topo_navmesh {
|
if menu_bar.show_topo_navmesh {
|
||||||
if let Some(navmesh) = workspace
|
if let Some(navmesh) = workspace
|
||||||
.interactor
|
.interactor
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ 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-triangulation = Show Triangulation
|
||||||
tr-menu-view-show-pathfinding-scores = Show Pathfinding Scores
|
tr-menu-view-show-pathfinding-scores = Show Pathfinding Scores
|
||||||
tr-menu-view-show-topo-navmesh = Show Topological Navmesh
|
tr-menu-view-show-topo-navmesh = Show Topological Navmesh
|
||||||
tr-menu-view-show-bboxes = Show BBoxes
|
tr-menu-view-show-bboxes = Show BBoxes
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use derive_getters::Getters;
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use geo::Point;
|
use geo::Point;
|
||||||
use petgraph::{
|
use petgraph::{
|
||||||
|
|
@ -21,10 +22,10 @@ use thiserror::Error;
|
||||||
use crate::{
|
use crate::{
|
||||||
drawing::{
|
drawing::{
|
||||||
bend::{FixedBendIndex, LooseBendIndex},
|
bend::{FixedBendIndex, LooseBendIndex},
|
||||||
dot::FixedDotIndex,
|
dot::{DotIndex, FixedDotIndex},
|
||||||
gear::{GearIndex, GetNextGear},
|
gear::{GearIndex, GetNextGear},
|
||||||
graph::{GetMaybeNet, MakePrimitive, PrimitiveIndex},
|
graph::{GetMaybeNet, MakePrimitive, PrimitiveIndex},
|
||||||
primitive::{GetJoints, MakePrimitiveShape, Primitive},
|
primitive::{GetCore, GetJoints, MakePrimitiveShape, Primitive},
|
||||||
rules::AccessRules,
|
rules::AccessRules,
|
||||||
Drawing,
|
Drawing,
|
||||||
},
|
},
|
||||||
|
|
@ -92,14 +93,14 @@ impl From<BinavnodeNodeIndex> for GearIndex {
|
||||||
/// The name "trianvertex" is a shortening of "triangulation vertex".
|
/// The name "trianvertex" is a shortening of "triangulation vertex".
|
||||||
#[enum_dispatch(GetPetgraphIndex, MakePrimitive)]
|
#[enum_dispatch(GetPetgraphIndex, MakePrimitive)]
|
||||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
enum TrianvertexNodeIndex {
|
pub enum TrianvertexNodeIndex {
|
||||||
FixedDot(FixedDotIndex),
|
FixedDot(FixedDotIndex),
|
||||||
FixedBend(FixedBendIndex),
|
FixedBend(FixedBendIndex),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TrianvertexNodeIndex> for BinavnodeNodeIndex {
|
impl From<TrianvertexNodeIndex> for BinavnodeNodeIndex {
|
||||||
fn from(vertex: TrianvertexNodeIndex) -> Self {
|
fn from(trianvertex: TrianvertexNodeIndex) -> Self {
|
||||||
match vertex {
|
match trianvertex {
|
||||||
TrianvertexNodeIndex::FixedDot(dot) => BinavnodeNodeIndex::FixedDot(dot),
|
TrianvertexNodeIndex::FixedDot(dot) => BinavnodeNodeIndex::FixedDot(dot),
|
||||||
TrianvertexNodeIndex::FixedBend(bend) => BinavnodeNodeIndex::FixedBend(bend),
|
TrianvertexNodeIndex::FixedBend(bend) => BinavnodeNodeIndex::FixedBend(bend),
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +108,7 @@ impl From<TrianvertexNodeIndex> for BinavnodeNodeIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct TrianvertexWeight {
|
pub struct TrianvertexWeight {
|
||||||
pub node: TrianvertexNodeIndex,
|
pub node: TrianvertexNodeIndex,
|
||||||
pub pos: Point,
|
pub pos: Point,
|
||||||
}
|
}
|
||||||
|
|
@ -154,13 +155,21 @@ pub enum NavmeshError {
|
||||||
/// along-edge crossing.
|
/// along-edge crossing.
|
||||||
///
|
///
|
||||||
/// The name "navmesh" is a blend of "navigation mesh".
|
/// The name "navmesh" is a blend of "navigation mesh".
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone, Getters)]
|
||||||
pub struct Navmesh {
|
pub struct Navmesh {
|
||||||
graph: UnGraph<NavnodeWeight, (), usize>,
|
graph: UnGraph<NavnodeWeight, (), usize>,
|
||||||
|
#[getter(skip)]
|
||||||
origin: FixedDotIndex,
|
origin: FixedDotIndex,
|
||||||
|
#[getter(skip)]
|
||||||
origin_navnode: NavnodeIndex,
|
origin_navnode: NavnodeIndex,
|
||||||
|
#[getter(skip)]
|
||||||
destination: FixedDotIndex,
|
destination: FixedDotIndex,
|
||||||
|
#[getter(skip)]
|
||||||
destination_navnode: NavnodeIndex,
|
destination_navnode: NavnodeIndex,
|
||||||
|
|
||||||
|
/// Original triangulation stored for debugging purposes.
|
||||||
|
// XXX: Maybe have a way to compile this out in release?
|
||||||
|
triangulation: Triangulation<TrianvertexNodeIndex, TrianvertexWeight, ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Navmesh {
|
impl Navmesh {
|
||||||
|
|
@ -192,7 +201,7 @@ impl Navmesh {
|
||||||
pos: primitive.shape().center(),
|
pos: primitive.shape().center(),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
PrimitiveIndex::FixedSeg(seg) => {
|
PrimitiveIndex::LoneLooseSeg(seg) => {
|
||||||
let (from_dot, to_dot) = layout.drawing().primitive(seg).joints();
|
let (from_dot, to_dot) = layout.drawing().primitive(seg).joints();
|
||||||
|
|
||||||
triangulation.add_constraint_edge(
|
triangulation.add_constraint_edge(
|
||||||
|
|
@ -206,6 +215,32 @@ impl Navmesh {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
PrimitiveIndex::SeqLooseSeg(seg) => {
|
||||||
|
let (from_joint, to_joint) = layout.drawing().primitive(seg).joints();
|
||||||
|
|
||||||
|
let from_dot = match from_joint {
|
||||||
|
DotIndex::Fixed(dot) => dot,
|
||||||
|
DotIndex::Loose(dot) => {
|
||||||
|
let bend = layout.drawing().primitive(dot).bend();
|
||||||
|
|
||||||
|
layout.drawing().primitive(bend).core()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let to_bend = layout.drawing().primitive(to_joint).bend();
|
||||||
|
let to_dot = layout.drawing().primitive(to_bend).core();
|
||||||
|
|
||||||
|
triangulation.add_constraint_edge(
|
||||||
|
TrianvertexWeight {
|
||||||
|
node: from_dot.into(),
|
||||||
|
pos: from_dot.primitive(layout.drawing()).shape().center(),
|
||||||
|
},
|
||||||
|
TrianvertexWeight {
|
||||||
|
node: to_dot.into(),
|
||||||
|
pos: to_dot.primitive(layout.drawing()).shape().center(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
PrimitiveIndex::FixedBend(bend) => {
|
PrimitiveIndex::FixedBend(bend) => {
|
||||||
triangulation.add_vertex(TrianvertexWeight {
|
triangulation.add_vertex(TrianvertexWeight {
|
||||||
node: bend.into(),
|
node: bend.into(),
|
||||||
|
|
@ -218,6 +253,50 @@ impl Navmesh {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for node in layout.drawing().layer_primitive_nodes(layer) {
|
||||||
|
let primitive = node.primitive(layout.drawing());
|
||||||
|
|
||||||
|
if let Some(primitive_net) = primitive.maybe_net() {
|
||||||
|
if node == origin.into()
|
||||||
|
|| node == destination.into()
|
||||||
|
|| Some(primitive_net) != maybe_net
|
||||||
|
{
|
||||||
|
// If you have a band that was routed from a polygonal pad,
|
||||||
|
// upon another routing some of the constraint edges created
|
||||||
|
// from the loose segs band will intersect some of the
|
||||||
|
// constraint edges created from the fixed segs constituting
|
||||||
|
// the pad boundary.
|
||||||
|
//
|
||||||
|
// Such constraint intersections are erroneous and cause
|
||||||
|
// Spade to throw a panic at runtime. So, to prevent this
|
||||||
|
// from occuring, we iterate over the layout for the second
|
||||||
|
// time, after all the constraint edges from bands have
|
||||||
|
// been placed, and only then add constraint edges created
|
||||||
|
// from fixed segs, but only ones that do not cause an
|
||||||
|
// intersection.
|
||||||
|
match node {
|
||||||
|
PrimitiveIndex::FixedSeg(seg) => {
|
||||||
|
let (from_dot, to_dot) = layout.drawing().primitive(seg).joints();
|
||||||
|
|
||||||
|
let from_weight = TrianvertexWeight {
|
||||||
|
node: from_dot.into(),
|
||||||
|
pos: from_dot.primitive(layout.drawing()).shape().center(),
|
||||||
|
};
|
||||||
|
let to_weight = TrianvertexWeight {
|
||||||
|
node: to_dot.into(),
|
||||||
|
pos: to_dot.primitive(layout.drawing()).shape().center(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !triangulation.intersects_constraint(&from_weight, &to_weight) {
|
||||||
|
triangulation.add_constraint_edge(from_weight, to_weight)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self::new_from_triangulation(layout, triangulation, origin, destination, options)
|
Self::new_from_triangulation(layout, triangulation, origin, destination, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,6 +397,7 @@ impl Navmesh {
|
||||||
origin_navnode: NavnodeIndex(origin_navnode.unwrap()),
|
origin_navnode: NavnodeIndex(origin_navnode.unwrap()),
|
||||||
destination,
|
destination,
|
||||||
destination_navnode: NavnodeIndex(destination_navnode.unwrap()),
|
destination_navnode: NavnodeIndex(destination_navnode.unwrap()),
|
||||||
|
triangulation,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,11 +422,6 @@ impl Navmesh {
|
||||||
.push((navnode1, navnode2));
|
.push((navnode1, navnode2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the navmesh's underlying petgraph graph structure.
|
|
||||||
pub fn graph(&self) -> &UnGraph<NavnodeWeight, (), usize> {
|
|
||||||
&self.graph
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the origin node.
|
/// Returns the origin node.
|
||||||
pub fn origin(&self) -> FixedDotIndex {
|
pub fn origin(&self) -> FixedDotIndex {
|
||||||
self.origin
|
self.origin
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,31 @@ impl<I: GetPetgraphIndex, VW: GetTrianvertexNodeIndex<I> + HasPosition, EW: Defa
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_constraint_edge(&mut self, from: VW, to: VW) -> Result<bool, InsertionError> {
|
pub fn add_constraint_edge(&mut self, from: VW, to: VW) -> Result<bool, InsertionError> {
|
||||||
self.cdt.add_constraint_edge(from, to)
|
let from_index = from.node_index().petgraph_index().index();
|
||||||
|
let to_index = to.node_index().petgraph_index().index();
|
||||||
|
|
||||||
|
// It is possible for one or both constraint edge endpoint vertices to
|
||||||
|
// not exist in the triangulation even after everything has been added.
|
||||||
|
// This can happen if the constraint was formed from a band wrapped
|
||||||
|
// over a polygonal pad that is the routing origin or destination, since
|
||||||
|
// in such situation the vertices of the pad boundary are not added to
|
||||||
|
// the triangulation.
|
||||||
|
//
|
||||||
|
// To prevent this from causing a panic at runtime, we idempotently add
|
||||||
|
// the constraint edge endpoint vertices to triangulation before adding
|
||||||
|
// the edge itself.
|
||||||
|
self.add_vertex(from)?;
|
||||||
|
self.add_vertex(to)?;
|
||||||
|
|
||||||
|
Ok(self.cdt.add_constraint(
|
||||||
|
self.trianvertex_to_handle[from_index].unwrap(),
|
||||||
|
self.trianvertex_to_handle[to_index].unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersects_constraint(&self, from: &VW, to: &VW) -> bool {
|
||||||
|
self.cdt
|
||||||
|
.intersects_constraint(from.position(), to.position())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weight(&self, vertex: I) -> &VW {
|
pub fn weight(&self, vertex: I) -> &VW {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue