Gradient mesh: flat pieces follow the quad budget's subdivision

A mesh over the quad budget is cut coarser than asked (#3592); its flat pieces now use the same subdivision, so each patch takes its own quads.
This commit is contained in:
bflatastic
2026-10-31 19:43:16 +07:00
parent 9259434a89
commit 2c28ccf020
2 changed files with 25 additions and 4 deletions
+15
View File
@@ -204,6 +204,21 @@ mod tests {
assert!(huge.quads(1).is_empty());
}
/// Flat pieces of a mesh over the quad budget follow the coarser subdivision [`GradientMesh::quads`]
/// gives way to: each patch's pieces are its own quads', so together they cover the mesh once.
#[test]
fn a_large_meshs_pieces_follow_the_quad_budget() {
let m = GradientMesh::from_surface(60, 60, &|u, v| Point::new(u * 300.0, v * 300.0), &|u, v| Color::rgb(u as f32, v as f32, 0.5));
let area = |pts: &[Point]| {
let n = pts.len();
(0..n).map(|i| pts[i].x * pts[(i + 1) % n].y - pts[(i + 1) % n].x * pts[i].y).sum::<f64>().abs() / 2.0
};
let pieces = m.pieces(32);
assert!(pieces.len() >= 3600, "{} pieces", pieces.len());
let total: f64 = pieces.iter().map(|p| area(&p.pts)).sum();
assert!((total - 90_000.0).abs() < 90.0, "covered {total}");
}
/// A file's rows and columns can be anything: a point count too large to compute is no match
/// for the points (u32::MAX × u32::MAX wrapped to 0 and passed for a grid without points).
#[test]
+10 -4
View File
@@ -367,13 +367,19 @@ impl GradientMesh {
(Color::rgb(rgb[0], rgb[1], rgb[2]), op)
}
/// Tessellate into `n`×`n` quads per patch, each coloured at its centre and its corners.
/// The quads per patch side [`Self::quads`] cuts at when `n` is asked: 1 to 64, and coarser
/// where the patches at `n` would pass [`Self::MAX_QUADS`] (a quad per patch is the least).
fn subdivision(&self, n: usize) -> usize {
n.clamp(1, 64).min(((Self::MAX_QUADS / self.patches().max(1)) as f64).sqrt() as usize).max(1)
}
/// Tessellate into `n`×`n` quads per patch ([`Self::subdivision`]), each coloured at its centre
/// and its corners.
pub fn quads(&self, n: usize) -> Vec<MeshQuad> {
if !self.is_valid() {
return vec![];
}
// Finer than the budget allows gives way: a quad per patch is the least.
let n = n.clamp(1, 64).min(((Self::MAX_QUADS / self.patches()) as f64).sqrt() as usize).max(1);
let n = self.subdivision(n);
let mut out = Vec::with_capacity(self.patches() * n * n);
let mut grid = vec![Point::ZERO; (n + 1) * (n + 1)];
let mut colors = vec![(Color::default(), 0.0f32); (n + 1) * (n + 1)];
@@ -411,7 +417,7 @@ impl GradientMesh {
/// all its quads. A piece's outline runs through its quads' outer corners, so it covers what
/// they covered, and it takes the colour at its centre.
pub fn pieces(&self, n: usize) -> Vec<MeshPiece> {
let n = n.clamp(1, 64);
let n = self.subdivision(n);
let quads = self.quads(n);
let cols = (self.cols as usize).max(1);
let mut out = Vec::with_capacity(quads.len());