From 9742740b9eba732bb97f91ab4687de1abf04e48f Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Tue, 8 Jul 2025 01:29:40 +0200 Subject: [PATCH] refactor(router/navmesh): Factor out some repeating code --- crates/topola-egui/src/viewport.rs | 6 +- src/router/navmesh.rs | 187 +++++++++++++++++------------ 2 files changed, 114 insertions(+), 79 deletions(-) diff --git a/crates/topola-egui/src/viewport.rs b/crates/topola-egui/src/viewport.rs index dc5a996..e410c31 100644 --- a/crates/topola-egui/src/viewport.rs +++ b/crates/topola-egui/src/viewport.rs @@ -24,7 +24,7 @@ use topola::{ layout::poly::MakePolygon, math::{Circle, RotationSense}, router::{ - navmesh::{BinavnodeNodeIndex, NavnodeIndex}, + navmesh::{BinavnodeNodeIndex, NavmeshTriangulationConstraint, NavnodeIndex}, ng::pie, }, }; @@ -443,7 +443,9 @@ impl Viewport { if let Some(thetastar) = activity.maybe_thetastar() { 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 to = to_weight.pos + [100.0, 100.0].into(); diff --git a/src/router/navmesh.rs b/src/router/navmesh.rs index d6558a6..85825f4 100644 --- a/src/router/navmesh.rs +++ b/src/router/navmesh.rs @@ -27,6 +27,7 @@ use crate::{ graph::{GetMaybeNet, MakePrimitive, PrimitiveIndex}, primitive::{GetCore, GetJoints, MakePrimitiveShape, Primitive}, rules::AccessRules, + seg::{FixedSegIndex, LoneLooseSegIndex, SegIndex, SeqLooseSegIndex}, Drawing, }, geometry::{shape::AccessShape, GetLayer}, @@ -107,12 +108,28 @@ impl From for BinavnodeNodeIndex { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct TrianvertexWeight { pub node: TrianvertexNodeIndex, pub pos: Point, } +impl TrianvertexWeight { + fn new_from_fixed_dot(layout: &Layout, dot: FixedDotIndex) -> Self { + Self { + node: dot.into(), + pos: dot.primitive(layout.drawing()).shape().center(), + } + } + + fn new_from_fixed_bend(layout: &Layout, bend: FixedBendIndex) -> Self { + Self { + node: bend.into(), + pos: bend.primitive(layout.drawing()).shape().center(), + } + } +} + impl GetTrianvertexNodeIndex for TrianvertexWeight { fn node_index(&self) -> TrianvertexNodeIndex { 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, + 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, 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, 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, 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 /// vertex", "navigation node" are all equivalent. /// @@ -148,6 +208,8 @@ pub enum NavmeshError { Insertion(#[from] InsertionError), } +type NavmeshTriangulation = Triangulation; + /// 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 /// 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. // XXX: Maybe have a way to compile this out in release? - triangulation: Triangulation, + triangulation: NavmeshTriangulation, // Original triangulation constraints stored for debugging purposes. // XXX: Maybe have a way to compile this out in release? - constraints: Vec<(TrianvertexWeight, TrianvertexWeight)>, + constraints: Vec, } impl Navmesh { @@ -183,8 +245,8 @@ impl Navmesh { destination: FixedDotIndex, options: RouterOptions, ) -> Result { - let mut triangulation: Triangulation = - Triangulation::new(layout.drawing().geometry().graph().node_bound()); + let mut triangulation: NavmeshTriangulation = + NavmeshTriangulation::new(layout.drawing().geometry().graph().node_bound()); let mut constraints = vec![]; let layer = layout.drawing().primitive(origin).layer(); @@ -200,62 +262,28 @@ impl Navmesh { { match node { PrimitiveIndex::FixedDot(dot) => { - triangulation.add_vertex(TrianvertexWeight { - node: dot.into(), - pos: primitive.shape().center(), - })?; + triangulation + .add_vertex(TrianvertexWeight::new_from_fixed_dot(layout, dot))?; } PrimitiveIndex::LoneLooseSeg(seg) => { - let (from_dot, to_dot) = layout.drawing().primitive(seg).joints(); - 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)); + Self::add_constraint( + &mut triangulation, + &mut constraints, + NavmeshTriangulationConstraint::new_from_lone_loose_seg( + layout, seg, + ), + )?; } 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(); - 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)); + Self::add_constraint( + &mut triangulation, + &mut constraints, + NavmeshTriangulationConstraint::new_from_seq_loose_seg(layout, seg), + )?; } PrimitiveIndex::FixedBend(bend) => { - triangulation.add_vertex(TrianvertexWeight { - node: bend.into(), - pos: primitive.shape().center(), - })?; + triangulation + .add_vertex(TrianvertexWeight::new_from_fixed_bend(layout, bend))?; } _ => (), } @@ -272,33 +300,28 @@ impl Navmesh { || 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. + // when you will start a new routing some of the constraint + // edges created from the loose segs of a 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. + // time, after all the constraint edges from bands have been + // placed, and only then add constraint edges created from + // fixed segs that do not cause an intersection. match node { 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 { - 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)?; + if !triangulation.intersects_constraint(&constraint.0, &constraint.1) { + Self::add_constraint( + &mut triangulation, + &mut constraints, + constraint, + ); } } _ => (), @@ -317,12 +340,22 @@ impl Navmesh { ) } + fn add_constraint( + triangulation: &mut NavmeshTriangulation, + constraints: &mut Vec, + constraint: NavmeshTriangulationConstraint, + ) -> Result<(), InsertionError> { + triangulation.add_constraint_edge(constraint.0, constraint.1)?; + constraints.push(constraint); + Ok(()) + } + fn new_from_triangulation( layout: &Layout, - triangulation: Triangulation, + triangulation: NavmeshTriangulation, origin: FixedDotIndex, destination: FixedDotIndex, - constraints: Vec<(TrianvertexWeight, TrianvertexWeight)>, + constraints: Vec, options: RouterOptions, ) -> Result { let mut graph: UnGraph = UnGraph::default();