3D and Materials: the edge index's search from outside its grid looks along the side

A point outside the grid can be nearer an edge further along the grid's side than one
straight ahead of it; the search stopped a ring early there. The bucket lookup is one
function.
This commit is contained in:
bflatastic committed 2026-10-13 03:14:50 +07:00
1 parent b3ada24da7
commit c3c036daf2
2 files changed
+28 -11

No files matched your search

+15 -11
View File
@@ -701,14 +701,18 @@ impl Edges {
Self { edges, origin: bounds.origin(), size, n: N, buckets }
}
/// The bucket row or column of coordinate `v`, from the grid's origin `o` on that axis.
fn cell(&self, v: f64, o: f64) -> isize {
(((v - o) / self.size).floor() as isize).clamp(0, self.n as isize - 1)
}
/// Is `p` within `tolerance` of an edge? Only the buckets `p` could share an edge with are
/// looked at.
fn near(&self, p: Point, tolerance: f64) -> bool {
self.touch(Rect::new(p.x - tolerance, p.y - tolerance, p.x + tolerance, p.y + tolerance)) && {
let n = self.n as isize;
let index = |v: f64, o: f64| (((v - o) / self.size).floor() as isize).clamp(0, n - 1);
let (i0, i1) = (index(p.x - tolerance, self.origin.x), index(p.x + tolerance, self.origin.x));
let (j0, j1) = (index(p.y - tolerance, self.origin.y), index(p.y + tolerance, self.origin.y));
let (i0, i1) = (self.cell(p.x - tolerance, self.origin.x), self.cell(p.x + tolerance, self.origin.x));
let (j0, j1) = (self.cell(p.y - tolerance, self.origin.y), self.cell(p.y + tolerance, self.origin.y));
(j0..=j1).any(|j| {
(i0..=i1).any(|i| {
self.buckets[(j * n + i) as usize]
@@ -724,9 +728,8 @@ impl Edges {
/// buckets `r` covers comes more than once).
fn each(&self, r: Rect, mut f: impl FnMut(Point, Point)) {
let n = self.n as isize;
let index = |v: f64, o: f64| (((v - o) / self.size).floor() as isize).clamp(0, n - 1);
for j in index(r.y0, self.origin.y)..=index(r.y1, self.origin.y) {
for i in index(r.x0, self.origin.x)..=index(r.x1, self.origin.x) {
for j in self.cell(r.y0, self.origin.y)..=self.cell(r.y1, self.origin.y) {
for i in self.cell(r.x0, self.origin.x)..=self.cell(r.x1, self.origin.x) {
for (a, b) in self.buckets[(j * n + i) as usize].iter().filter_map(|k| self.edges.get(*k as usize)) {
if a.x.min(b.x) <= r.x1 && a.x.max(b.x) >= r.x0 && a.y.min(b.y) <= r.y1 && a.y.max(b.y) >= r.y0 {
f(*a, *b);
@@ -739,8 +742,8 @@ impl Edges {
/// Does any edge pass through `r` (its bounding box, at least, overlapping it)?
fn touch(&self, r: Rect) -> bool {
let n = self.n as isize;
let index = |v: f64, o: f64| (((v - o) / self.size).floor() as isize).clamp(0, n - 1);
let (i0, i1, j0, j1) = (index(r.x0, self.origin.x), index(r.x1, self.origin.x), index(r.y0, self.origin.y), index(r.y1, self.origin.y));
let (i0, i1) = (self.cell(r.x0, self.origin.x), self.cell(r.x1, self.origin.x));
let (j0, j1) = (self.cell(r.y0, self.origin.y), self.cell(r.y1, self.origin.y));
(j0..=j1).any(|j| {
(i0..=i1).any(|i| {
self.buckets[(j * n + i) as usize]
@@ -760,7 +763,7 @@ impl Edges {
}
let n = self.n as isize;
let (ci, cj) = ((fx.floor() as isize).clamp(0, n - 1), (fy.floor() as isize).clamp(0, n - 1));
// How far `p` is outside the grid, which every bucket's distance bound must add.
// How far `p` is outside the grid: the nearest point of the grid to it lies in `p`'s bucket.
let outside = (fx.clamp(0.0, n as f64) - fx).hypot(fy.clamp(0.0, n as f64) - fy) * self.size;
let mut best = f64::INFINITY;
for r in 0..n {
@@ -776,8 +779,9 @@ impl Edges {
}
}
}
// Buckets r + 1 away are at least r buckets from any point of `p`'s own.
if best <= outside + r as f64 * self.size {
// Buckets r + 1 away are at least r buckets from any point of `p`'s own, and that
// far across the grid from a `p` outside it (at right angles to its way in, at worst).
if best <= outside.hypot(r as f64 * self.size) {
break;
}
}
+13
View File
@@ -349,3 +349,16 @@ fn the_edge_index_finds_the_nearest_edge() {
assert!((fast - slow).abs() < 1e-9, "{q:?}: {fast} against {slow}");
}
}
/// From a point outside the grid, an edge further along the grid's side can be nearer than one
/// straight ahead: the search doesn't stop before it has looked there.
#[test]
fn the_edge_index_looks_along_the_side_from_outside() {
let ring = |b: BezPath| rings(&shape(&b).0, 64.0, MAX_SOLID_POINTS).unwrap().remove(0);
// Straight ahead of the point, a little further up its side, and the far corner (a 64 pt grid).
let rings = vec![ring(square(2.5, 0.0, 1.0)), ring(square(0.0, 4.2, 0.8)), ring(square(63.0, 63.0, 1.0))];
let edges = Edges::new(&rings);
let q = Point::new(-10.0, 0.5);
let (fast, slow) = (edges.distance(q), edge_distance(&rings, q));
assert!((fast - slow).abs() < 1e-9, "{fast} against {slow}");
}