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, pub normals: Vec, 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 { 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 { 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 { 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()); } }