]> git.nega.tv - josh/narcissus/commitdiff
Add a basic obj file parser
authorJoshua Simmons <josh@nega.tv>
Sun, 25 Sep 2022 11:54:06 +0000 (13:54 +0200)
committerJoshua Simmons <josh@nega.tv>
Sun, 25 Sep 2022 11:54:06 +0000 (13:54 +0200)
Cargo.lock
narcissus-core/Cargo.toml
narcissus-core/src/lib.rs
narcissus-core/src/obj.rs [new file with mode: 0644]

index d3f164701905993226b2af00553ed8faaa957a75..a070e00517e3a5c9ad2259080ec733a5b3d0b88c 100644 (file)
@@ -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",
 ]
 
index bc3c075dc409cfe431977211d693291b287d8d60..3e7ca127a5d1c426afdfb81372e7893549015dd8 100644 (file)
@@ -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
index 5373b9edc79e6f0f5d8f634d1c284c441bd7849f..d7dbf7f8f1a9ff8bede42e5092919eedab675465 100644 (file)
@@ -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 (file)
index 0000000..7077f32
--- /dev/null
@@ -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<std::io::Error> 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<T>
+where
+    T: Read,
+{
+    reader: T,
+    pos: usize,
+    cap: usize,
+    buf: Box<[u8; MAX_LINE_SIZE]>,
+}
+
+impl<T> Parser<T>
+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<V: Visitor>(&mut self, visitor: &mut V) -> Result<(), Error> {
+        fn parse_line<V: Visitor>(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<f32, Error> {
+                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(())
+    }
+}