Puppet: clamp an imported Expansion before padding the outline grid

This commit is contained in:
Dylan M. Taylor
2026-11-02 20:33:36 -05:00
parent 7870b5916a
commit 8594cf0d41
2 changed files with 36 additions and 4 deletions
+20 -4
View File
@@ -614,13 +614,29 @@ pub fn triangulate(outline: &[[f64; 2]], opts: &MeshOpts) -> Mesh {
Mesh { verts, tris, outline: poly }
}
/// The Expansion range the outline honours, in layer pixels (the property's slider range). A
/// project file can store any number; outside this range it is clamped, so the padded grid
/// below stays a few hundred pixels larger than the buffer.
pub const EXPANSION_RANGE: (f64, f64) = (-100.0, 200.0);
/// Most buffer pixels of padding the outline grid takes on each side (Expansion × buffer scale).
const MAX_PAD: f64 = 4096.0;
/// The outline of the alpha region under `seed` (buffer pixels → layer space via `scale` and
/// `offset`), expanded by `expansion` layer pixels.
/// `offset`), expanded by `expansion` layer pixels (clamped to [`EXPANSION_RANGE`]).
pub fn outline(alpha: &[f32], w: usize, h: usize, scale: f64, offset: [f64; 2], seed: [f64; 2], expansion: f64) -> Vec<[f64; 2]> {
let expansion = if expansion.is_finite() { expansion.clamp(EXPANSION_RANGE.0, EXPANSION_RANGE.1) } else { 0.0 };
// Pad so expansion can grow past the buffer edge.
let pad = (expansion.max(0.0) * scale).ceil() as usize + 2;
let (pw, ph) = (w + 2 * pad, h + 2 * pad);
let mut on = vec![false; pw * ph];
let pad = (expansion.max(0.0) * scale).ceil();
if !pad.is_finite() || pad > MAX_PAD {
return vec![];
}
let pad = pad as usize + 2;
let Some((pw, ph)) = w.checked_add(2 * pad).zip(h.checked_add(2 * pad)) else { return vec![] };
let Some(cells) = pw.checked_mul(ph) else { return vec![] };
if alpha.len() < w.saturating_mul(h) {
return vec![];
}
let mut on = vec![false; cells];
for y in 0..h {
for x in 0..w {
on[(y + pad) * pw + x + pad] = alpha[y * w + x] >= 0.5;
+16
View File
@@ -106,6 +106,22 @@ fn expansion_grows_the_outline() {
assert!((am - pi * 225.0).abs() < 60.0, "{am}");
}
/// An imported Expansion far past the property's range is clamped instead of padding the grid
/// by that many pixels (a 1e12 expansion would ask for terabytes).
#[test]
fn hostile_expansion_is_clamped() {
let (w, h) = (40, 40);
let alpha = disc_alpha(w, h, [20.0, 20.0], 10.0);
let max = signed_area(&outline(&alpha, w, h, 1.0, [0.0; 2], [20.0, 20.0], EXPANSION_RANGE.1)).abs();
for e in [1e12, f64::MAX, f64::INFINITY] {
let a = signed_area(&outline(&alpha, w, h, 1.0, [0.0; 2], [20.0, 20.0], e)).abs();
assert!((a - max).abs() < 1.0 || e.is_infinite(), "{e}: {a} vs {max}");
}
assert!(outline(&alpha, w, h, 1.0, [0.0; 2], [20.0, 20.0], f64::NAN).len() >= 3);
assert!(outline(&alpha, w, h, 1e9, [0.0; 2], [20.0, 20.0], 100.0).is_empty());
let _ = outline(&alpha, w, h, 1.0, [0.0; 2], [20.0, 20.0], -1e12);
}
fn rect_mesh(w: f64, h: f64, tris: f64) -> Mesh {
triangulate(&[[0.0, 0.0], [w, 0.0], [w, h], [0.0, h]], &MeshOpts { triangles: tris, density: 50.0, expansion: 0.0 })
}