feat(drawing/drawing): Make it possible to have multiple outers for each gear

This commit is contained in:
Mikolaj Wielgus 2025-07-22 01:38:03 +02:00 committed by mikolaj
parent 6317d8b08a
commit 83285dde6d
6 changed files with 206 additions and 170 deletions

View File

@ -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<PrimitiveIndex>;
fn bend_outer_bows(&self, bend: LooseBendIndex) -> Vec<PrimitiveIndex>;
fn wraparounded_bows(&self, around: GearIndex) -> Vec<PrimitiveIndex>;
fn bend_outward_bows(&self, bend: LooseBendIndex) -> Vec<PrimitiveIndex>;
}
impl<CW: Clone, Cel: Copy, R: AccessRules> Collect for Drawing<CW, Cel, R> {
@ -62,35 +60,12 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Collect for Drawing<CW, Cel, R> {
v
}
fn bend_outer_bows(&self, bend: LooseBendIndex) -> Vec<PrimitiveIndex> {
fn bend_outward_bows(&self, bend: LooseBendIndex) -> Vec<PrimitiveIndex> {
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<PrimitiveIndex> {
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

View File

@ -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<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
LooseIndex::Bend(bend) => {
bends.push(bend);
if let Some(outer) = self.primitive(bend).outer() {
for outer in self.primitive(bend).outers().collect::<Vec<_>>() {
outers.push(outer);
self.reattach_bend(recorder, outer, self.primitive(bend).inner());
}
@ -509,10 +512,11 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
}
}
//
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<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
bend_weight: LooseBendWeight,
sense: RotationSense,
) -> Result<Cane, DrawingException> {
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<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
&|_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::<Vec<_>>() {
self.update_this_and_outward_bows(recorder, outer)
.inspect_err(|_| {
let joint = self.primitive(cane.bend).other_joint(cane.dot);
@ -700,9 +704,21 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
recorder: &mut DrawingEdit<CW, Cel>,
around: LooseBendIndex,
) -> Result<(), DrawingException> {
let mut maybe_rail = Some(around);
self.update_bow(recorder, around)?;
while let Some(rail) = maybe_rail {
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<CW, Cel>,
rail: LooseBendIndex,
) -> Result<(), DrawingException> {
let rail_primitive = self.primitive(rail);
let joints = rail_primitive.joints();
let width = rail_primitive.width();
@ -744,7 +760,7 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
(from, to, offset)
};
let rail_outer_bows = self.bend_outer_bows(rail);
let rail_outer_bows = self.bend_outward_bows(rail);
// Commenting out these two makes the crash go away.
self.move_dot_with_infringement_filtering(
@ -767,11 +783,6 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
&|_drawing, _infringer, infringee| rail_outer_bows.contains(&infringee),
)?;
// Update offsets in case the rule conditions changed.
maybe_rail = self.primitive(rail).outer();
}
Ok(())
}
@ -877,11 +888,11 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
cane: &Cane,
face: LooseDotIndex,
) {
let maybe_outer = self.primitive(cane.bend).outer();
let outers = self.primitive(cane.bend).outers().collect::<Vec<_>>();
// 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<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
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.
}
}

View File

@ -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<LooseBendIndex>;
}
#[enum_dispatch(GetPetgraphIndex, MakePrimitive)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GearIndex {
@ -56,7 +53,7 @@ impl From<BendIndex> 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<CW, Cel, R> GetNextGear for FixedDot<'_, CW, Cel, R> {
fn next_gear(&self) -> Option<LooseBendIndex> {
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<LooseBendIndex>;
}
#[enum_dispatch]
pub trait WalkOutwards {
fn outwards(&self) -> DrawingOutwardWalker;
}
/// I found it easier to just duplicate `OutwardWalker<BI>` for `Drawing<...>`.
pub struct DrawingOutwardWalker {
frontier: VecDeque<LooseBendIndex>,
}
impl DrawingOutwardWalker {
pub fn new(initial_frontier: impl Iterator<Item = LooseBendIndex>) -> Self {
let mut frontier = VecDeque::new();
frontier.extend(initial_frontier);
Self { frontier }
}
}
impl<CW, Cel, R> GetNextGear for LooseBend<'_, CW, Cel, R> {
fn next_gear(&self) -> Option<LooseBendIndex> {
self.outer()
}
}
impl<CW: Clone, Cel: Copy, R: AccessRules> Walker<&Drawing<CW, Cel, R>> for DrawingOutwardWalker {
type Item = LooseBendIndex;
impl<CW, Cel, R> GetNextGear for FixedBend<'_, CW, Cel, R> {
fn next_gear(&self) -> Option<LooseBendIndex> {
self.first_gear()
fn walk_next(&mut self, drawing: &Drawing<CW, Cel, R>) -> Option<Self::Item> {
let front = self.frontier.pop_front()?;
self.frontier.extend(drawing.primitive(front).outers());
Some(front)
}
}

View File

@ -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<LooseBendIndex> {
pub trait GetLowestGears: GetDrawing + GetPetgraphIndex {
// TODO: Make it return an iterator instead of a vec.
fn lowest_gears(&self) -> Vec<LooseBendIndex> {
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<CW, Cel, R> GetLimbs for FixedDot<'_, CW, Cel, R> {
}
}
impl<CW, Cel, R> GetFirstGear for FixedDot<'_, CW, Cel, R> {}
impl<CW, Cel, R> GetLowestGears for FixedDot<'_, CW, Cel, R> {}
impl<CW, Cel, R> GetOuterGears for FixedDot<'_, CW, Cel, R> {
fn outer_gears(&self) -> Vec<LooseBendIndex> {
self.lowest_gears()
}
}
impl<CW, Cel, R> 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<CW, Cel, R> GetJoints for FixedBend<'_, CW, Cel, R> {
}
}
impl<CW, Cel, R> GetFirstGear for FixedBend<'_, CW, Cel, R> {}
//impl<'a, R: QueryRules> GetInnerOuter for FixedBend<'a, CW, Cel, R> {}
impl<CW, Cel, R> GetLowestGears for FixedBend<'_, CW, Cel, R> {}
impl<CW, Cel, R> GetOuterGears for FixedBend<'_, CW, Cel, R> {
fn outer_gears(&self) -> Vec<LooseBendIndex> {
self.lowest_gears()
}
}
impl<CW, Cel, R> 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<CW, Cel, R> GetJoints for LooseBend<'_, CW, Cel, R> {
}
}
impl<CW, Cel, R> GetOuterGears for LooseBend<'_, CW, Cel, R> {
fn outer_gears(&self) -> Vec<LooseBendIndex> {
self.outers().collect()
}
}
impl<CW, Cel, R> WalkOutwards for LooseBend<'_, CW, Cel, R> {
fn outwards(&self) -> DrawingOutwardWalker {
DrawingOutwardWalker::new(self.outers())
}
}
impl<CW, Cel, R> LooseBend<'_, CW, Cel, R> {
pub fn inner(&self) -> Option<LooseBendIndex> {
self.drawing()
@ -485,11 +524,10 @@ impl<CW, Cel, R> LooseBend<'_, CW, Cel, R> {
.map(|ni| LooseBendIndex::new(ni.petgraph_index()))
}
pub fn outer(&self) -> Option<LooseBendIndex> {
pub fn outers(&self) -> impl Iterator<Item = LooseBendIndex> + '_ {
self.drawing()
.geometry()
.outers(self.bend_index())
.next()
.map(|node| LooseBendIndex::new(node.petgraph_index()))
}
}

View File

@ -573,15 +573,6 @@ impl<
BI: GetPetgraphIndex,
> Geometry<PW, DW, SW, BW, CW, Cel, PI, DI, SI, BI>
{
pub fn first_rail(&self, node: NodeIndex<usize>) -> Option<BI> {
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<usize>) -> impl Iterator<Item = BI> + '_ {
self.graph
.edges_directed(node, Incoming)

View File

@ -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::<GearIndex>::into(Into::<BinavnodeNodeIndex>::into(trianvertex));
let gear = Into::<GearIndex>::into(Into::<BinavnodeNodeIndex>::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::<Vec<_>>()
.is_empty()
{
Self::add_trianvertex_to_graph_and_map_as_binavnode(
&mut graph,
&mut map,
trianvertex,
bend.into(),
outward.into(),
);
}
}
} else {
Self::add_trianvertex_to_graph_and_map_as_binavnode(
&mut graph,