From: Joshua Simmons Date: Sun, 25 Sep 2022 11:54:06 +0000 (+0200) Subject: Add a basic obj file parser X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=4f9302ed93d8697934c4f88cb872143f97857d44;p=josh%2Fnarcissus Add a basic obj file parser --- diff --git a/Cargo.lock b/Cargo.lock index d3f1647..a070e00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + [[package]] name = "narcissus" version = "0.1.0" @@ -24,6 +30,7 @@ dependencies = [ name = "narcissus-core" version = "0.1.0" dependencies = [ + "fast-float", "stb_image-sys", ] diff --git a/narcissus-core/Cargo.toml b/narcissus-core/Cargo.toml index bc3c075..3e7ca12 100644 --- a/narcissus-core/Cargo.toml +++ b/narcissus-core/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +fast-float = "0.2" stb_image-sys = { path = "../ffi/stb_image-sys" } \ No newline at end of file diff --git a/narcissus-core/src/lib.rs b/narcissus-core/src/lib.rs index 5373b9e..d7dbf7f 100644 --- a/narcissus-core/src/lib.rs +++ b/narcissus-core/src/lib.rs @@ -4,6 +4,7 @@ mod image; mod libc; pub mod manual_arc; mod mutex; +pub mod obj; mod pool; mod ref_count; pub mod slice; diff --git a/narcissus-core/src/obj.rs b/narcissus-core/src/obj.rs new file mode 100644 index 0000000..7077f32 --- /dev/null +++ b/narcissus-core/src/obj.rs @@ -0,0 +1,284 @@ +use std::io::Read; + +use fast_float::parse_partial; + +const MAX_LINE_SIZE: usize = 8 * 1024; + +#[derive(Debug)] +pub enum Error { + Io(std::io::Error), + Parse, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for Error {} + +impl From for Error { + fn from(inner: std::io::Error) -> Self { + Error::Io(inner) + } +} + +pub trait Visitor { + /// Parsed a vertex position, `w` is optional and defaults to `1.0`. + fn visit_position(&mut self, x: f32, y: f32, z: f32, w: f32); + + /// Parsed a vertex texcoord, `v`, and `w` are optional and default to `0.0`. + fn visit_texcoord(&mut self, u: f32, v: f32, w: f32); + + /// Parsed a vertex normal. All components are required, they are not necessarily normalized. + fn visit_normal(&mut self, x: f32, y: f32, z: f32); + + /// Parsed a face. Each element of `indices` is comprised of three components, the position index, texcoord index, + /// and normal index, respectively. + /// + /// Indices are 1-based. Negative indices are not supported. + /// The texcoord index is optional and will be zero if not present in the source file. + /// + /// `indices.len()` must be greater than or equal to three. + fn visit_face(&mut self, indices: &[(i32, i32, i32)]); + fn visit_object(&mut self, name: &str); + fn visit_group(&mut self, name: &str); + fn visit_smooth_group(&mut self, group: i32); +} + +/// Very basic obj parser. +pub struct Parser +where + T: Read, +{ + reader: T, + pos: usize, + cap: usize, + buf: Box<[u8; MAX_LINE_SIZE]>, +} + +impl Parser +where + T: Read, +{ + pub fn new(reader: T) -> Self { + Self { + reader, + pos: 0, + cap: 0, + buf: Box::new([0; MAX_LINE_SIZE]), + } + } + + pub fn visit(&mut self, visitor: &mut V) -> Result<(), Error> { + fn parse_line(line: &[u8], visitor: &mut V) -> Result<(), Error> { + debug_assert!(line.len() > 0); + + let mut i = 0; + + #[inline(always)] + fn consume(i: &mut usize, line: &[u8], c: u8) -> bool { + if line.get(*i) == Some(&c) { + *i += 1; + true + } else { + false + } + } + + #[inline(always)] + fn expect(i: &mut usize, line: &[u8], c: u8) -> Result<(), Error> { + if consume(i, line, c) { + Ok(()) + } else { + Err(Error::Parse) + } + } + + #[inline(always)] + fn parse_f32(i: &mut usize, line: &[u8]) -> Result { + if let Ok((x, digits)) = parse_partial(&line[*i..]) { + *i += digits; + return Ok(x); + } + Err(Error::Parse) + } + + #[inline(always)] + fn parse_f32_opt(i: &mut usize, line: &[u8], default: f32) -> f32 { + if let Ok((x, digits)) = parse_partial(&line[*i..]) { + *i += digits; + x + } else { + default + } + } + + #[inline(always)] + fn parse_i32(i: &mut usize, line: &[u8]) -> i32 { + let mut consumed = 0; + let mut acc = 0; + for &c in &line[*i..] { + let c = c.wrapping_sub(b'0'); + if c > 9 { + break; + } + acc = acc * 10 + c as i32; + consumed += 1; + } + *i += consumed; + acc + } + + #[inline(always)] + fn parse_vertex(i: &mut usize, line: &[u8]) -> Result<(i32, i32, i32), Error> { + let vertex_index = parse_i32(i, line); + if vertex_index == 0 { + return Err(Error::Parse); + } + match line.get(*i) { + Some(&b'/') => { + *i += 1; + let texcoord_index = parse_i32(i, line); + expect(i, line, b'/')?; + let normal_index = parse_i32(i, line); + Ok((vertex_index, texcoord_index, normal_index)) + } + Some(&b' ') => Ok((vertex_index, 0, 0)), + _ => Err(Error::Parse), + } + } + + let mut vertices = Vec::with_capacity(4); + + match line[i] { + b'v' => { + i += 1; + match line.get(i) { + Some(b' ') => { + i += 1; + let x = parse_f32(&mut i, line)?; + expect(&mut i, line, b' ')?; + let y = parse_f32(&mut i, line)?; + expect(&mut i, line, b' ')?; + let z = parse_f32(&mut i, line)?; + consume(&mut i, line, b' '); + let w = parse_f32_opt(&mut i, line, 1.0); + visitor.visit_position(x, y, z, w) + } + Some(b't') => { + i += 1; + expect(&mut i, line, b' ')?; + let u = parse_f32(&mut i, line)?; + consume(&mut i, line, b' '); + let v = parse_f32_opt(&mut i, line, 0.0); + consume(&mut i, line, b' '); + let w = parse_f32_opt(&mut i, line, 0.0); + visitor.visit_texcoord(u, v, w) + } + Some(b'n') => { + i += 1; + expect(&mut i, line, b' ')?; + let x = parse_f32(&mut i, line)?; + expect(&mut i, line, b' ')?; + let y = parse_f32(&mut i, line)?; + expect(&mut i, line, b' ')?; + let z = parse_f32(&mut i, line)?; + visitor.visit_normal(x, y, z) + } + _ => return Err(Error::Parse), + } + } + b'f' => { + i += 1; + expect(&mut i, line, b' ')?; + vertices.clear(); + + let vertex0 = parse_vertex(&mut i, line)?; + expect(&mut i, line, b' ')?; + let vertex1 = parse_vertex(&mut i, line)?; + expect(&mut i, line, b' ')?; + let vertex2 = parse_vertex(&mut i, line)?; + + vertices.reserve(3); + vertices.push(vertex0); + vertices.push(vertex1); + vertices.push(vertex2); + + while consume(&mut i, line, b' ') { + let vertex_n = parse_vertex(&mut i, line)?; + vertices.push(vertex_n); + } + + visitor.visit_face(&vertices) + } + b's' => { + i += 1; + expect(&mut i, line, b' ')?; + let group = parse_i32(&mut i, line); + visitor.visit_smooth_group(group) + } + b'o' => { + i += 1; + expect(&mut i, line, b' ')?; + if let Ok(name) = std::str::from_utf8(&line[i..]) { + visitor.visit_object(name) + } + } + b'g' => { + i += 1; + expect(&mut i, line, b' ')?; + if let Ok(name) = std::str::from_utf8(&line[i..]) { + visitor.visit_group(name) + } + } + b'#' => {} + _ => {} + } + + Ok(()) + } + + loop { + // refill + let remainder = self.cap - self.pos; + + if remainder == MAX_LINE_SIZE { + return Err(Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + "line too long", + ))); + } + + if remainder != 0 { + self.buf.copy_within(self.pos.., 0); + } + + self.pos = 0; + self.cap = remainder; + + let read = self.reader.read(&mut self.buf[self.cap..])?; + self.cap += read; + + for (i, &c) in self.buf[..self.cap].iter().enumerate() { + let is_newline = (c == b'\n') | (c == b'\r'); + // skip empty lines + if (i - self.pos) > 1 && is_newline { + parse_line(&self.buf[self.pos..i], visitor)?; + self.pos = i + 1; + } + } + + // eof + if read == 0 { + if self.pos != self.cap { + parse_line(&self.buf[self.pos..self.cap], visitor)?; + } + break; + } + } + + Ok(()) + } +}