address review: contour depth counts containers, glTF wrap copies and FBX textures share the import pixel budget

This commit is contained in:
Dylan M. Taylor
2026-11-03 22:28:03 +00:00
parent f99317b89b
commit 0c9ae76636
4 changed files with 48 additions and 22 deletions
+14 -17
View File
@@ -130,24 +130,21 @@ pub fn group_contours(contours: Vec<Vec<P>>) -> Vec<Outline> {
.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<Option<usize>> = (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<usize> = (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<Option<usize>> = 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<usize> = (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<Outline> = vec![];
let mut slot = vec![usize::MAX; n];
+3 -1
View File
@@ -851,6 +851,8 @@ fn parse_within(bytes: &[u8], resolve: &dyn Fn(&str) -> Option<Vec<u8>>, limit:
let mut textures: Vec<Arc<Texture>> = vec![];
let mut material_of: HashMap<i64, usize> = HashMap::new();
let mut texture_of: HashMap<i64, Option<usize>> = 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<Arc<Texture>>| -> Option<usize> {
if let Some(t) = texture_of.get(&tid) {
return *t;
@@ -874,7 +876,7 @@ fn parse_within(bytes: &[u8], resolve: &dyn Fn(&str) -> Option<Vec<u8>>, 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)
})();
+21 -4
View File
@@ -400,10 +400,27 @@ pub(crate) fn parse_with_budget(bytes: &[u8], resolve: &dyn Fn(&str) -> Option<V
let ws = samp.and_then(|s| s.get("wrapS")).and_then(Value::as_u64).unwrap_or(10497);
let wt = samp.and_then(|s| s.get("wrapT")).and_then(Value::as_u64).unwrap_or(10497);
let w = [wrap(ws), wrap(wt)];
let i = *texture_of.entry((src, w)).or_insert_with(|| {
textures.push(if img.wrap == w { img } else { Arc::new(Texture { wrap: w, ..(*img).clone() }) });
textures.len() - 1
});
let i = match texture_of.get(&(src, w)) {
Some(&i) => 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<TexRef> {
+10
View File
@@ -432,6 +432,16 @@ fn contours_group_into_outlines_by_nesting() {
assert_eq!(g.iter().map(|o| o.holes.len()).sum::<usize>(), 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::<usize>(), 0);
}
#[test]
fn gltf_cameras_and_punctual_lights() {
let doc = r#"{