refactor(router/navmesh): Factor out some repeating code

This commit is contained in:
Mikolaj Wielgus 2025-07-08 01:29:40 +02:00 committed by mikolaj
parent ebd115c3dd
commit 9742740b9e
2 changed files with 114 additions and 79 deletions

View File

@ -24,7 +24,7 @@ use topola::{
layout::poly::MakePolygon, layout::poly::MakePolygon,
math::{Circle, RotationSense}, math::{Circle, RotationSense},
router::{ router::{
navmesh::{BinavnodeNodeIndex, NavnodeIndex}, navmesh::{BinavnodeNodeIndex, NavmeshTriangulationConstraint, NavnodeIndex},
ng::pie, ng::pie,
}, },
}; };
@ -443,7 +443,9 @@ impl Viewport {
if let Some(thetastar) = activity.maybe_thetastar() { if let Some(thetastar) = activity.maybe_thetastar() {
let navmesh = thetastar.graph(); let navmesh = thetastar.graph();
for (from_weight, to_weight) in navmesh.constraints() { for NavmeshTriangulationConstraint(from_weight, to_weight) in
navmesh.constraints()
{
let from = from_weight.pos + [100.0, 100.0].into(); let from = from_weight.pos + [100.0, 100.0].into();
let to = to_weight.pos + [100.0, 100.0].into(); let to = to_weight.pos + [100.0, 100.0].into();

View File

@ -27,6 +27,7 @@ use crate::{
graph::{GetMaybeNet, MakePrimitive, PrimitiveIndex}, graph::{GetMaybeNet, MakePrimitive, PrimitiveIndex},
primitive::{GetCore, GetJoints, MakePrimitiveShape, Primitive}, primitive::{GetCore, GetJoints, MakePrimitiveShape, Primitive},
rules::AccessRules, rules::AccessRules,
seg::{FixedSegIndex, LoneLooseSegIndex, SegIndex, SeqLooseSegIndex},
Drawing, Drawing,
}, },
geometry::{shape::AccessShape, GetLayer}, geometry::{shape::AccessShape, GetLayer},
@ -107,12 +108,28 @@ impl From<TrianvertexNodeIndex> for BinavnodeNodeIndex {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct TrianvertexWeight { pub struct TrianvertexWeight {
pub node: TrianvertexNodeIndex, pub node: TrianvertexNodeIndex,
pub pos: Point, pub pos: Point,
} }
impl TrianvertexWeight {
fn new_from_fixed_dot(layout: &Layout<impl AccessRules>, dot: FixedDotIndex) -> Self {
Self {
node: dot.into(),
pos: dot.primitive(layout.drawing()).shape().center(),
}
}
fn new_from_fixed_bend(layout: &Layout<impl AccessRules>, bend: FixedBendIndex) -> Self {
Self {
node: bend.into(),
pos: bend.primitive(layout.drawing()).shape().center(),
}
}
}
impl GetTrianvertexNodeIndex<TrianvertexNodeIndex> for TrianvertexWeight { impl GetTrianvertexNodeIndex<TrianvertexNodeIndex> for TrianvertexWeight {
fn node_index(&self) -> TrianvertexNodeIndex { fn node_index(&self) -> TrianvertexNodeIndex {
self.node self.node
@ -126,6 +143,49 @@ impl HasPosition for TrianvertexWeight {
} }
} }
#[derive(Clone)]
pub struct NavmeshTriangulationConstraint(pub TrianvertexWeight, pub TrianvertexWeight);
impl NavmeshTriangulationConstraint {
fn new_from_fixed_dot_pair(
layout: &Layout<impl AccessRules>,
from_dot: FixedDotIndex,
to_dot: FixedDotIndex,
) -> Self {
Self(
TrianvertexWeight::new_from_fixed_dot(layout, from_dot),
TrianvertexWeight::new_from_fixed_dot(layout, to_dot),
)
}
fn new_from_lone_loose_seg(layout: &Layout<impl AccessRules>, seg: LoneLooseSegIndex) -> Self {
let (from_dot, to_dot) = layout.drawing().primitive(seg).joints();
Self::new_from_fixed_dot_pair(layout, from_dot, to_dot)
}
fn new_from_seq_loose_seg(layout: &Layout<impl AccessRules>, seg: SeqLooseSegIndex) -> Self {
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();
Self::new_from_fixed_dot_pair(layout, from_dot, to_dot)
}
fn new_from_fixed_seg(layout: &Layout<impl AccessRules>, seg: FixedSegIndex) -> Self {
let (from_dot, to_dot) = layout.drawing().primitive(seg).joints();
Self::new_from_fixed_dot_pair(layout, from_dot, to_dot)
}
}
/// The terms "navnode" and "navmesh vertex", "navmesh node", "navigation /// The terms "navnode" and "navmesh vertex", "navmesh node", "navigation
/// vertex", "navigation node" are all equivalent. /// vertex", "navigation node" are all equivalent.
/// ///
@ -148,6 +208,8 @@ pub enum NavmeshError {
Insertion(#[from] InsertionError), Insertion(#[from] InsertionError),
} }
type NavmeshTriangulation = Triangulation<TrianvertexNodeIndex, TrianvertexWeight, ()>;
/// The navmesh holds the entire Topola's search space represented as a graph. /// The navmesh holds the entire Topola's search space represented as a graph.
/// Topola's routing works by navigating over this graph with a pathfinding /// Topola's routing works by navigating over this graph with a pathfinding
/// algorithm such as A* while drawing a track segment (always a cane except /// algorithm such as A* while drawing a track segment (always a cane except
@ -169,10 +231,10 @@ pub struct Navmesh {
/// Original triangulation stored for debugging purposes. /// Original triangulation stored for debugging purposes.
// XXX: Maybe have a way to compile this out in release? // XXX: Maybe have a way to compile this out in release?
triangulation: Triangulation<TrianvertexNodeIndex, TrianvertexWeight, ()>, triangulation: NavmeshTriangulation,
// Original triangulation constraints stored for debugging purposes. // Original triangulation constraints stored for debugging purposes.
// XXX: Maybe have a way to compile this out in release? // XXX: Maybe have a way to compile this out in release?
constraints: Vec<(TrianvertexWeight, TrianvertexWeight)>, constraints: Vec<NavmeshTriangulationConstraint>,
} }
impl Navmesh { impl Navmesh {
@ -183,8 +245,8 @@ impl Navmesh {
destination: FixedDotIndex, destination: FixedDotIndex,
options: RouterOptions, options: RouterOptions,
) -> Result<Self, NavmeshError> { ) -> Result<Self, NavmeshError> {
let mut triangulation: Triangulation<TrianvertexNodeIndex, TrianvertexWeight, ()> = let mut triangulation: NavmeshTriangulation =
Triangulation::new(layout.drawing().geometry().graph().node_bound()); NavmeshTriangulation::new(layout.drawing().geometry().graph().node_bound());
let mut constraints = vec![]; let mut constraints = vec![];
let layer = layout.drawing().primitive(origin).layer(); let layer = layout.drawing().primitive(origin).layer();
@ -200,62 +262,28 @@ impl Navmesh {
{ {
match node { match node {
PrimitiveIndex::FixedDot(dot) => { PrimitiveIndex::FixedDot(dot) => {
triangulation.add_vertex(TrianvertexWeight { triangulation
node: dot.into(), .add_vertex(TrianvertexWeight::new_from_fixed_dot(layout, dot))?;
pos: primitive.shape().center(),
})?;
} }
PrimitiveIndex::LoneLooseSeg(seg) => { PrimitiveIndex::LoneLooseSeg(seg) => {
let (from_dot, to_dot) = layout.drawing().primitive(seg).joints(); Self::add_constraint(
let (from_weight, to_weight) = ( &mut triangulation,
TrianvertexWeight { &mut constraints,
node: from_dot.into(), NavmeshTriangulationConstraint::new_from_lone_loose_seg(
pos: from_dot.primitive(layout.drawing()).shape().center(), layout, seg,
}, ),
TrianvertexWeight { )?;
node: to_dot.into(),
pos: to_dot.primitive(layout.drawing()).shape().center(),
},
);
triangulation
.add_constraint_edge(from_weight.clone(), to_weight.clone())?;
constraints.push((from_weight, to_weight));
} }
PrimitiveIndex::SeqLooseSeg(seg) => { PrimitiveIndex::SeqLooseSeg(seg) => {
let (from_joint, to_joint) = layout.drawing().primitive(seg).joints(); Self::add_constraint(
&mut triangulation,
let from_dot = match from_joint { &mut constraints,
DotIndex::Fixed(dot) => dot, NavmeshTriangulationConstraint::new_from_seq_loose_seg(layout, seg),
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();
let (from_weight, to_weight) = (
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(),
},
);
triangulation
.add_constraint_edge(from_weight.clone(), to_weight.clone())?;
constraints.push((from_weight, to_weight));
} }
PrimitiveIndex::FixedBend(bend) => { PrimitiveIndex::FixedBend(bend) => {
triangulation.add_vertex(TrianvertexWeight { triangulation
node: bend.into(), .add_vertex(TrianvertexWeight::new_from_fixed_bend(layout, bend))?;
pos: primitive.shape().center(),
})?;
} }
_ => (), _ => (),
} }
@ -272,33 +300,28 @@ impl Navmesh {
|| Some(primitive_net) != maybe_net || Some(primitive_net) != maybe_net
{ {
// If you have a band that was routed from a polygonal pad, // If you have a band that was routed from a polygonal pad,
// upon another routing some of the constraint edges created // when you will start a new routing some of the constraint
// from the loose segs band will intersect some of the // edges created from the loose segs of a band will
// constraint edges created from the fixed segs constituting // intersect some of the constraint edges created from the
// the pad boundary. // fixed segs constituting the pad boundary.
// //
// Such constraint intersections are erroneous and cause // Such constraint intersections are erroneous and cause
// Spade to throw a panic at runtime. So, to prevent this // Spade to throw a panic at runtime. So, to prevent this
// from occuring, we iterate over the layout for the second // from occuring, we iterate over the layout for the second
// time, after all the constraint edges from bands have // time, after all the constraint edges from bands have been
// been placed, and only then add constraint edges created // placed, and only then add constraint edges created from
// from fixed segs, but only ones that do not cause an // fixed segs that do not cause an intersection.
// intersection.
match node { match node {
PrimitiveIndex::FixedSeg(seg) => { PrimitiveIndex::FixedSeg(seg) => {
let (from_dot, to_dot) = layout.drawing().primitive(seg).joints(); let constraint =
NavmeshTriangulationConstraint::new_from_fixed_seg(layout, seg);
let from_weight = TrianvertexWeight { if !triangulation.intersects_constraint(&constraint.0, &constraint.1) {
node: from_dot.into(), Self::add_constraint(
pos: from_dot.primitive(layout.drawing()).shape().center(), &mut triangulation,
}; &mut constraints,
let to_weight = TrianvertexWeight { constraint,
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)?;
} }
} }
_ => (), _ => (),
@ -317,12 +340,22 @@ impl Navmesh {
) )
} }
fn add_constraint(
triangulation: &mut NavmeshTriangulation,
constraints: &mut Vec<NavmeshTriangulationConstraint>,
constraint: NavmeshTriangulationConstraint,
) -> Result<(), InsertionError> {
triangulation.add_constraint_edge(constraint.0, constraint.1)?;
constraints.push(constraint);
Ok(())
}
fn new_from_triangulation( fn new_from_triangulation(
layout: &Layout<impl AccessRules>, layout: &Layout<impl AccessRules>,
triangulation: Triangulation<TrianvertexNodeIndex, TrianvertexWeight, ()>, triangulation: NavmeshTriangulation,
origin: FixedDotIndex, origin: FixedDotIndex,
destination: FixedDotIndex, destination: FixedDotIndex,
constraints: Vec<(TrianvertexWeight, TrianvertexWeight)>, constraints: Vec<NavmeshTriangulationConstraint>,
options: RouterOptions, options: RouterOptions,
) -> Result<Self, NavmeshError> { ) -> Result<Self, NavmeshError> {
let mut graph: UnGraph<NavnodeWeight, (), usize> = UnGraph::default(); let mut graph: UnGraph<NavnodeWeight, (), usize> = UnGraph::default();