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.
This commit is contained in:
dotCooCoo
2026-11-01 01:22:08 +07:00
committed by bflatastic
parent 5a5a7b9ce8
commit c1205c4017
2 changed files with 28 additions and 4 deletions
+2 -1
View File
@@ -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)));
}
+26 -3
View File
@@ -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<f64>, Option<f64>)| {
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<usize> {
self.points