From f4b78749b1d61be3556e50bbe083ac20f8a1c9d7 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Sun, 21 Sep 2025 20:22:20 +0200 Subject: [PATCH] feat(topola-egui): Show shape of infringee in addition to inflated infringer's --- src/drawing/drawing.rs | 16 ++++++++---- src/drawing/query.rs | 54 +++++++++++++++++++++++++--------------- src/layout/layout.rs | 2 +- src/router/navmesh.rs | 4 +-- src/router/ng/mod.rs | 6 +++-- src/router/prenavmesh.rs | 2 +- src/router/router.rs | 6 +++-- 7 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/drawing/drawing.rs b/src/drawing/drawing.rs index 5c0a1f4..bb4bbc8 100644 --- a/src/drawing/drawing.rs +++ b/src/drawing/drawing.rs @@ -67,11 +67,17 @@ impl fmt::Debug for DrawingException { } impl DrawingException { - pub fn maybe_ghost_and_obstacle(&self) -> Option<(&PrimitiveShape, PrimitiveIndex)> { + pub fn maybe_ghosts_and_obstacle( + &self, + ) -> Option<(&PrimitiveShape, &PrimitiveShape, PrimitiveIndex)> { match self { Self::NoTangents(_) => None, - Self::Infringement(Infringement(ghost, obstacle)) => Some((ghost, *obstacle)), - Self::Collision(Collision(ghost, obstacle)) => Some((ghost, *obstacle)), + Self::Infringement(Infringement(infringer_ghost, infringee_ghost, obstacle)) => { + Some((infringer_ghost, infringee_ghost, *obstacle)) + } + Self::Collision(Collision(collider_ghost, collidee_ghost, obstacle)) => { + Some((collider_ghost, collidee_ghost, *obstacle)) + } Self::AlreadyConnected(_) => None, } } @@ -86,7 +92,7 @@ impl DrawingException { /// having the same net implies being connectable. #[derive(Error, Debug, Clone, Copy)] #[error("{0:?} infringes on {1:?}")] -pub struct Infringement(pub PrimitiveShape, pub PrimitiveIndex); +pub struct Infringement(pub PrimitiveShape, pub PrimitiveShape, pub PrimitiveIndex); /// A collision is a special case of infringement where the uninflated shapes of /// two primitives themselves intersect. In other words, collision detection is @@ -107,7 +113,7 @@ pub struct Infringement(pub PrimitiveShape, pub PrimitiveIndex); /// net, making them connectable and thus uninfringable. #[derive(Error, Debug, Clone, Copy)] #[error("{0:?} collides with {1:?}")] -pub struct Collision(pub PrimitiveShape, pub PrimitiveIndex); +pub struct Collision(pub PrimitiveShape, pub PrimitiveShape, pub PrimitiveIndex); #[derive(Error, Debug, Clone, Copy)] #[error("{1:?} is already connected to net {0}")] diff --git a/src/drawing/query.rs b/src/drawing/query.rs index 46d2fb2..b7feea2 100644 --- a/src/drawing/query.rs +++ b/src/drawing/query.rs @@ -190,9 +190,9 @@ impl Drawing { // Infringement with loose dots resulted in false positives for // line-of-sight paths. !matches!(infringer, PrimitiveIndex::LooseDot(..)) - && !matches!(infringement.1, PrimitiveIndex::LooseDot(..)) + && !matches!(infringement.2, PrimitiveIndex::LooseDot(..)) }) - .filter(move |infringement| !self.are_connectable(infringer, infringement.1)) + .filter(move |infringement| !self.are_connectable(infringer, infringement.2)) } pub fn overlapees<'a>( @@ -224,21 +224,28 @@ impl Drawing { let infringee_conditions = primitive_node.primitive_ref(self).conditions(); let epsilon = 1.0; - let inflated_shape = intersector.primitive_ref(self).shape().inflate( - match (&conditions, infringee_conditions) { - (None, _) | (_, None) => 0.0, - (Some(lhs), Some(rhs)) => { - // Note the epsilon comparison. - // XXX: Epsilon is probably too large. But what should - // it be exactly then? - (self.rules().clearance(lhs, &rhs) - epsilon).clamp(0.0, f64::INFINITY) - } - }, - ); + let inflated_infringer_shape = intersector.primitive_ref(self).shape().inflate(match ( + &conditions, + infringee_conditions, + ) { + (None, _) | (_, None) => 0.0, + (Some(lhs), Some(rhs)) => { + // Note the epsilon comparison. + // XXX: Epsilon is probably too large. But what should + // it be exactly then? + (self.rules().clearance(lhs, &rhs) - epsilon).clamp(0.0, f64::INFINITY) + } + }); - inflated_shape - .intersects(&primitive_node.primitive_ref(self).shape()) - .then_some(Infringement(inflated_shape, primitive_node)) + let infringee_shape = primitive_node.primitive_ref(self).shape(); + + inflated_infringer_shape + .intersects(&infringee_shape) + .then_some(Infringement( + inflated_infringer_shape, + infringee_shape, + primitive_node, + )) }) } @@ -266,11 +273,11 @@ impl Drawing { collider: PrimitiveIndex, predicate: &impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool, ) -> Option { - let shape = collider.primitive_ref(self).shape(); + let collider_shape = collider.primitive_ref(self).shape(); self.recording_geometry_with_rtree() .rtree() - .locate_in_envelope_intersecting(&shape.full_height_envelope_3d(0.0, 2)) + .locate_in_envelope_intersecting(&collider_shape.full_height_envelope_3d(0.0, 2)) .filter_map(|wrapper| { if let GenericNode::Primitive(collidee) = wrapper.data { Some(collidee) @@ -290,8 +297,15 @@ impl Drawing { || matches!(collidee, PrimitiveIndex::SeqLooseSeg(..)))) }) .filter(|collidee| predicate(&self, collider, *collidee)) - .find(|collidee| shape.intersects(&collidee.primitive_ref(self).shape())) - .map(|collidee| Collision(shape, collidee)) + .find_map(|collidee| { + let collidee_shape = collidee.primitive_ref(self).shape(); + + if collider_shape.intersects(&collidee_shape) { + Some(Collision(collider_shape, collidee_shape, collidee)) + } else { + None + } + }) } fn are_connectable(&self, node1: PrimitiveIndex, node2: PrimitiveIndex) -> bool { diff --git a/src/layout/layout.rs b/src/layout/layout.rs index fbf4473..e608e46 100644 --- a/src/layout/layout.rs +++ b/src/layout/layout.rs @@ -97,7 +97,7 @@ impl Layout { !drawing .overlapees(around.into()) .find(|overlapee| { - PrimitiveIndex::from(overlapee.1) + PrimitiveIndex::from(overlapee.2) .primitive_ref(drawing) .limbs() .contains(&infringee) diff --git a/src/router/navmesh.rs b/src/router/navmesh.rs index dd3a8e8..3071ee5 100644 --- a/src/router/navmesh.rs +++ b/src/router/navmesh.rs @@ -337,14 +337,14 @@ impl Navmesh { // Ignore overlaps with fillets. if layout .drawing() - .compounds(GenericIndex::<()>::new(overlapee.1.index())) + .compounds(GenericIndex::<()>::new(overlapee.2.index())) .find(|(label, _)| *label == CompoundEntryLabel::Fillet) .is_some() { continue; } - let PrimitiveIndex::FixedDot(overlapee) = overlapee.1 else { + let PrimitiveIndex::FixedDot(overlapee) = overlapee.2 else { continue; }; diff --git a/src/router/ng/mod.rs b/src/router/ng/mod.rs index 846063f..ede06c7 100644 --- a/src/router/ng/mod.rs +++ b/src/router/ng/mod.rs @@ -257,9 +257,11 @@ impl EvalException { Self::Draw(DrawException::NoBitangents(_)) => (Vec::new(), Vec::new(), Vec::new()), Self::Draw(DrawException::CannotFinishIn(_, dwxc)) | Self::Draw(DrawException::CannotWrapAround(_, dwxc)) => { - match dwxc.maybe_ghost_and_obstacle() { + match dwxc.maybe_ghosts_and_obstacle() { None => (Vec::new(), Vec::new(), Vec::new()), - Some((ghost, obstacle)) => (vec![*ghost], Vec::new(), vec![obstacle]), + Some((infringer_ghost, _, obstacle)) => { + (vec![*infringer_ghost], Vec::new(), vec![obstacle]) + } } } Self::ResolvingPathFailed { .. } => (Vec::new(), Vec::new(), Vec::new()), diff --git a/src/router/prenavmesh.rs b/src/router/prenavmesh.rs index 8411589..8ed957e 100644 --- a/src/router/prenavmesh.rs +++ b/src/router/prenavmesh.rs @@ -308,7 +308,7 @@ impl Prenavmesh { layout .drawing() // TODO: Add `.compounds()` method working on `PrimitiveIndex`. - .compounds(GenericIndex::<()>::new(overlapee.1.index())) + .compounds(GenericIndex::<()>::new(overlapee.2.index())) .find(|(label, _)| *label == CompoundEntryLabel::Fillet) .is_some() }) diff --git a/src/router/router.rs b/src/router/router.rs index c5c5d6a..8962bce 100644 --- a/src/router/router.rs +++ b/src/router/router.rs @@ -110,11 +110,13 @@ impl ThetastarStrategy DrawException::CannotWrapAround(.., layout_err) => layout_err, }; - let Some((ghost, obstacle)) = layout_err.maybe_ghost_and_obstacle() else { + let Some((infringer_ghost, infringee_ghost, obstacle)) = + layout_err.maybe_ghosts_and_obstacle() + else { return ControlFlow::Break(None); }; - self.probe_ghosts = vec![*ghost]; + self.probe_ghosts = vec![*infringer_ghost, *infringee_ghost]; self.probe_obstacles = vec![obstacle]; let Some(initial_parent_navnode) = maybe_initial_parent_navnode else {