Download crates/nanobrush-geometry/src/patch.rs from nmarshall/nanobrush: direct link, hf CLI and curl.
- Browser
- Download file 6.26 kB
-
https://huggingface.co/nmarshall/nanobrush/resolve/main/crates/nanobrush-geometry/src/patch.rs
- Command line
-
hf download hf://nmarshall/nanobrush/crates/nanobrush-geometry/src/patch.rs
-
curl -L -o patch.rs https://huggingface.co/nmarshall/nanobrush/resolve/main/crates/nanobrush-geometry/src/patch.rs
6.26 kB
| use crate::{math::*, *}; | |
| use mb3_dat::Patch9; | |
| pub struct Sample { | |
| pub point: DVec3, | |
| pub normal: DVec3, | |
| } | |
| 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, | |
| }) | |
| } | |
| 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, | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| 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]); | |
| } | |
| 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)); | |
| } | |
| 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()); | |
| } | |
| } | |