feat(drawing/gear): Implement finding next and previous gear in chain for fixed dots

This commit is contained in:
Mikolaj Wielgus 2025-08-26 00:23:48 +02:00
parent 310e983b1d
commit 41438eeccc
3 changed files with 110 additions and 18 deletions

View File

@ -53,7 +53,7 @@ impl From<BendIndex> for GearIndex {
}
}
#[enum_dispatch(WalkOutwards, GetOuterGears, GetDrawing, GetPetgraphIndex)]
#[enum_dispatch(GetOuterGears, WalkOutwards, GetDrawing, GetPetgraphIndex)]
pub enum GearRef<'a, CW, Cel, R> {
FixedDot(FixedDot<'a, CW, Cel, R>),
FixedBend(FixedBend<'a, CW, Cel, R>),
@ -83,6 +83,29 @@ pub trait WalkOutwards {
fn outwards(&self) -> DrawingOutwardWalker;
}
//#[enum_dispatch]
pub trait GetPrevNextInChain {
fn next_in_chain(&self, maybe_prev: Option<GearIndex>) -> Option<GearIndex>;
fn prev_in_chain(&self, maybe_next: Option<GearIndex>) -> Option<GearIndex> {
// Just as in the `GetPrevNextLoose` trait.
let maybe_prev = maybe_next.or_else(|| self.next_in_chain(None));
self.next_in_chain(maybe_prev)
}
}
// Because types have trait bounds, we cannot use enum_dispatch and instead we
// implement `GetPrevNextInChain` explicitly.
impl<'a, CW: Clone, Cel: Copy, R: AccessRules> GetPrevNextInChain for GearRef<'a, CW, Cel, R> {
fn next_in_chain(&self, maybe_prev: Option<GearIndex>) -> Option<GearIndex> {
match self {
GearRef::FixedDot(dot) => dot.next_in_chain(maybe_prev),
GearRef::FixedBend(bend) => bend.next_in_chain(maybe_prev),
GearRef::LooseBend(bend) => bend.next_in_chain(maybe_prev),
}
}
}
/// I found it easier to just duplicate `OutwardWalker<BI>` for `Drawing<...>`.
pub struct DrawingOutwardWalker {
frontier: VecDeque<LooseBendIndex>,

View File

@ -9,6 +9,7 @@ use crate::{
drawing::{
bend::{BendIndex, FixedBendWeight, LooseBendIndex, LooseBendWeight},
dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight},
gear::{GearIndex, GetPrevNextInChain},
graph::{GetMaybeNet, PrimitiveIndex, PrimitiveWeight},
rules::{AccessRules, Conditions, GetConditions},
seg::{FixedSegWeight, LoneLooseSegWeight, SegIndex, SeqLooseSegIndex, SeqLooseSegWeight},
@ -291,6 +292,24 @@ impl<CW, Cel, R> GetOuterGears for FixedDot<'_, CW, Cel, R> {
}
}
impl<CW: Clone, Cel: Copy, R: AccessRules> GetPrevNextInChain for FixedDot<'_, CW, Cel, R> {
fn next_in_chain(&self, maybe_prev: Option<GearIndex>) -> Option<GearIndex> {
self.drawing
.clearance_intersectors(self.index.into())
.find_map(|infringement| {
let PrimitiveIndex::FixedDot(intersectee) = infringement.1 else {
return None;
};
if let Some(prev) = maybe_prev {
(infringement.1 == prev.into()).then_some(intersectee.into())
} else {
Some(intersectee.into())
}
})
}
}
impl<CW, Cel, R> WalkOutwards for FixedDot<'_, CW, Cel, R> {
fn outwards(&self) -> DrawingOutwardWalker {
DrawingOutwardWalker::new(self.lowest_gears().into_iter())
@ -457,6 +476,12 @@ impl<CW, Cel, R> GetOuterGears for FixedBend<'_, CW, Cel, R> {
}
}
impl<CW, Cel, R> GetPrevNextInChain for FixedBend<'_, CW, Cel, R> {
fn next_in_chain(&self, _maybe_prev: Option<GearIndex>) -> Option<GearIndex> {
None
}
}
impl<CW, Cel, R> WalkOutwards for FixedBend<'_, CW, Cel, R> {
fn outwards(&self) -> DrawingOutwardWalker {
DrawingOutwardWalker::new(self.lowest_gears().into_iter())
@ -510,6 +535,12 @@ impl<CW, Cel, R> GetOuterGears for LooseBend<'_, CW, Cel, R> {
}
}
impl<CW, Cel, R> GetPrevNextInChain for LooseBend<'_, CW, Cel, R> {
fn next_in_chain(&self, _maybe_prev: Option<GearIndex>) -> Option<GearIndex> {
None
}
}
impl<CW, Cel, R> WalkOutwards for LooseBend<'_, CW, Cel, R> {
fn outwards(&self) -> DrawingOutwardWalker {
DrawingOutwardWalker::new(self.outers())

View File

@ -112,24 +112,38 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
v
}
pub(super) fn find_infringement_except(
&self,
pub(super) fn find_infringement_except<'a>(
&'a self,
infringer: PrimitiveIndex,
predicate: &impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool,
predicate: &'a impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool,
) -> Option<Infringement> {
self.infringements_except(infringer, predicate).next()
}
/*pub(super) fn infringements<'a>(
&'a self,
infringer: PrimitiveIndex,
) -> impl Iterator<Item = Infringement> + 'a {
self.infringements_except(infringer, &|_, _, _| true)
}*/
fn infringements_except<'a>(
&'a self,
infringer: PrimitiveIndex,
predicate: &'a impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool,
) -> impl Iterator<Item = Infringement> + 'a {
self.infringements_among(
infringer,
self.locate_possible_infringees(infringer)
.filter_map(|infringee_node| {
.filter_map(move |infringee_node| {
if let GenericNode::Primitive(primitive_node) = infringee_node {
Some(primitive_node)
} else {
None
}
})
.filter(|infringee| predicate(&self, infringer, *infringee)),
.filter(move |infringee| predicate(&self, infringer, *infringee)),
)
.next()
}
pub(super) fn infringements_among<'a>(
@ -137,21 +151,45 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
infringer: PrimitiveIndex,
it: impl Iterator<Item = PrimitiveIndex> + 'a,
) -> impl Iterator<Item = Infringement> + 'a {
let mut inflated_shape = infringer.primitive(self).shape(); // Unused temporary value just for initialization.
let conditions = infringer.primitive(self).conditions();
it.filter(move |infringee| {
self.clearance_intersectors_among(infringer, it)
.filter(move |infringement| {
// Infringement with loose dots resulted in false positives for
// line-of-sight paths.
!matches!(infringer, PrimitiveIndex::LooseDot(..))
&& !matches!(infringee, PrimitiveIndex::LooseDot(..))
&& !matches!(infringement.1, PrimitiveIndex::LooseDot(..))
})
.filter(move |infringee| !self.are_connectable(infringer, *infringee))
.filter_map(move |primitive_node| {
.filter(move |infringement| !self.are_connectable(infringer, infringement.1))
}
pub(super) fn clearance_intersectors<'a>(
&'a self,
intersector: PrimitiveIndex,
) -> impl Iterator<Item = Infringement> + 'a {
self.clearance_intersectors_among(
intersector,
self.locate_possible_infringees(intersector)
.filter_map(move |infringee_node| {
if let GenericNode::Primitive(primitive_node) = infringee_node {
Some(primitive_node)
} else {
None
}
}),
)
}
pub(super) fn clearance_intersectors_among<'a>(
&'a self,
intersector: PrimitiveIndex,
it: impl Iterator<Item = PrimitiveIndex> + 'a,
) -> impl Iterator<Item = Infringement> + 'a {
let conditions = intersector.primitive(self).conditions();
it.filter_map(move |primitive_node| {
let infringee_conditions = primitive_node.primitive(self).conditions();
let epsilon = 1.0;
inflated_shape = infringer.primitive(self).shape().inflate(
let inflated_shape = intersector.primitive(self).shape().inflate(
match (&conditions, infringee_conditions) {
(None, _) | (_, None) => 0.0,
(Some(lhs), Some(rhs)) => {