diff --git a/crates/model/src/extrude.rs b/crates/model/src/extrude.rs index 4cf575f28..f7afdabe6 100644 --- a/crates/model/src/extrude.rs +++ b/crates/model/src/extrude.rs @@ -130,24 +130,21 @@ pub fn group_contours(contours: Vec>) -> Vec { .iter() .map(|c| c.iter().fold([[f64::MAX; 2], [f64::MIN; 2]], |[lo, hi], p| [[lo[0].min(p[0]), lo[1].min(p[1])], [hi[0].max(p[0]), hi[1].max(p[1])]])) .collect(); - // The innermost container of each contour: the smallest larger contour holding a vertex of - // it (nested contours do not cross); its bounding box must hold the vertex first. - let parent: Vec> = (0..n) - .map(|i| { - let p = cs[i][0]; - (0..n) - .filter(|&j| j != i && area[j] > area[i]) - .filter(|&j| (bounds[j][0][0]..=bounds[j][1][0]).contains(&p[0]) && (bounds[j][0][1]..=bounds[j][1][1]).contains(&p[1])) - .filter(|&j| point_in(p, &cs[j])) - .min_by(|&a, &b| area[a].total_cmp(&area[b])) - }) - .collect(); - // Nesting depth, containers (the larger contours) first. - let mut by_area: Vec = (0..n).collect(); - by_area.sort_by(|&a, &b| area[b].total_cmp(&area[a])); + // The containers of each contour: the larger contours holding a vertex of it (nested contours + // do not cross); the bounding box must hold the vertex first. Depth is their count, so + // overlapping (non-nested) paths keep every container they sit inside; the innermost + // container (smallest area) takes a hole. + let mut parent: Vec> = vec![None; n]; let mut depth = vec![0usize; n]; - for &i in &by_area { - depth[i] = parent[i].map_or(0, |p| depth[p] + 1); + for i in 0..n { + let p = cs[i][0]; + let containers: Vec = (0..n) + .filter(|&j| j != i && area[j] > area[i]) + .filter(|&j| (bounds[j][0][0]..=bounds[j][1][0]).contains(&p[0]) && (bounds[j][0][1]..=bounds[j][1][1]).contains(&p[1])) + .filter(|&j| point_in(p, &cs[j])) + .collect(); + depth[i] = containers.len(); + parent[i] = containers.into_iter().min_by(|&a, &b| area[a].total_cmp(&area[b])); } let mut outlines: Vec = vec![]; let mut slot = vec![usize::MAX; n]; diff --git a/crates/model/src/fbx.rs b/crates/model/src/fbx.rs index bca387ff2..c6f6c9523 100644 --- a/crates/model/src/fbx.rs +++ b/crates/model/src/fbx.rs @@ -851,6 +851,8 @@ fn parse_within(bytes: &[u8], resolve: &dyn Fn(&str) -> Option>, limit: let mut textures: Vec> = vec![]; let mut material_of: HashMap = HashMap::new(); let mut texture_of: HashMap> = HashMap::new(); + // One pixel budget for every texture of the import. + let mut pixels_left = crate::MAX_IMPORT_TEXTURE_PIXELS; let mut load_texture = |tid: i64, textures: &mut Vec>| -> Option { if let Some(t) = texture_of.get(&tid) { return *t; @@ -874,7 +876,7 @@ fn parse_within(bytes: &[u8], resolve: &dyn Fn(&str) -> Option>, limit: resolve(&f).or_else(|| resolve(f.rsplit('/').next()?)) }) })?; - let t = Texture::decode(&bytes).ok()?; + let t = Texture::decode_within(&bytes, &mut pixels_left).ok()?; textures.push(Arc::new(t)); Some(textures.len() - 1) })(); diff --git a/crates/model/src/gltf.rs b/crates/model/src/gltf.rs index c614ae7f8..63a4a0076 100644 --- a/crates/model/src/gltf.rs +++ b/crates/model/src/gltf.rs @@ -400,10 +400,27 @@ pub(crate) fn parse_with_budget(bytes: &[u8], resolve: &dyn Fn(&str) -> Option i, + None => { + let tex = if img.wrap == w { + img + } else { + // A wrap variant copies the pixels, so it is charged to the import budget too; + // one that does not fit is left out like an undecodable image. + let cost = u64::from(img.width) * u64::from(img.height); + if cost > pixels_left { + tex_map.push(None); + continue; + } + pixels_left -= cost; + Arc::new(Texture { wrap: w, ..(*img).clone() }) + }; + textures.push(tex); + texture_of.insert((src, w), textures.len() - 1); + textures.len() - 1 + } + }; tex_map.push(Some(i)); } let tref = |v: Option<&Value>| -> Option { diff --git a/crates/model/src/tests.rs b/crates/model/src/tests.rs index 843a9db9e..a57ea7e9a 100644 --- a/crates/model/src/tests.rs +++ b/crates/model/src/tests.rs @@ -432,6 +432,16 @@ fn contours_group_into_outlines_by_nesting() { assert_eq!(g.iter().map(|o| o.holes.len()).sum::(), 2); } +#[test] +fn overlapping_contours_keep_every_container_in_their_depth() { + let rect = |x0: f64, y0: f64, x1: f64, y1: f64| vec![[x0, y0], [x1, y0], [x1, y1], [x0, y1]]; + // C's first vertex lies inside both A and B, which overlap without nesting: C is two deep, + // an outer contour, not a hole cut out of B. + let g = group_contours(vec![rect(0.0, 0.0, 10.0, 10.0), rect(5.0, -2.0, 13.0, 8.0), rect(9.0, 1.0, 12.0, 2.0)]); + assert_eq!(g.len(), 3); + assert_eq!(g.iter().map(|o| o.holes.len()).sum::(), 0); +} + #[test] fn gltf_cameras_and_punctual_lights() { let doc = r#"{