From c1205c4017fcfc54d20afd5d8e1b547143a7225e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 09:09:26 -0700 Subject: [PATCH] Mesh: a point outside the mesh locates on the nearest boundary edge locate_within maps a point outside the outline to the nearest point of the outer edges, so a patch folded past the outline does not take an edge click as an interior one. --- crates/doc/src/live.rs | 3 ++- crates/doc/src/live/mesh.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/doc/src/live.rs b/crates/doc/src/live.rs index 85949436b..304b7fede 100644 --- a/crates/doc/src/live.rs +++ b/crates/doc/src/live.rs @@ -253,8 +253,9 @@ mod tests { assert_eq!(m.locate_within(p, 1.0), None); let (r, c, u, v) = m.locate_within(p, 3.0).unwrap(); assert_eq!((r, c), (0, 1)); + assert_eq!(u, 1.0, "on the right edge"); let q = m.eval(r, c, u, v); - assert!(q.distance(Point::new(100.0, 30.0)) < 0.5, "{q:?}"); + assert!(q.distance(Point::new(100.0, 30.0)) < 1e-6, "{q:?}"); assert_eq!(m.locate_within(Point::new(30.0, 60.0), 3.0), m.locate(Point::new(30.0, 60.0))); } diff --git a/crates/doc/src/live/mesh.rs b/crates/doc/src/live/mesh.rs index b4e74e711..7fac277d3 100644 --- a/crates/doc/src/live/mesh.rs +++ b/crates/doc/src/live/mesh.rs @@ -598,13 +598,12 @@ impl GradientMesh { /// As [`Self::locate`], and for a point outside the mesh within `tol` of its outline, the /// patch and local (u, v) of the nearest point of the mesh's edge. pub fn locate_within(&self, p: Point, tol: f64) -> Option<(usize, usize, f64, f64)> { - use vectorcraft_geom::kurbo::ParamCurveNearest; if !self.is_valid() { return None; } let bp = self.outline().to_bezpath(); - if bp.winding(p) == 0 && !(tol > 0.0 && bp.segments().any(|s| s.nearest(p, 1e-9).distance_sq <= tol * tol)) { - return None; + if bp.winding(p) == 0 { + return self.nearest_edge(p).filter(|e| e.0 <= tol * tol).map(|(_, r, c, u, v)| (r, c, u, v)); } const N: usize = 16; let mut best = (f64::INFINITY, 0, 0, 0.0, 0.0); @@ -641,6 +640,30 @@ impl GradientMesh { Some((r, c, u, v)) } + /// The point of the mesh's outer edge nearest `p`: its squared distance from `p`, and its + /// boundary patch and local (u, v). + fn nearest_edge(&self, p: Point) -> Option<(f64, usize, usize, f64, f64)> { + use vectorcraft_geom::kurbo::ParamCurveNearest; + let (rows, cols) = (self.rows as usize, self.cols as usize); + let mut best: Option<(f64, usize, usize, f64, f64)> = None; + let mut take = |e: CubicBez, at: (usize, usize, Option, Option)| { + let n = e.nearest(p, 1e-9); + if best.is_none_or(|b| n.distance_sq < b.0) { + let (r, c, u, v) = at; + best = Some((n.distance_sq, r, c, u.unwrap_or(n.t), v.unwrap_or(n.t))); + } + }; + for c in 0..cols { + take(self.edge_u(0, c), (0, c, None, Some(0.0))); + take(self.edge_u(rows, c), (rows - 1, c, None, Some(1.0))); + } + for r in 0..rows { + take(self.edge_v(r, 0), (r, 0, Some(0.0), None)); + take(self.edge_v(r, cols), (r, cols - 1, Some(1.0), None)); + } + best + } + /// Index of the mesh point within `tol` of `p`. pub fn point_near(&self, p: Point, tol: f64) -> Option { self.points