#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct Control(pub [f32; 8]);
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct Patch {
pub controls: [Control; 16],
}
#[derive(Debug, PartialEq)]
pub(crate) enum MeshError {
Dimensions,
NonFinite,
QualityLimit,
}
pub(crate) fn patches(rows: &[Vec<Control>]) -> Result<Vec<Patch>, MeshError> {
let height = rows.len();
let width = rows.first().map_or(0, Vec::len);
if !(2..=8).contains(&width)
|| !(2..=8).contains(&height)
|| rows.iter().any(|row| row.len() != width)
{
return Err(MeshError::Dimensions);
}
if rows
.iter()
.flatten()
.any(|p| p.0.iter().any(|v| !v.is_finite()))
{
return Err(MeshError::NonFinite);
}
let horizontal: Vec<Vec<[Control; 4]>> = rows
.iter()
.map(|row| (0..width - 1).map(|x| curve(row, x)).collect())
.collect();
let mut result = Vec::with_capacity((width - 1) * (height - 1));
for y in 0..height - 1 {
for x in 0..width - 1 {
let mut controls = [Control([0.0; 8]); 16];
for u in 0..4 {
let column: Vec<_> = horizontal.iter().map(|row| row[x][u]).collect();
for (v, control) in curve(&column, y).into_iter().enumerate() {
controls[v * 4 + u] = control;
}
}
if controls.iter().any(|p| p.0.iter().any(|v| !v.is_finite())) {
return Err(MeshError::NonFinite);
}
result.push(Patch { controls });
}
}
Ok(result)
}
fn curve(points: &[Control], i: usize) -> [Control; 4] {
let tangent = |index: usize, channel: usize| {
let before = index.saturating_sub(1);
let after = (index + 1).min(points.len() - 1);
(points[after].0[channel] as f64 - points[before].0[channel] as f64)
/ (after - before) as f64
};
let a = points[i];
let b = points[i + 1];
[
a,
Control(std::array::from_fn(|c| {
(a.0[c] as f64 + tangent(i, c) / 3.0) as f32
})),
Control(std::array::from_fn(|c| {
(b.0[c] as f64 - tangent(i + 1, c) / 3.0) as f32
})),
b,
]
}
impl Patch {
#[cfg(test)]
pub(crate) fn evaluate(&self, u: f64, v: f64) -> [f64; 8] {
let bernstein = |t: f64| {
let s = 1.0 - t;
[s * s * s, 3.0 * s * s * t, 3.0 * s * t * t, t * t * t]
};
let bu = bernstein(u);
let bv = bernstein(v);
std::array::from_fn(|channel| {
(0..4)
.flat_map(|y| (0..4).map(move |x| (x, y)))
.map(|(x, y)| bu[x] * bv[y] * self.controls[y * 4 + x].0[channel] as f64)
.sum()
})
}
fn curvature(&self, channel: usize) -> f64 {
let p = |x: usize, y: usize| self.controls[y * 4 + x].0[channel] as f64;
let mut uu = 0.0f64;
let mut vv = 0.0f64;
let mut uv = 0.0f64;
for y in 0..4 {
for x in 0..2 {
uu = uu.max(6.0 * (p(x + 2, y) - 2.0 * p(x + 1, y) + p(x, y)).abs());
vv = vv.max(6.0 * (p(y, x + 2) - 2.0 * p(y, x + 1) + p(y, x)).abs());
}
}
for y in 0..3 {
for x in 0..3 {
uv = uv.max(9.0 * (p(x + 1, y + 1) - p(x + 1, y) - p(x, y + 1) + p(x, y)).abs());
}
}
(uu + 2.0 * uv + vv) / 8.0
}
}
pub(crate) fn subdivision(
patches: &[Patch],
pixels_per_unit: [f64; 2],
previous: u32,
) -> Result<u32, MeshError> {
if pixels_per_unit.iter().any(|v| !v.is_finite() || *v < 0.0) {
return Err(MeshError::NonFinite);
}
let mut ratio = 0.0f64;
for patch in patches {
let geometry =
patch.curvature(0) * pixels_per_unit[0] + patch.curvature(1) * pixels_per_unit[1];
ratio = ratio.max(geometry / 0.125);
for channel in 2..6 {
ratio = ratio.max(patch.curvature(channel) * 1024.0);
}
}
let mut count = 1u32;
while f64::from(count * count) < ratio && count < 128 {
count *= 2;
}
if f64::from(count * count) < ratio {
return Err(MeshError::QualityLimit);
}
if previous.is_power_of_two() && previous <= 128 && count < previous && count * 2 >= previous {
count = previous;
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
fn lattice(width: usize, height: usize) -> Vec<Vec<Control>> {
(0..height)
.map(|y| {
(0..width)
.map(|x| {
let x = x as f32;
let y = y as f32;
Control([x + 0.1 * y * y, y + 0.2 * x * x, x * y, x, y, 1.0, 0.0, 0.0])
})
.collect()
})
.collect()
}
#[test]
fn anchors_and_shared_edges_and_derivatives() {
for size in [2, 3, 8] {
let rows = lattice(size, size);
let patches = patches(&rows).unwrap();
let width = size - 1;
for y in 0..width {
for x in 0..width {
let patch = &patches[y * width + x];
for (u, v) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
assert_eq!(
patch.evaluate(u as f64, v as f64),
rows[y + v][x + u].0.map(f64::from)
);
}
for i in 0..4 {
if x + 1 < width {
let next = &patches[y * width + x + 1];
assert_eq!(patch.controls[i * 4 + 3], next.controls[i * 4]);
for c in 0..6 {
let left =
patch.controls[i * 4 + 3].0[c] - patch.controls[i * 4 + 2].0[c];
let right =
next.controls[i * 4 + 1].0[c] - next.controls[i * 4].0[c];
assert!((left - right).abs() < 0.00001);
}
}
if y + 1 < width {
let next = &patches[(y + 1) * width + x];
assert_eq!(patch.controls[12 + i], next.controls[i]);
for c in 0..6 {
let above =
patch.controls[12 + i].0[c] - patch.controls[8 + i].0[c];
let below = next.controls[4 + i].0[c] - next.controls[i].0[c];
assert!((above - below).abs() < 0.00001);
}
}
}
}
}
}
}
#[test]
fn invalid_inputs_and_quality_cap_are_explicit() {
for rows in [
vec![],
lattice(1, 2),
lattice(2, 9),
vec![vec![Control([0.0; 8]); 2], vec![]],
] {
assert_eq!(patches(&rows), Err(MeshError::Dimensions));
}
let mut rows = lattice(3, 3);
rows[1][1].0[0] = f32::NAN;
assert_eq!(patches(&rows), Err(MeshError::NonFinite));
let field = patches(&lattice(3, 3)).unwrap();
assert_eq!(
subdivision(&field, [1e9; 2], 0),
Err(MeshError::QualityLimit)
);
let count = subdivision(&field, [1.0; 2], 0).unwrap();
assert_eq!(subdivision(&field, [1.0; 2], count * 2).unwrap(), count * 2);
}
#[test]
fn tessellation_error_is_bounded_between_vertices() {
let field = patches(&lattice(3, 3)).unwrap();
let n = subdivision(&field, [100.0; 2], 0).unwrap();
for patch in field {
for y in 0..n {
for x in 0..n {
for (s, t) in [(0.2, 0.3), (0.8, 0.7), (0.3, 0.3)] {
let uv = |dx, dy| {
patch.evaluate((x as f64 + dx) / n as f64, (y as f64 + dy) / n as f64)
};
let exact = uv(s, t);
let (vertices, weights) = if s >= t {
(
[uv(0.0, 0.0), uv(1.0, 0.0), uv(1.0, 1.0)],
[1.0 - s, s - t, t],
)
} else {
(
[uv(0.0, 0.0), uv(1.0, 1.0), uv(0.0, 1.0)],
[1.0 - t, s, t - s],
)
};
for c in 0..6 {
let approx: f64 = (0..3).map(|i| weights[i] * vertices[i][c]).sum();
let limit = if c < 2 { 0.125 / 100.0 } else { 1.0 / 1024.0 };
assert!((exact[c] - approx).abs() <= limit);
}
}
}
}
}
}
}