Download crates/mb3-obj/src/lib.rs from nmarshall/nanobrush: direct link, hf CLI and curl.
- Browser
- Download file 7.41 kB
-
https://huggingface.co/nmarshall/nanobrush/resolve/main/crates/mb3-obj/src/lib.rs
- Command line
-
hf download hf://nmarshall/nanobrush/crates/mb3-obj/src/lib.rs
-
curl -L -o lib.rs https://huggingface.co/nmarshall/nanobrush/resolve/main/crates/mb3-obj/src/lib.rs
7.41 kB
| //! Bounded OBJ export of explicit computed geometry; editable data is not stored. | |
| use mb3_dat::{Limits, Vec3, World}; | |
| use std::{collections::HashMap, fmt}; | |
| pub enum Error { | |
| ComputedRequired, | |
| InvalidGeometry, | |
| NonFinite, | |
| Overflow, | |
| Limit, | |
| Allocation, | |
| } | |
| impl fmt::Display for Error { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "OBJ: {self:?}") | |
| } | |
| } | |
| impl std::error::Error for Error {} | |
| type Result<T> = std::result::Result<T, Error>; | |
| struct Output<'a> { | |
| bytes: Vec<u8>, | |
| limits: &'a Limits, | |
| items: usize, | |
| owned: usize, | |
| error: Option<Error>, | |
| } | |
| impl fmt::Write for Output<'_> { | |
| fn write_str(&mut self, s: &str) -> fmt::Result { | |
| let result = (|| { | |
| let n = self | |
| .bytes | |
| .len() | |
| .checked_add(s.len()) | |
| .ok_or(Error::Overflow)?; | |
| if n > self.limits.max_bytes { | |
| return Err(Error::Limit); | |
| } | |
| self.bytes | |
| .try_reserve(s.len()) | |
| .map_err(|_| Error::Allocation)?; | |
| self.bytes.extend_from_slice(s.as_bytes()); | |
| Ok(()) | |
| })(); | |
| if let Err(e) = result { | |
| self.error = Some(e); | |
| return Err(fmt::Error); | |
| } | |
| Ok(()) | |
| } | |
| } | |
| impl Output<'_> { | |
| fn put(&mut self, args: fmt::Arguments<'_>) -> Result<()> { | |
| fmt::write(self, args).map_err(|_| self.error.take().unwrap_or(Error::Overflow)) | |
| } | |
| fn charge(&mut self, n: usize) -> Result<()> { | |
| self.items = self.items.checked_add(n).ok_or(Error::Overflow)?; | |
| if self.items > self.limits.max_items { | |
| Err(Error::Limit) | |
| } else { | |
| Ok(()) | |
| } | |
| } | |
| fn vertex(&mut self, v: Vec3) -> Result<()> { | |
| key(v)?; | |
| self.put(format_args!("v {} {} {}\n", v[0], v[1], v[2])) | |
| } | |
| } | |
| fn key(v: Vec3) -> Result<[u32; 3]> { | |
| if !v.iter().all(|n| n.is_finite()) { | |
| return Err(Error::NonFinite); | |
| } | |
| Ok(v.map(|n| if n == 0. { 0 } else { n.to_bits() })) | |
| } | |
| fn mul(a: usize, b: usize) -> Result<usize> { | |
| a.checked_mul(b).ok_or(Error::Overflow) | |
| } | |
| fn add(a: usize, b: usize) -> Result<usize> { | |
| a.checked_add(b).ok_or(Error::Overflow) | |
| } | |
| pub fn encode(world: &World, limits: &Limits) -> Result<Vec<u8>> { | |
| if !world.computed { | |
| return Err(Error::ComputedRequired); | |
| } | |
| let mut o = Output { | |
| bytes: Vec::new(), | |
| limits, | |
| items: 0, | |
| owned: 0, | |
| error: None, | |
| }; | |
| let mut base = 1usize; | |
| o.charge(add(world.brushes.len(), world.patches.len())?)?; | |
| for brush in &world.brushes { | |
| o.charge(brush.planes.len())?; | |
| o.put(format_args!("#brush\n"))?; | |
| let mut map = HashMap::new(); | |
| for p in &brush.planes { | |
| let vertices = p.polygon.as_ref().ok_or(Error::InvalidGeometry)?; | |
| if vertices.len() < 3 { | |
| return Err(Error::InvalidGeometry); | |
| } | |
| o.charge(vertices.len())?; | |
| for v in vertices { | |
| let k = key(*v)?; | |
| if !map.contains_key(&k) { | |
| o.owned = add(o.owned, 2 * std::mem::size_of::<([u32; 3], usize)>())?; | |
| if o.owned > limits.max_owned_bytes { | |
| return Err(Error::Limit); | |
| } | |
| map.try_reserve(1).map_err(|_| Error::Allocation)?; | |
| let id = add(base, map.len())?; | |
| map.insert(k, id); | |
| o.vertex(*v)?; | |
| } | |
| } | |
| } | |
| for p in &brush.planes { | |
| o.put(format_args!("f"))?; | |
| for v in p.polygon.as_ref().ok_or(Error::InvalidGeometry)? { | |
| let id = map.get(&key(*v)?).ok_or(Error::InvalidGeometry)?; | |
| o.put(format_args!(" {id}"))?; | |
| } | |
| o.put(format_args!("\n"))?; | |
| } | |
| base = add(base, map.len())?; | |
| } | |
| for p in &world.patches { | |
| let data = p.computed.as_ref().ok_or(Error::InvalidGeometry)?; | |
| let u = add(mul(p.anchors_u as usize / 2, p.quads_u as usize)?, 1)?; | |
| let v = add(mul(p.anchors_v as usize / 2, p.quads_v as usize)?, 1)?; | |
| let n = mul(u, v)?; | |
| if data.points.len() != n | |
| || data.normals.len() != n | |
| || p.anchors.len() != mul(p.anchors_u as usize, p.anchors_v as usize)? | |
| { | |
| return Err(Error::InvalidGeometry); | |
| } | |
| o.charge(add( | |
| add(p.anchors.len(), data.normals.len())?, | |
| add(n, mul(u - 1, v - 1)?)?, | |
| )?)?; | |
| for v in p.anchors.iter().chain(&data.normals) { | |
| key(*v)?; | |
| } | |
| o.put(format_args!("#patch9\n"))?; | |
| for point in &data.points { | |
| o.vertex(*point)?; | |
| } | |
| for y in 0..v - 1 { | |
| for x in 0..u - 1 { | |
| let i = add(base, add(x, mul(y, u)?)?)?; | |
| o.put(format_args!( | |
| "f {} {} {} {}\n", | |
| i, | |
| add(i, u)?, | |
| add(add(i, u)?, 1)?, | |
| add(i, 1)? | |
| ))?; | |
| } | |
| } | |
| base = add(base, n)?; | |
| } | |
| Ok(o.bytes) | |
| } | |
| mod tests { | |
| use super::*; | |
| use mb3_dat::{Brush, Plane}; | |
| fn world() -> World { | |
| World { | |
| computed: true, | |
| brushes: vec![Brush { | |
| planes: vec![Plane { | |
| polygon: Some(vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.]]), | |
| ..Plane::default() | |
| }], | |
| }], | |
| ..World::default() | |
| } | |
| } | |
| fn exact_triangle_and_limits() { | |
| let w = world(); | |
| assert_eq!( | |
| encode(&w, &Limits::default()).unwrap(), | |
| b"#brush\nv 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n" | |
| ); | |
| for l in [ | |
| Limits { | |
| max_bytes: 2, | |
| ..Limits::default() | |
| }, | |
| Limits { | |
| max_items: 1, | |
| ..Limits::default() | |
| }, | |
| Limits { | |
| max_owned_bytes: 1, | |
| ..Limits::default() | |
| }, | |
| ] { | |
| assert!(encode(&w, &l).is_err()); | |
| } | |
| } | |
| fn no_global_dedup_and_signed_zero_equality() { | |
| let mut w = world(); | |
| w.brushes[0].planes.push(Plane { | |
| polygon: Some(vec![[-0., 0., 0.], [1., 0., 0.], [0., 1., 0.]]), | |
| ..Plane::default() | |
| }); | |
| w.brushes.push(w.brushes[0].clone()); | |
| let s = String::from_utf8(encode(&w, &Limits::default()).unwrap()).unwrap(); | |
| assert_eq!(s.lines().filter(|s| s.starts_with("v ")).count(), 6); | |
| assert!(s.contains("f 4 5 6")); | |
| } | |
| fn invalid_models_refused() { | |
| let mut w = world(); | |
| w.computed = false; | |
| assert_eq!( | |
| encode(&w, &Limits::default()).unwrap_err(), | |
| Error::ComputedRequired | |
| ); | |
| w = world(); | |
| w.brushes[0].planes[0].polygon.as_mut().unwrap()[0][0] = f32::NAN; | |
| assert_eq!( | |
| encode(&w, &Limits::default()).unwrap_err(), | |
| Error::NonFinite | |
| ); | |
| w = world(); | |
| w.brushes[0].planes[0].polygon = Some(vec![]); | |
| assert!(encode(&w, &Limits::default()).is_err()); | |
| } | |
| } | |