From 6992369041781141af7b3d6a36743b1b03d42aa0 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Sat, 14 Mar 2026 18:55:19 +0100 Subject: [PATCH] Filter out bbox-located primitives for which hit-test fails --- topola/src/layout.rs | 40 +++++++++++++++++++++++++++++++++------- topola/src/math.rs | 27 +++++++++++++++++++++++++++ topola/src/navmesher.rs | 26 +++++--------------------- 3 files changed, 65 insertions(+), 28 deletions(-) diff --git a/topola/src/layout.rs b/topola/src/layout.rs index 67df45a..09767f6 100644 --- a/topola/src/layout.rs +++ b/topola/src/layout.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use stable_vec::StableVec; use undoredo::{ApplyDelta, Delta, FlushDelta, Recorder}; -use crate::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId, Vector2}; +use crate::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Vector2, Via, ViaId}; #[derive( Clone, Constructor, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize, @@ -172,6 +172,19 @@ impl Layout { ] } + pub fn segment_contains_point(&self, segment_id: SegmentId, point: Vector2) -> bool { + let endpoints = self.segment_endpoints(segment_id); + let segment = self.segments.get(&segment_id.id()).unwrap(); + let vertices = crate::math::inflated_segment( + endpoints[0].x, + endpoints[0].y, + endpoints[1].x, + endpoints[1].y, + segment.half_width, + ); + point.inside_polygon(&vertices) + } + pub fn segment_bbox(&self, segment_id: SegmentId) -> Rectangle<[i64; 3]> { let endpoints = self.segment_endpoints(segment_id); let layer = self.segments.get(&segment_id.id()).unwrap().layer as i64; @@ -188,23 +201,30 @@ impl Layout { pub fn locate_joints_at_point( &self, layer: usize, - point: [i64; 2], + point: Vector2, ) -> impl Iterator { self.joints_rtree .as_ref() - .locate_all_at_point(&[point[0], point[1], layer as i64]) + .locate_all_at_point(&[point.x, point.y, layer as i64]) .map(|geom_with_data| geom_with_data.data) + .filter(move |joint_id| { + self.joints + .get(&joint_id.id()) + .unwrap() + .contains_point(point) + }) } pub fn locate_segments_at_point( &self, layer: usize, - point: [i64; 2], + point: Vector2, ) -> impl Iterator { self.segments_rtree .as_ref() - .locate_all_at_point(&[point[0], point[1], layer as i64]) + .locate_all_at_point(&[point.x, point.y, layer as i64]) .map(|geom_with_data| geom_with_data.data) + .filter(move |segment_id| self.segment_contains_point(*segment_id, point)) } // TODO: vias. @@ -212,12 +232,18 @@ impl Layout { pub fn locate_polygons_at_point( &self, layer: usize, - point: [i64; 2], + point: Vector2, ) -> impl Iterator { self.polygons_rtree .as_ref() - .locate_all_at_point(&[point[0], point[1], layer as i64]) + .locate_all_at_point(&[point.x, point.y, layer as i64]) .map(|geom_with_data| geom_with_data.data) + .filter(move |polygon_id| { + self.polygons + .get(&polygon_id.id()) + .unwrap() + .contains_point(point) + }) } pub fn pin(&self, pin: PinId) -> &Pin { diff --git a/topola/src/math.rs b/topola/src/math.rs index 3da9606..0b2ed0a 100644 --- a/topola/src/math.rs +++ b/topola/src/math.rs @@ -57,6 +57,33 @@ impl_inside_polygon!(f64); impl_inside_polygon!(i32); impl_inside_polygon!(i64); +/// Returns the four vertices of a segment inflated by `half_width`, forming a convex +/// quadrilateral. The segment goes from (x1, y1) to (x2, y2). +pub fn inflated_segment( + x1: i64, + y1: i64, + x2: i64, + y2: i64, + half_width: u64, +) -> [Vector2; 4] { + let dx = x2 - x1; + let dy = y2 - y1; + + let approx_len = + std::cmp::max(dx.abs(), dy.abs()) + 3 * std::cmp::min(dx.abs(), dy.abs()) / 8; + + // Perpendicular vector scaled to half-width. + let px = -dy * (half_width as i64) / approx_len; + let py = dx * (half_width as i64) / approx_len; + + [ + Vector2::new(x1 + px, y1 + py), + Vector2::new(x2 + px, y2 + py), + Vector2::new(x2 - px, y2 - py), + Vector2::new(x1 - px, y1 - py), + ] +} + macro_rules! impl_rotate_around_point { ($t:ty) => { impl Vector2<$t> { diff --git a/topola/src/navmesher.rs b/topola/src/navmesher.rs index f9959f8..1d7e33f 100644 --- a/topola/src/navmesher.rs +++ b/topola/src/navmesher.rs @@ -6,6 +6,7 @@ use dearcut::RecordingTriangulator; use derive_getters::Getters; use crate::{ + math, Board, primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId}, }; @@ -162,35 +163,18 @@ impl NavmesherBoard { navmesher.insert_polygon( segment.layer, - Self::inflated_segment( + math::inflated_segment( endpoints[0].x, endpoints[0].y, endpoints[1].x, endpoints[1].y, segment.half_width, - ), + ) + .into_iter() + .map(Into::into), ) } - fn inflated_segment(x1: i64, y1: i64, x2: i64, y2: i64, half_width: u64) -> [[i64; 2]; 4] { - let dx = x2 - x1; - let dy = y2 - y1; - - let approx_len = - std::cmp::max(dx.abs(), dy.abs()) + 3 * std::cmp::min(dx.abs(), dy.abs()) / 8; - - // Perpendicular vector scaled to half-width. - let px = -dy * (half_width as i64) / approx_len; - let py = dx * (half_width as i64) / approx_len; - - [ - [x1 + px, y1 + py], - [x2 + px, y2 + py], - [x2 - px, y2 - py], - [x1 - px, y1 - py], - ] - } - pub fn insert_via(&mut self, via: Via) -> ViaId { // TODO: Insert into navmesh. self.board.add_via(via)