nmarshall's picture
geometry: patch tessellation
4536b63
Raw History Blame Contribute Delete
6.26 kB
use crate::{math::*, *};
use mb3_dat::Patch9;
#[derive(Clone, Copy, Debug)]
pub struct Sample {
pub point: DVec3,
pub normal: DVec3,
}
#[derive(Debug)]
pub struct Mesh {
pub points: Vec<DVec3>,
pub normals: Vec<DVec3>,
pub columns: usize,
pub rows: usize,
pub bounds: Aabb,
pub stats: Stats,
}
fn shape(p: &Patch9) -> Result<(usize, usize)> {
let au = p.anchors_u as usize;
let av = p.anchors_v as usize;
if au < 3 || av < 3 || au % 2 == 0 || av % 2 == 0 || p.quads_u < 2 || p.quads_v < 2 {
return Err(Error::InvalidDimensions);
}
if p.anchors.len() != au.checked_mul(av).ok_or(Error::Overflow)? {
return Err(Error::InconsistentData);
}
Ok((au / 2, av / 2))
}
fn basis(t: f64) -> ([f64; 3], [f64; 3]) {
(
[(1. - t) * (1. - t), 2. * t * (1. - t), t * t],
[-2. * (1. - t), 2. - 4. * t, 2. * t],
)
}
fn evaluate(
p: &Patch9,
tile_u: usize,
tile_v: usize,
u: f64,
v: f64,
policy: Policy,
) -> Result<Sample> {
let (bu, du) = basis(u);
let (bv, dv) = basis(v);
let mut point = [0.; 3];
let mut tangent_u = [0.; 3];
let mut tangent_v = [0.; 3];
for j in 0..3 {
for i in 0..3 {
let index = (tile_u * 2 + i) + (tile_v * 2 + j) * (p.anchors_u as usize);
let a = from_f32(*p.anchors.get(index).ok_or(Error::InconsistentData)?)?;
point = add(point, scale(a, bu[i] * bv[j]));
tangent_u = add(tangent_u, scale(a, du[i] * bv[j]));
tangent_v = add(tangent_v, scale(a, bu[i] * dv[j]));
}
}
let n = cross(unit(tangent_v)?, unit(tangent_u)?);
if length(n) <= policy.angular_epsilon {
return Err(Error::Degenerate);
}
Ok(Sample {
point: finite(point)?,
normal: unit(n)?,
})
}
pub fn sample(
p: &Patch9,
tile_u: usize,
tile_v: usize,
u: f64,
v: f64,
policy: Policy,
) -> Result<Sample> {
let policy = policy.validate()?;
let (tu, tv) = shape(p)?;
if tile_u >= tu
|| tile_v >= tv
|| !u.is_finite()
|| !v.is_finite()
|| !(0. ..=1.).contains(&u)
|| !(0. ..=1.).contains(&v)
{
return Err(Error::InvalidDimensions);
}
evaluate(p, tile_u, tile_v, u, v, policy)
}
pub fn tessellate(p: &Patch9, policy: Policy, limits: ComputeLimits) -> Result<Mesh> {
let policy = policy.validate()?;
let (tu, tv) = shape(p)?;
let qu = p.quads_u as usize;
let qv = p.quads_v as usize;
let columns = tu
.checked_mul(qu)
.and_then(|n| n.checked_add(1))
.ok_or(Error::Overflow)?;
let rows = tv
.checked_mul(qv)
.and_then(|n| n.checked_add(1))
.ok_or(Error::Overflow)?;
let n = columns.checked_mul(rows).ok_or(Error::Overflow)?;
if n > limits.max_vertices {
return Err(Error::Limit);
}
let mut b = Budget {
limits,
stats: Stats::default(),
};
b.work(p.anchors.len())?;
for v in &p.anchors {
from_f32(*v)?;
}
let mut points = b.reserve(n)?;
let mut normals = b.reserve(n)?;
for y in 0..rows {
let ty = (y / qv).min(tv - 1);
let v = (y - ty * qv) as f64 / qv as f64;
for x in 0..columns {
b.work(9)?;
let tx = (x / qu).min(tu - 1);
let u = (x - tx * qu) as f64 / qu as f64;
let s = evaluate(p, tx, ty, u, v, policy)?;
points.push(s.point);
normals.push(s.normal);
}
}
let bounds = Aabb::from_points(points.iter().copied())?.ok_or(Error::Degenerate)?;
b.stats.vertices = n;
Ok(Mesh {
points,
normals,
columns,
rows,
bounds,
stats: b.stats,
})
}
#[cfg(test)]
pub(crate) fn flat(au: u32, av: u32) -> Patch9 {
Patch9 {
anchors_u: au,
anchors_v: av,
quads_u: 2,
quads_v: 2,
anchors: (0..av)
.flat_map(|v| (0..au).map(move |u| [u as f32, 0., v as f32]))
.collect(),
computed: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flat_patch_endpoints_grid_and_normals() {
let p = flat(3, 3);
let m = tessellate(&p, Policy::default(), ComputeLimits::default()).unwrap();
assert_eq!((m.columns, m.rows), (3, 3));
assert_eq!(m.points[0], [0., 0., 0.]);
assert_eq!(m.points[8], [2., 0., 2.]);
assert!(m.normals.iter().all(|v| *v == [0., 1., 0.]));
assert_eq!(m.bounds.min, [0.; 3]);
}
#[test]
fn curvature_midpoint_and_shared_seams() {
let mut p = flat(5, 3);
p.anchors[1][1] = 4.;
p.anchors[6][1] = 4.;
p.anchors[11][1] = 4.;
let left = sample(&p, 0, 0, 1., 0.5, Policy::default()).unwrap();
let right = sample(&p, 1, 0, 0., 0.5, Policy::default()).unwrap();
assert_eq!(left.point, right.point);
assert_eq!(
sample(&p, 0, 0, 0.5, 0.5, Policy::default()).unwrap().point,
[1., 2., 1.]
);
let m = tessellate(&p, Policy::default(), ComputeLimits::default()).unwrap();
assert_eq!((m.columns, m.rows), (5, 3));
}
#[test]
fn bad_shapes_degeneracy_and_limits() {
let mut p = flat(3, 3);
p.anchors.pop();
assert!(tessellate(&p, Policy::default(), ComputeLimits::default()).is_err());
p = flat(3, 3);
p.anchors.fill([0.; 3]);
assert!(tessellate(&p, Policy::default(), ComputeLimits::default()).is_err());
p = flat(3, 3);
for l in [
ComputeLimits {
max_vertices: 8,
..Default::default()
},
ComputeLimits {
max_work: 1,
..Default::default()
},
ComputeLimits {
max_owned_bytes: 1,
..Default::default()
},
] {
assert!(tessellate(&p, Policy::default(), l).is_err());
}
assert!(sample(&p, 1, 0, 0., 0., Policy::default()).is_err());
p.quads_u = u32::MAX;
p.quads_v = u32::MAX;
assert!(tessellate(&p, Policy::default(), ComputeLimits::default()).is_err());
}
}