From 8a66ece14c4cc89224171fb09f92bebe8e55e87a Mon Sep 17 00:00:00 2001 From: Ellen Emilia Anna Zscheile Date: Tue, 22 Apr 2025 21:41:46 +0200 Subject: [PATCH] feat(layout): cache convex hull on polygon initialization --- src/layout/layout.rs | 78 ++++++++++++++++++++++++++++++++++++++++---- src/layout/poly.rs | 16 ++++----- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/layout/layout.rs b/src/layout/layout.rs index 99d6c2e..77b2ed9 100644 --- a/src/layout/layout.rs +++ b/src/layout/layout.rs @@ -5,7 +5,7 @@ use contracts_try::debug_ensures; use derive_getters::Getters; use enum_dispatch::enum_dispatch; -use geo::Point; +use geo::{algorithm::ConvexHull, IsConvex, Point}; use rstar::AABB; use crate::{ @@ -52,7 +52,11 @@ pub enum CompoundWeight { Via(ViaWeight), } -pub type CompoundEntryKind = (); +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CompoundEntryKind { + Normal, + NotInConvexHull, +} /// The alias to differ node types pub type NodeIndex = GenericNode>; @@ -119,7 +123,12 @@ impl Layout { }), ) { Ok(dot) => { - self.drawing.add_to_compound(recorder, dot, (), compound); + self.drawing.add_to_compound( + recorder, + dot, + CompoundEntryKind::Normal, + compound, + ); dots.push(dot); } Err(err) => { @@ -233,7 +242,7 @@ impl Layout { self.drawing.add_to_compound( recorder, GenericIndex::<()>::new(i.petgraph_index()), - (), + CompoundEntryKind::Normal, poly_compound, ); } @@ -251,9 +260,64 @@ impl Layout { }), ); + if !shape.exterior().is_convex() { + // create a temporary RTree to speed up lookups from O(n²) to O(n log n) + #[derive(Clone, Copy)] + struct Rto { + pt: Point, + idx: PrimitiveIndex, + } + impl rstar::RTreeObject for Rto { + type Envelope = rstar::AABB<[f64; 2]>; + fn envelope(&self) -> rstar::AABB<[f64; 2]> { + rstar::AABB::from_point([self.pt.x(), self.pt.y()]) + } + } + impl rstar::PointDistance for Rto { + fn distance_2(&self, point: &[f64; 2]) -> f64 { + let d_x = self.pt.0.x - point[0]; + let d_y = self.pt.0.y - point[1]; + d_x * d_x + d_y * d_y + } + } + + let mut temp_rtree = rstar::RTree::::new(); + + for &idx in nodes { + let PrimitiveIndex::FixedDot(dot) = idx else { + continue; + }; + + let pt = self.drawing.geometry().dot_weight(dot.into()).pos(); + temp_rtree.insert(Rto { pt, idx }); + } + + // compute the convex hull + let convex_hull_exterior = shape.exterior().convex_hull().into_inner().0; + for pt in convex_hull_exterior { + // note that the convex hull should be a strict subset of the points + // on the exterior of `shape` + assert!(temp_rtree.pop_nearest_neighbor(&[pt.x, pt.y]).is_some()); + } + + // mark all elements which aren't in the convex hull + for Rto { idx, .. } in temp_rtree { + self.drawing.add_to_compound( + recorder, + GenericIndex::<()>::new(idx.petgraph_index()), + CompoundEntryKind::NotInConvexHull, + poly_compound, + ); + } + } + // maybe this should be a different edge kind - self.drawing - .add_to_compound(recorder, apex, (), poly_compound); + self.drawing.add_to_compound( + recorder, + apex, + CompoundEntryKind::NotInConvexHull, + poly_compound, + ); assert!(is_apex(&self.drawing, apex)); @@ -304,7 +368,7 @@ impl Layout { pub fn poly_members( &self, poly: GenericIndex, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { self.drawing .geometry() .compound_members(GenericIndex::new(poly.petgraph_index())) diff --git a/src/layout/poly.rs b/src/layout/poly.rs index 9150600..1578b7b 100644 --- a/src/layout/poly.rs +++ b/src/layout/poly.rs @@ -61,18 +61,16 @@ impl<'a, R> PolyRef<'a, R> { Self { index, drawing } } - fn is_apex(&self, dot: FixedDotIndex) -> bool { - is_apex(self.drawing, dot) - } - pub fn apex(&self) -> FixedDotIndex { self.drawing .geometry() .compound_members(self.index.into()) - .find_map(|(_kind, primitive_node)| { - if let PrimitiveIndex::FixedDot(dot) = primitive_node { - if self.is_apex(dot) { - return Some(dot); + .find_map(|(kind, primitive_node)| { + if kind == CompoundEntryKind::NotInConvexHull { + if let PrimitiveIndex::FixedDot(dot) = primitive_node { + if is_apex(self.drawing, dot) { + return Some(dot); + } } } @@ -110,7 +108,7 @@ impl MakePolygon for PolyRef<'_, R> { return None; }; - if self.is_apex(dot) { + if is_apex(self.drawing, dot) { None } else { Some(self.drawing.geometry().dot_weight(dot.into()).pos())