diff --git a/crates/model/src/extrude.rs b/crates/model/src/extrude.rs index 4f4357853..71d550367 100644 --- a/crates/model/src/extrude.rs +++ b/crates/model/src/extrude.rs @@ -157,12 +157,17 @@ pub fn group_contours(contours: Vec>) -> Vec { let mut slot = vec![usize::MAX; n]; let mut order: Vec = (0..n).collect(); order.sort_by_key(|&i| depth[i]); - for i in order { + // Every outline gets its slot before any hole is attached: with overlapping paths a hole's + // parent can be deeper than the hole itself. + for &i in &order { if depth[i].is_multiple_of(2) { slot[i] = outlines.len(); outlines.push(Outline { outer: cs[i].clone(), holes: vec![] }); - } else if let Some(p) = parent[i] - && slot[p] != usize::MAX + } + } + for &i in &order { + if !depth[i].is_multiple_of(2) + && let Some(p) = parent[i] { outlines[slot[p]].holes.push(cs[i].clone()); } diff --git a/crates/model/src/tests.rs b/crates/model/src/tests.rs index 934737bc1..b36e32e78 100644 --- a/crates/model/src/tests.rs +++ b/crates/model/src/tests.rs @@ -452,6 +452,23 @@ fn a_contour_inside_a_hole_takes_the_innermost_outline_not_the_hole() { assert_eq!(g.iter().map(|o| o.holes.len()).collect::>(), [1, 1], "C is a hole of A, D a hole of B (not dropped)"); } +#[test] +fn a_hole_whose_outline_is_deeper_than_it_is_still_attached() { + let rect = |x0: f64, y0: f64, x1: f64, y1: f64| vec![[x0, y0], [x1, y0], [x1, y1], [x0, y1]]; + // D is three deep and its innermost outline A four deep: A's slot must exist before D is attached. + let a = rect(0.0, 0.0, 10.0, 10.0); + let g = group_contours(vec![ + a.clone(), + rect(-1.0, -1.0, 20.0, 20.0), + rect(-2.0, -2.0, 25.0, 25.0), + rect(5.0, 5.0, 6.0, 6.0), + rect(-100.0, -100.0, 3.0, 3.0), + rect(-0.5, -1000.0, 0.5, 1000.0), + ]); + let o = g.iter().find(|o| o.outer == a).expect("A is an outline"); + assert_eq!(o.holes.len(), 1, "D is a hole of A"); +} + #[test] fn gltf_images_naming_one_file_share_their_wrap_variants() { let png = {