From 83285dde6d90421d103d392461e6ac0c7c234ae5 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Tue, 22 Jul 2025 01:38:03 +0200 Subject: [PATCH] feat(drawing/drawing): Make it possible to have multiple outers for each gear --- src/drawing/collect.rs | 39 ++------- src/drawing/drawing.rs | 175 +++++++++++++++++++++------------------ src/drawing/gear.rs | 55 +++++++----- src/drawing/primitive.rs | 54 ++++++++++-- src/geometry/geometry.rs | 9 -- src/router/navmesh.rs | 44 +++++----- 6 files changed, 206 insertions(+), 170 deletions(-) diff --git a/src/drawing/collect.rs b/src/drawing/collect.rs index 35da816..c08a23b 100644 --- a/src/drawing/collect.rs +++ b/src/drawing/collect.rs @@ -2,12 +2,12 @@ // // SPDX-License-Identifier: MIT -use crate::graph::MakeRef; +use petgraph::visit::Walker; use super::{ band::{BandTermsegIndex, BandUid}, bend::LooseBendIndex, - gear::{GearIndex, GetNextGear}, + gear::WalkOutwards, graph::PrimitiveIndex, loose::{GetPrevNextLoose, LooseIndex}, primitive::GetJoints, @@ -26,9 +26,7 @@ pub trait Collect { fn bend_bow(&self, bend: LooseBendIndex) -> Vec; - fn bend_outer_bows(&self, bend: LooseBendIndex) -> Vec; - - fn wraparounded_bows(&self, around: GearIndex) -> Vec; + fn bend_outward_bows(&self, bend: LooseBendIndex) -> Vec; } impl Collect for Drawing { @@ -62,35 +60,12 @@ impl Collect for Drawing { v } - fn bend_outer_bows(&self, bend: LooseBendIndex) -> Vec { + fn bend_outward_bows(&self, bend: LooseBendIndex) -> Vec { let mut v = vec![]; - let mut gear = bend; - while let Some(outer) = self.primitive(gear).outer() { - v.append(&mut self.bend_bow(outer)); - gear = outer; - } - - v - } - - fn wraparounded_bows(&self, around: GearIndex) -> Vec { - let mut v = vec![]; - let mut gear = around; - - while let Some(bend) = gear.ref_(self).next_gear() { - let primitive = self.primitive(bend); - - v.push(bend.into()); - - let joints = primitive.joints(); - v.push(joints.0.into()); - v.push(joints.1.into()); - - v.push(self.primitive(joints.0).seg().unwrap().into()); - v.push(self.primitive(joints.1).seg().unwrap().into()); - - gear = bend.into(); + let mut outwards = self.primitive(bend).outwards(); + while let Some(next) = outwards.walk_next(self) { + v.append(&mut self.bend_bow(next)); } v diff --git a/src/drawing/drawing.rs b/src/drawing/drawing.rs index 628bc24..bbeef9a 100644 --- a/src/drawing/drawing.rs +++ b/src/drawing/drawing.rs @@ -5,6 +5,7 @@ use contracts_try::{debug_ensures, debug_invariant}; use derive_getters::Getters; use geo::{Point, Polygon}; +use petgraph::visit::Walker; use core::fmt; use rstar::{RTree, AABB}; @@ -17,7 +18,7 @@ use crate::{ cane::Cane, collect::Collect, dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight}, - gear::{GearIndex, GetNextGear}, + gear::GearIndex, graph::{GetMaybeNet, IsInLayer, MakePrimitive, PrimitiveIndex, PrimitiveWeight}, guide::Guide, loose::{GetPrevNextLoose, Loose, LooseIndex}, @@ -44,6 +45,8 @@ use crate::{ math::RotationSense, }; +use super::gear::{GetOuterGears, WalkOutwards}; + #[derive(Clone, Copy, Error)] pub enum DrawingException { #[error(transparent)] @@ -255,7 +258,7 @@ impl Drawing { LooseIndex::Bend(bend) => { bends.push(bend); - if let Some(outer) = self.primitive(bend).outer() { + for outer in self.primitive(bend).outers().collect::>() { outers.push(outer); self.reattach_bend(recorder, outer, self.primitive(bend).inner()); } @@ -509,10 +512,11 @@ impl Drawing { } } // - if let Some(next_gear) = around.ref_(self).next_gear() { - if let Some(next_gear_net) = next_gear.primitive(self).maybe_net() { + let mut outwards = around.ref_(self).outwards(); + while let Some(gear) = outwards.walk_next(self) { + if let Some(next_gear_net) = gear.primitive(self).maybe_net() { if net == next_gear_net { - return Err(AlreadyConnected(net, next_gear.into()).into()); + return Err(AlreadyConnected(net, gear.into()).into()); } } } @@ -666,7 +670,7 @@ impl Drawing { bend_weight: LooseBendWeight, sense: RotationSense, ) -> Result { - let maybe_next_gear = around.ref_(self).next_gear(); + let outer_gears = around.ref_(self).outer_gears(); let cane = self.add_cane_with_infringement_filtering( recorder, from, @@ -678,11 +682,11 @@ impl Drawing { &|_drawing, _infringer, _infringee| true, )?; - if let Some(next_gear) = maybe_next_gear { - self.reattach_bend(recorder, next_gear, Some(cane.bend)); + for gear in outer_gears { + self.reattach_bend(recorder, gear, Some(cane.bend)); } - if let Some(outer) = self.primitive(cane.bend).outer() { + for outer in self.primitive(cane.bend).outers().collect::>() { self.update_this_and_outward_bows(recorder, outer) .inspect_err(|_| { let joint = self.primitive(cane.bend).other_joint(cane.dot); @@ -700,81 +704,88 @@ impl Drawing { recorder: &mut DrawingEdit, around: LooseBendIndex, ) -> Result<(), DrawingException> { - let mut maybe_rail = Some(around); + self.update_bow(recorder, around)?; - while let Some(rail) = maybe_rail { - let rail_primitive = self.primitive(rail); - let joints = rail_primitive.joints(); - let width = rail_primitive.width(); - - let from_head = self.rear_head(joints.1); - let to_head = self.rear_head(joints.0); - - let (from, to, offset) = if let Some(inner) = rail_primitive.inner() { - let inner = inner.into(); - let from = self.guide_for_head_around_bend_segment( - &from_head, - inner, - RotationSense::Counterclockwise, - width, - )?; - let to = self.guide_for_head_around_bend_segment( - &to_head, - inner, - RotationSense::Clockwise, - width, - )?; - let offset = self.guide_for_head_around_bend_offset(&from_head, inner, width); - (from, to, offset) - } else { - let core = rail_primitive.core().into(); - let from = self.guide_for_head_around_dot_segment( - &from_head, - core, - RotationSense::Counterclockwise, - width, - )?; - let to = self.guide_for_head_around_dot_segment( - &to_head, - core, - RotationSense::Clockwise, - width, - )?; - let offset = self.guide_for_head_around_dot_offset(&from_head, core, width); - (from, to, offset) - }; - - let rail_outer_bows = self.bend_outer_bows(rail); - - // Commenting out these two makes the crash go away. - self.move_dot_with_infringement_filtering( - recorder, - joints.0.into(), - from.end_point(), - &|_drawing, _infringer, infringee| rail_outer_bows.contains(&infringee), - )?; - self.move_dot_with_infringement_filtering( - recorder, - joints.1.into(), - to.end_point(), - &|_drawing, _infringer, infringee| rail_outer_bows.contains(&infringee), - )?; - - self.shift_bend_with_infringement_filtering( - recorder, - rail.into(), - offset, - &|_drawing, _infringer, infringee| rail_outer_bows.contains(&infringee), - )?; - - // Update offsets in case the rule conditions changed. - - maybe_rail = self.primitive(rail).outer(); + let mut outwards = self.primitive(around).outwards(); + while let Some(rail) = outwards.walk_next(self) { + self.update_bow(recorder, rail)?; } Ok(()) } + fn update_bow( + &mut self, + recorder: &mut DrawingEdit, + rail: LooseBendIndex, + ) -> Result<(), DrawingException> { + let rail_primitive = self.primitive(rail); + let joints = rail_primitive.joints(); + let width = rail_primitive.width(); + + let from_head = self.rear_head(joints.1); + let to_head = self.rear_head(joints.0); + + let (from, to, offset) = if let Some(inner) = rail_primitive.inner() { + let inner = inner.into(); + let from = self.guide_for_head_around_bend_segment( + &from_head, + inner, + RotationSense::Counterclockwise, + width, + )?; + let to = self.guide_for_head_around_bend_segment( + &to_head, + inner, + RotationSense::Clockwise, + width, + )?; + let offset = self.guide_for_head_around_bend_offset(&from_head, inner, width); + (from, to, offset) + } else { + let core = rail_primitive.core().into(); + let from = self.guide_for_head_around_dot_segment( + &from_head, + core, + RotationSense::Counterclockwise, + width, + )?; + let to = self.guide_for_head_around_dot_segment( + &to_head, + core, + RotationSense::Clockwise, + width, + )?; + let offset = self.guide_for_head_around_dot_offset(&from_head, core, width); + (from, to, offset) + }; + + let rail_outer_bows = self.bend_outward_bows(rail); + + // Commenting out these two makes the crash go away. + self.move_dot_with_infringement_filtering( + recorder, + joints.0.into(), + from.end_point(), + &|_drawing, _infringer, infringee| rail_outer_bows.contains(&infringee), + )?; + self.move_dot_with_infringement_filtering( + recorder, + joints.1.into(), + to.end_point(), + &|_drawing, _infringer, infringee| rail_outer_bows.contains(&infringee), + )?; + + self.shift_bend_with_infringement_filtering( + recorder, + rail.into(), + offset, + &|_drawing, _infringer, infringee| rail_outer_bows.contains(&infringee), + )?; + + Ok(()) + } + #[debug_ensures(ret.is_ok() -> self.recording_geometry_with_rtree.graph().node_count() == old(self.recording_geometry_with_rtree.graph().node_count() + 4))] #[debug_ensures(ret.is_ok() -> self.recording_geometry_with_rtree.graph().edge_count() >= old(self.recording_geometry_with_rtree.graph().edge_count() + 5))] #[debug_ensures(ret.is_err() -> self.recording_geometry_with_rtree.graph().node_count() == old(self.recording_geometry_with_rtree.graph().node_count()))] @@ -877,11 +888,11 @@ impl Drawing { cane: &Cane, face: LooseDotIndex, ) { - let maybe_outer = self.primitive(cane.bend).outer(); + let outers = self.primitive(cane.bend).outers().collect::>(); // Removing a loose bend affects its outer bends. - if let Some(outer) = maybe_outer { - self.reattach_bend(recorder, outer, self.primitive(cane.bend).inner()); + for outer in &outers { + self.reattach_bend(recorder, *outer, self.primitive(cane.bend).inner()); } self.recording_geometry_with_rtree @@ -897,7 +908,7 @@ impl Drawing { self.recording_geometry_with_rtree .remove_dot(recorder, cane.dot.into()); - if let Some(outer) = maybe_outer { + for outer in outers { self.update_this_and_outward_bows(recorder, outer).unwrap(); // Must never fail. } } diff --git a/src/drawing/gear.rs b/src/drawing/gear.rs index f3a10cc..9ca93e1 100644 --- a/src/drawing/gear.rs +++ b/src/drawing/gear.rs @@ -2,26 +2,23 @@ // // SPDX-License-Identifier: MIT +use std::collections::VecDeque; + use enum_dispatch::enum_dispatch; -use petgraph::stable_graph::NodeIndex; +use petgraph::{stable_graph::NodeIndex, visit::Walker}; use crate::{ drawing::{ bend::{BendIndex, FixedBendIndex, LooseBendIndex}, dot::FixedDotIndex, graph::{MakePrimitive, PrimitiveIndex}, - primitive::{FixedBend, FixedDot, GetFirstGear, LooseBend, Primitive}, + primitive::{FixedBend, FixedDot, LooseBend, Primitive}, rules::AccessRules, Drawing, }, graph::{GetPetgraphIndex, MakeRef}, }; -#[enum_dispatch] -pub trait GetNextGear: GetPetgraphIndex { - fn next_gear(&self) -> Option; -} - #[enum_dispatch(GetPetgraphIndex, MakePrimitive)] #[derive(Debug, Clone, Copy, PartialEq)] pub enum GearIndex { @@ -56,7 +53,7 @@ impl From for GearIndex { } } -#[enum_dispatch(GetNextGear, GetDrawing, GetPetgraphIndex)] +#[enum_dispatch(WalkOutwards, GetOuterGears, GetDrawing, GetPetgraphIndex)] pub enum GearRef<'a, CW, Cel, R> { FixedDot(FixedDot<'a, CW, Cel, R>), FixedBend(FixedBend<'a, CW, Cel, R>), @@ -73,20 +70,40 @@ impl<'a, CW, Cel, R> GearRef<'a, CW, Cel, R> { } } -impl GetNextGear for FixedDot<'_, CW, Cel, R> { - fn next_gear(&self) -> Option { - self.first_gear() +#[enum_dispatch] +pub trait GetOuterGears { + // TODO: This duplicates `.outers()` methods in some other places, we need + // to merge them with this. + // TODO: Use iterator instead of vec. + fn outer_gears(&self) -> Vec; +} + +#[enum_dispatch] +pub trait WalkOutwards { + fn outwards(&self) -> DrawingOutwardWalker; +} + +/// I found it easier to just duplicate `OutwardWalker` for `Drawing<...>`. +pub struct DrawingOutwardWalker { + frontier: VecDeque, +} + +impl DrawingOutwardWalker { + pub fn new(initial_frontier: impl Iterator) -> Self { + let mut frontier = VecDeque::new(); + frontier.extend(initial_frontier); + + Self { frontier } } } -impl GetNextGear for LooseBend<'_, CW, Cel, R> { - fn next_gear(&self) -> Option { - self.outer() - } -} +impl Walker<&Drawing> for DrawingOutwardWalker { + type Item = LooseBendIndex; -impl GetNextGear for FixedBend<'_, CW, Cel, R> { - fn next_gear(&self) -> Option { - self.first_gear() + fn walk_next(&mut self, drawing: &Drawing) -> Option { + let front = self.frontier.pop_front()?; + self.frontier.extend(drawing.primitive(front).outers()); + + Some(front) } } diff --git a/src/drawing/primitive.rs b/src/drawing/primitive.rs index 8a69413..4d7f79f 100644 --- a/src/drawing/primitive.rs +++ b/src/drawing/primitive.rs @@ -18,6 +18,8 @@ use crate::{ graph::{GenericIndex, GetPetgraphIndex}, }; +use super::gear::{DrawingOutwardWalker, GetOuterGears, WalkOutwards}; + pub trait GetDrawing { type CompoundWeight; type CompoundEntryLabel; @@ -86,12 +88,14 @@ pub trait GetJoints { fn joints(&self) -> (Self::F, Self::T); } -pub trait GetFirstGear: GetDrawing + GetPetgraphIndex { - fn first_gear(&self) -> Option { +pub trait GetLowestGears: GetDrawing + GetPetgraphIndex { + // TODO: Make it return an iterator instead of a vec. + fn lowest_gears(&self) -> Vec { self.drawing() .geometry() - .first_rail(self.petgraph_index()) + .all_rails(self.petgraph_index()) .map(|ni| LooseBendIndex::new(ni.petgraph_index())) + .collect() } } @@ -279,7 +283,19 @@ impl GetLimbs for FixedDot<'_, CW, Cel, R> { } } -impl GetFirstGear for FixedDot<'_, CW, Cel, R> {} +impl GetLowestGears for FixedDot<'_, CW, Cel, R> {} + +impl GetOuterGears for FixedDot<'_, CW, Cel, R> { + fn outer_gears(&self) -> Vec { + self.lowest_gears() + } +} + +impl WalkOutwards for FixedDot<'_, CW, Cel, R> { + fn outwards(&self) -> DrawingOutwardWalker { + DrawingOutwardWalker::new(self.lowest_gears().into_iter()) + } +} pub type LooseDot<'a, CW, Cel, R> = GenericPrimitive<'a, LooseDotWeight, CW, Cel, R>; impl_loose_primitive!(LooseDot, LooseDotWeight); @@ -433,8 +449,19 @@ impl GetJoints for FixedBend<'_, CW, Cel, R> { } } -impl GetFirstGear for FixedBend<'_, CW, Cel, R> {} -//impl<'a, R: QueryRules> GetInnerOuter for FixedBend<'a, CW, Cel, R> {} +impl GetLowestGears for FixedBend<'_, CW, Cel, R> {} + +impl GetOuterGears for FixedBend<'_, CW, Cel, R> { + fn outer_gears(&self) -> Vec { + self.lowest_gears() + } +} + +impl WalkOutwards for FixedBend<'_, CW, Cel, R> { + fn outwards(&self) -> DrawingOutwardWalker { + DrawingOutwardWalker::new(self.lowest_gears().into_iter()) + } +} pub type LooseBend<'a, CW, Cel, R> = GenericPrimitive<'a, LooseBendWeight, CW, Cel, R>; impl_loose_primitive!(LooseBend, LooseBendWeight); @@ -477,6 +504,18 @@ impl GetJoints for LooseBend<'_, CW, Cel, R> { } } +impl GetOuterGears for LooseBend<'_, CW, Cel, R> { + fn outer_gears(&self) -> Vec { + self.outers().collect() + } +} + +impl WalkOutwards for LooseBend<'_, CW, Cel, R> { + fn outwards(&self) -> DrawingOutwardWalker { + DrawingOutwardWalker::new(self.outers()) + } +} + impl LooseBend<'_, CW, Cel, R> { pub fn inner(&self) -> Option { self.drawing() @@ -485,11 +524,10 @@ impl LooseBend<'_, CW, Cel, R> { .map(|ni| LooseBendIndex::new(ni.petgraph_index())) } - pub fn outer(&self) -> Option { + pub fn outers(&self) -> impl Iterator + '_ { self.drawing() .geometry() .outers(self.bend_index()) - .next() .map(|node| LooseBendIndex::new(node.petgraph_index())) } } diff --git a/src/geometry/geometry.rs b/src/geometry/geometry.rs index e43f105..3380fea 100644 --- a/src/geometry/geometry.rs +++ b/src/geometry/geometry.rs @@ -573,15 +573,6 @@ impl< BI: GetPetgraphIndex, > Geometry { - pub fn first_rail(&self, node: NodeIndex) -> Option { - self.all_rails(node).find(|bend| { - !self - .graph - .edges_directed(bend.petgraph_index(), Incoming) - .any(|edge| matches!(edge.weight(), GeometryLabel::Outer)) - }) - } - pub fn all_rails(&self, node: NodeIndex) -> impl Iterator + '_ { self.graph .edges_directed(node, Incoming) diff --git a/src/router/navmesh.rs b/src/router/navmesh.rs index f559183..f3ef85e 100644 --- a/src/router/navmesh.rs +++ b/src/router/navmesh.rs @@ -11,7 +11,8 @@ use petgraph::{ graph::UnGraph, stable_graph::NodeIndex, visit::{ - Data, EdgeRef, GraphBase, IntoEdgeReferences, IntoEdges, IntoNeighbors, IntoNodeIdentifiers, + Data, EdgeRef, GraphBase, IntoEdgeReferences, IntoEdges, IntoNeighbors, + IntoNodeIdentifiers, Walker, }, }; use spade::InsertionError; @@ -21,7 +22,7 @@ use crate::{ drawing::{ bend::{FixedBendIndex, LooseBendIndex}, dot::FixedDotIndex, - gear::{GearIndex, GetNextGear}, + gear::{GearIndex, GetOuterGears, WalkOutwards}, graph::{MakePrimitive, PrimitiveIndex}, primitive::Primitive, rules::AccessRules, @@ -183,8 +184,7 @@ impl Navmesh { } else { map.insert(trianvertex, vec![]); - let mut gear = - Into::::into(Into::::into(trianvertex)); + let gear = Into::::into(Into::::into(trianvertex)); if options.squeeze_through_under_bends { Self::add_trianvertex_to_graph_and_map_as_binavnode( @@ -195,30 +195,34 @@ impl Navmesh { ); if options.wrap_around_bands { - while let Some(bend) = gear.ref_(layout.drawing()).next_gear() { + let mut outwards = gear.ref_(layout.drawing()).outwards(); + while let Some(outward) = outwards.walk_next(layout.drawing()) { Self::add_trianvertex_to_graph_and_map_as_binavnode( &mut graph, &mut map, trianvertex, - bend.into(), + outward.into(), ); - gear = bend.into(); } } - } else if let Some(first_bend) = gear.ref_(layout.drawing()).next_gear() { - let mut bend = first_bend; - - while let Some(next_bend) = gear.ref_(layout.drawing()).next_gear() { - bend = next_bend; - gear = bend.into(); + } else if !gear.ref_(layout.drawing()).outer_gears().is_empty() { + let mut outwards = gear.ref_(layout.drawing()).outwards(); + while let Some(outward) = outwards.walk_next(layout.drawing()) { + if layout + .drawing() + .primitive(outward) + .outers() + .collect::>() + .is_empty() + { + Self::add_trianvertex_to_graph_and_map_as_binavnode( + &mut graph, + &mut map, + trianvertex, + outward.into(), + ); + } } - - Self::add_trianvertex_to_graph_and_map_as_binavnode( - &mut graph, - &mut map, - trianvertex, - bend.into(), - ); } else { Self::add_trianvertex_to_graph_and_map_as_binavnode( &mut graph,