feat(topola-egui): Show shape of infringee in addition to inflated infringer's

This commit is contained in:
Mikolaj Wielgus 2025-09-21 20:22:20 +02:00
parent 3078d9d546
commit f4b78749b1
7 changed files with 57 additions and 33 deletions

View File

@ -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}")]

View File

@ -190,9 +190,9 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
// 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,8 +224,10 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
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) {
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.
@ -233,12 +235,17 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
// 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<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
collider: PrimitiveIndex,
predicate: &impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool,
) -> Option<Collision> {
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<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
|| 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 {

View File

@ -97,7 +97,7 @@ impl<R: AccessRules> Layout<R> {
!drawing
.overlapees(around.into())
.find(|overlapee| {
PrimitiveIndex::from(overlapee.1)
PrimitiveIndex::from(overlapee.2)
.primitive_ref(drawing)
.limbs()
.contains(&infringee)

View File

@ -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;
};

View File

@ -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()),

View File

@ -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()
})

View File

@ -110,11 +110,13 @@ impl<R: AccessRules> ThetastarStrategy<Navmesh, f64, BandTermsegIndex>
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 {