diff --git a/crates/three-d/src/solid/inflate.rs b/crates/three-d/src/solid/inflate.rs index 15da644a9..d80fef185 100644 --- a/crates/three-d/src/solid/inflate.rs +++ b/crates/three-d/src/solid/inflate.rs @@ -28,7 +28,13 @@ fn puff(path: &PathData, rule: FillRule, bounds: Rect, o: &Inflate) -> Result } } // Which rings hold which: each ring against the others, its edges walked only where its - // bounds hold the point, and within [`MAX_CONTAINMENT_EDGES`] edges in all. + // bounds hold the point, and within [`MAX_CONTAINMENT_EDGES`] edges and pairs in all (a pair + // whose bounds miss the point costs a step too: many small rings are a square of pairs). let too_complex = || String::from("The shape is too complex for a 3D effect"); let mut work = 0usize; let mut within = |out: &[Vec], bounds: &[Rect], i: usize| -> Result, String> { let p = out.get(i).and_then(|r| r.first()).copied().unwrap_or_default(); let mut held_by = vec![]; for (j, (o, b)) in out.iter().zip(bounds).enumerate() { + work = work.saturating_add(1); if j == i || !(b.x0 <= p.x && p.x <= b.x1 && b.y0 <= p.y && p.y <= b.y1) { + if work > MAX_CONTAINMENT_EDGES { + return Err(too_complex()); + } continue; } work = work.saturating_add(o.len()); diff --git a/crates/three-d/src/solid/tests.rs b/crates/three-d/src/solid/tests.rs index cdbfa7054..0f78aa712 100644 --- a/crates/three-d/src/solid/tests.rs +++ b/crates/three-d/src/solid/tests.rs @@ -732,3 +732,25 @@ fn nonzero_shapes_keep_the_middle_a_same_way_contour_encloses() { assert!(caps(&nested(false)) > caps(&nested(true)) + 1000.0); assert!(inflate(&nested(false), FillRule::NonZero, outer, Inflate::default(), front()).is_ok()); } + +#[test] +fn inflate_over_bounds_of_subnormal_size_makes_no_grid() { + let (p, _) = shape(&square(0.0, 0.0, 10.0)); + // 5e-324 divides to a zero cell; 1e-307 is a normal size whose cell (size / 48) is subnormal. + for size in [5e-324, 1e-307] { + let tiny = Rect::new(0.0, 0.0, size, size); + assert_eq!(inflate(&p, FillRule::EvenOdd, tiny, Inflate::default(), front()).unwrap().len(), 0, "{size}"); + } +} + +#[test] +fn many_small_rings_far_apart_are_too_complex() { + // 8,000 squares in a row: no square's bounds hold another's corner, yet each pair is a step. + let mut b = BezPath::new(); + for i in 0..8000 { + b.extend(square(i as f64 * 3.0, 0.0, 1.0)); + } + let (p, r) = shape(&b); + let e = rings(&p, FillRule::NonZero, r.width().max(r.height()), usize::MAX).unwrap_err(); + assert!(e.contains("too complex"), "{e}"); +}