3D and Materials: Inflate builds its edge index once

inflate built an edge index (Edges) of the shape's rings, and
Membrane::solve, which inflate calls first, built a second index of the
same rings. Membrane::solve now takes the index that inflate builds.

In a release build, one Edges::new takes 0.08 ms of the 27 ms that
inflate takes on a ring of 2,000 points, and 2.0 ms of 219 ms on a ring
of 1,000 spikes whose 2,000 edges fill 693,816 bucket entries. The
facets do not change: for nine shapes, three views and 96 combinations
of the Inflate settings, the Debug text of all 2,592 results is byte
for byte the same on main and with the change.
This commit is contained in:
dotCooCoo
2026-10-16 02:56:34 +07:00
committed by bflatastic
parent 5a83bcfafa
commit 0155eaf468
2 changed files with 7 additions and 5 deletions
+3 -3
View File
@@ -886,8 +886,8 @@ struct Membrane {
}
impl Membrane {
fn solve(rings: &[Vec<Point>], origin: Point, step: f64, nx: usize, ny: usize) -> Self {
let edges = Edges::new(rings);
/// `edges` is the index of `rings`' edges (`Edges::new(rings)`).
fn solve(rings: &[Vec<Point>], edges: &Edges, origin: Point, step: f64, nx: usize, ny: usize) -> Self {
let node = |i: usize, j: usize| Point::new(origin.x + i as f64 * step, origin.y + j as f64 * step);
let idx = |i: usize, j: usize| j * nx + i;
// A node on the outline itself is held at zero with the outside.
@@ -1035,8 +1035,8 @@ pub fn inflate(path: &PathData, rule: FillRule, bounds: Rect, options: Inflate,
const CELLS: usize = 48;
let cell = size / CELLS as f64;
let (nx, ny) = (((bounds.width() / cell).ceil() as usize).max(1), ((bounds.height() / cell).ceil() as usize).max(1));
let m = Membrane::solve(&rings, bounds.origin(), cell, nx + 1, ny + 1);
let edges = Edges::new(&rings);
let m = Membrane::solve(&rings, &edges, bounds.origin(), cell, nx + 1, ny + 1);
let outline = PathData::new(rings.iter().map(|r| vectorcraft_geom::SubPath::polyline(r, true)).collect());
// A circle of radius r puffs into a half sphere of radius r at 50 % (its membrane peaks at r²/4).
let height = 2.0 * m.peak.sqrt() * o.volume / 50.0;
+4 -2
View File
@@ -284,10 +284,12 @@ fn the_inflate_membrane_matches_known_solutions() {
let mut c = BezPath::new();
c.extend(vectorcraft_geom::kurbo::Shape::path_elements(&vectorcraft_geom::kurbo::Circle::new((100.0, 100.0), 50.0), 0.01));
let (p, b) = shape(&c);
let disc = Membrane::solve(&rings(&p, FillRule::EvenOdd, 100.0, MAX_SOLID_POINTS).unwrap(), b.origin(), b.width() / 48.0, 49, 49);
let r = rings(&p, FillRule::EvenOdd, 100.0, MAX_SOLID_POINTS).unwrap();
let disc = Membrane::solve(&r, &Edges::new(&r), b.origin(), b.width() / 48.0, 49, 49);
assert!(close(disc.peak, 50.0 * 50.0 / 4.0, 625.0 * 0.02), "{}", disc.peak);
let (p, b) = shape(&square(0.0, 0.0, 100.0));
let sq = Membrane::solve(&rings(&p, FillRule::EvenOdd, 100.0, MAX_SOLID_POINTS).unwrap(), b.origin(), 100.0 / 48.0, 49, 49);
let r = rings(&p, FillRule::EvenOdd, 100.0, MAX_SOLID_POINTS).unwrap();
let sq = Membrane::solve(&r, &Edges::new(&r), b.origin(), 100.0 / 48.0, 49, 49);
assert!(close(sq.peak, 0.07367 * 100.0 * 100.0, 736.7 * 0.02), "{}", sq.peak);
// Smooth across the middle: no crease where a distance-to-the-edge height would have one
// (the diagonals). The second difference along a diagonal stays small and even.