Parcourir la source

Restructure project into different files

Christoph Stelz il y a 1 an
Parent
commit
84bf69807a
7 fichiers modifiés avec 726 ajouts et 479 suppressions
  1. 11 0
      src/bsdf.rs
  2. 67 0
      src/bvh.rs
  3. 136 0
      src/geometry.rs
  4. 177 0
      src/image.rs
  5. 15 479
      src/main.rs
  6. 288 0
      src/mesh.rs
  7. 32 0
      src/random.rs

+ 11 - 0
src/bsdf.rs

@@ -0,0 +1,11 @@
+// ┏┓ ┏━┓╺┳┓┏━╸
+// ┣┻┓┗━┓ ┃┃┣╸ 
+// ┗━┛┗━┛╺┻┛╹  
+
+use crate::geometry::Vec3;
+use crate::random::RNG;
+
+pub trait BSDF {
+    fn eval(i: Vec3, o: Vec3) -> f32;
+    fn sample(r: &mut RNG) -> Vec3;
+}

+ 67 - 0
src/bvh.rs

@@ -0,0 +1,67 @@
+// ┏┓ ╻ ╻╻ ╻
+// ┣┻┓┃┏┛┣━┫
+// ┗━┛┗┛ ╹ ╹
+
+
+use crate::geometry::Vec3;
+use crate::mesh::Face;
+use crate::mesh::Mesh;
+
+
+/**
+ * Axis-Aligned Bounding Box.
+ */
+pub struct AABB {
+    pub min: Vec3,
+    pub max: Vec3
+}
+
+/**
+ * Bounding Volume Hierarchy
+ */
+pub struct FaceReference<'a> {
+    m: &'a Mesh,
+    f: &'a Face
+}
+
+pub enum BVH<'a> {
+    Node(&'a BVH<'a>, &'a BVH<'a>),
+    Leaf(&'a BVH<'a>, Vec<FaceReference<'a>>)
+
+
+}
+
+fn f32_min(a: f32, b: f32) -> f32 {
+    return if a < b { a } else { b };
+}
+
+fn f32_max(a: f32, b: f32) -> f32 {
+    return if a >= b { a } else { b };
+}
+
+impl AABB {
+    pub fn extend(&mut self, m: &Mesh, f: &Face) {
+        let coords = m.face_coords(f);
+
+        self.min.x = f32::min(self.min.x, coords.iter().map(|c| c.x).reduce(f32_min).unwrap());
+        self.min.y = f32::min(self.min.y, coords.iter().map(|c| c.y).reduce(f32_min).unwrap());
+        self.min.z = f32::min(self.min.z, coords.iter().map(|c| c.z).reduce(f32_min).unwrap());
+
+        self.max.x = f32::max(self.max.x, coords.iter().map(|c| c.x).reduce(f32_max).unwrap());
+        self.max.y = f32::max(self.max.y, coords.iter().map(|c| c.y).reduce(f32_max).unwrap());
+        self.max.z = f32::max(self.max.z, coords.iter().map(|c| c.z).reduce(f32_max).unwrap());
+    }
+
+    pub fn from(m: &Mesh, f: &Face) -> AABB {
+        let mut aabb = AABB {
+            min: Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY),
+            max: Vec3::new(-f32::INFINITY, -f32::INFINITY, -f32::INFINITY),
+        };
+
+        aabb.extend(m, f);
+        
+        return aabb;
+    }
+}
+
+

+ 136 - 0
src/geometry.rs

@@ -0,0 +1,136 @@
+// ┏━╸┏━╸┏━┓┏┳┓┏━╸╺┳╸┏━┓╻ ╻
+// ┃╺┓┣╸ ┃ ┃┃┃┃┣╸  ┃ ┣┳┛┗┳┛
+// ┗━┛┗━╸┗━┛╹ ╹┗━╸ ╹ ╹┗╸ ╹
+
+#[derive(Copy, Clone, Debug)]
+pub struct Vec3 {
+    pub x: f32,
+    pub y: f32,
+    pub z: f32,
+}
+
+impl Vec3 {
+    pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
+        return Vec3 {
+            x: x,
+            y: y,
+            z: z
+        };
+    }
+
+    pub fn length(&self) -> f32 {
+        return (*self * *self).sqrt();
+    }
+    
+    pub fn distance_to(&self, other: Vec3) -> f32 {
+        return (*self - other).length();
+
+    }
+
+    pub fn normalize(&mut self) {
+        let l = self.length();
+        self.x /= l;
+        self.y /= l;
+        self.z /= l;
+    }
+
+    pub fn normalized(&self) -> Vec3 {
+        let mut other = self.clone();
+        other.normalize();
+        return other;
+    }
+
+    pub fn cross(&self, _rhs: Vec3) -> Vec3 {
+        return Vec3 {
+            x: self.y * _rhs.z - self.z * _rhs.y,
+            y: self.z * _rhs.x - self.x * _rhs.z,
+            z: self.x * _rhs.y - self.y * _rhs.x,
+        };
+    }
+}
+
+impl std::fmt::Display for Vec3 {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "({}, {}, {})", self.x, self.y, self.z);
+        Ok(())
+    }
+}
+
+impl std::ops::Add<Vec3> for Vec3 {
+    type Output = Vec3;
+
+    fn add(self, _rhs: Vec3) -> Vec3 {
+        return Vec3 {
+            x: self.x + _rhs.x,
+            y: self.y + _rhs.y,
+            z: self.z + _rhs.z,
+        };
+    }
+}
+
+impl std::ops::AddAssign for Vec3 {
+    fn add_assign(&mut self, other: Vec3) {
+        self.x += other.x;
+        self.y += other.y;
+        self.z += other.z;
+    }
+
+}
+
+impl std::ops::Neg for Vec3 {
+    type Output = Vec3;
+
+    fn neg(self) -> Vec3 {
+        return self * -1.0f32;
+
+    }
+}
+
+impl std::ops::Sub for Vec3 {
+    type Output = Vec3;
+
+    fn sub(self, _rhs: Vec3) -> Vec3 {
+        return self + (-_rhs);
+    }
+
+}
+
+// Dot product
+impl std::ops::Mul<Vec3> for Vec3 {
+    type Output = f32;
+
+    fn mul(self, _rhs: Vec3) -> f32 {
+        self.x * _rhs.x + self.y * _rhs.y + self.z * _rhs.z
+    }
+}
+
+// Scalar product
+impl std::ops::Mul<f32> for Vec3 {
+    type Output = Vec3;
+
+    fn mul(self, _rhs: f32) -> Vec3 {
+        return Vec3 {
+            x: _rhs * self.x,
+            y: _rhs * self.y,
+            z: _rhs * self.z
+        };
+    }
+}
+impl std::ops::Mul<Vec3> for f32 {
+    type Output = Vec3;
+
+    fn mul(self, _rhs: Vec3) -> Vec3 {
+        return _rhs * self;
+    }
+}
+
+pub struct Ray {
+    pub origin: Vec3,
+    pub direction: Vec3,
+}
+
+pub struct Intersection {
+    pub t: f32,
+    pub pos: Vec3,
+    pub normal: Vec3,
+}

+ 177 - 0
src/image.rs

@@ -0,0 +1,177 @@
+// ╻┏┳┓┏━┓┏━╸┏━╸   ╻┏━┓
+// ┃┃┃┃┣━┫┃╺┓┣╸    ┃┃ ┃
+// ╹╹ ╹╹ ╹┗━┛┗━╸   ╹┗━┛
+
+use std::fs::File;
+use std::io::BufReader;
+use std::io::Read;
+use std::convert::From;
+use std::io::BufWriter;
+use std::io::Write;
+use std::path::Path;
+use crate::geometry::Vec3;
+
+pub struct Image {
+    width: u16,
+    height: u16,
+    data: Vec<Vec3>,
+}
+
+impl Image {
+    pub fn get_pixel(&self, x: usize, y: usize) -> &Vec3 {
+        &self.data[y * self.width as usize + x]
+    }
+
+    pub fn mod_pixel(&mut self, x: usize, y: usize) -> &mut Vec3 {
+        &mut self.data[y * self.width as usize + x]
+    }
+
+    pub fn new(width: u16, height: u16) -> Image {
+        let mut data: Vec<Vec3> = Vec::new();
+        data.resize(
+            usize::from(width) * usize::from(height),
+            Vec3 {
+                x: 0.0,
+                y: 0.0,
+                z: 0.0,
+            },
+        );
+
+        Image {
+            width: width,
+            height: height,
+            data: data,
+        }
+    }
+
+    pub fn save_bmp(self: &Image, p: &Path) -> std::io::Result<()> {
+        let f: File = File::create(p)?;
+        let mut w = BufWriter::new(f);
+
+        // File header
+        w.write_all(&['B' as u8, 'M' as u8])?;
+        write_int(&mut w, 0)?; // size, left blank for now
+        write_int(&mut w, 0)?; // reserved bytes
+        write_int(&mut w, 54)?; // offset
+
+        // Bitmap info header
+        write_int(&mut w, 40)?; // header size in bytes
+        write_int(&mut w, self.width as u32)?;
+        write_int(&mut w, self.height as u32)?;
+        write_int16(&mut w, 1)?; // number of color planes
+        write_int16(&mut w, 24)?; // bits per pixel
+        write_int(&mut w, 0)?; // no compression
+        write_int(&mut w, 0)?; // image size
+        write_int(&mut w, 1000)?; // horizontal resolution in pixels per metre
+        write_int(&mut w, 1000)?; // vertical resolution
+        write_int(&mut w, 0)?; // number of colors
+        write_int(&mut w, 0)?; // number of important colors
+
+        // Pixel data
+        for y in (0..self.height).rev() {
+            for x in 0..self.width {
+                write_vec3(&mut w, self.get_pixel(x.into(), y.into()))?;
+            }
+        }
+
+        Ok(())
+    }
+}
+
+fn read_int(v: &[u8]) -> u32 {
+    let mut result = 0;
+
+    for i in 0..3 {
+        result |= (v[i] as u32) << (i * 8);
+    }
+
+    result
+}
+
+fn read_int16(v: &[u8]) -> u16 {
+    v[1] as u16 >> 8 | v[0] as u16
+}
+
+fn read_vec3(v: &[u8]) -> Vec3 {
+    Vec3 {
+        x: v[2] as f32,
+        y: v[1] as f32,
+        z: v[0] as f32,
+    }
+}
+
+fn round_row_size(width: u32) -> u32 {
+    ((24 * width + 31) / 32) * 4
+}
+
+pub fn load_bmp(path: &Path) -> Image {
+    // See https://en.wikipedia.org/wiki/BMP_file_format for more information.
+    // BMP is little-endian (least-significant bytes first)
+    // Some limitations here:
+    //   - we only support bottom-up bitmaps
+    //   - without compression
+    //   - and a depth of 24 bits per pixel
+
+    let mut buf = Vec::new();
+    let mut f = BufReader::new(File::open(path).expect("Could not find image file"));
+    f.read_to_end(&mut buf).expect("Could not read image file");
+
+    assert!(
+        buf[0] == 'B' as u8 && buf[1] == 'M' as u8,
+        "File is not a bitmap file"
+    );
+
+    let width = read_int(&buf[18..22]);
+    let height = read_int(&buf[22..26]);
+
+    let compression = read_int(&buf[30..34]);
+    assert!(compression == 0, "Only uncompressed BMPs are supported");
+
+    let depth = read_int16(&buf[28..30]);
+    assert!(depth == 24, "Only 24 bits per pixel are supported");
+
+    let offset = read_int(&buf[10..14]);
+
+    let row_size = round_row_size(width);
+
+    let mut image_data = Vec::new();
+
+    for row in 0..height {
+        for col in 0..width {
+            let y = height - row - 1;
+            let x = 3 * col;
+            let idx = (offset + y * row_size + x) as usize;
+            let pixel = read_vec3(&buf[idx..(idx + 3)]);
+            image_data.push(pixel);
+        }
+    }
+
+    assert!(image_data.len() == (width * height) as usize);
+
+    Image {
+        width: width as u16,
+        height: height as u16,
+        data: image_data,
+    }
+}
+
+fn write_int(f: &mut dyn Write, v: u32) -> std::io::Result<()> {
+    let bytes = [v as u8, (v >> 8) as u8, (v >> 16) as u8, (v >> 24) as u8];
+    f.write_all(&bytes)?;
+
+    Ok(())
+}
+
+fn write_int16(f: &mut dyn Write, v: u16) -> std::io::Result<()> {
+    let bytes = [v as u8, (v >> 8) as u8];
+    f.write_all(&bytes)?;
+
+    Ok(())
+}
+
+fn write_vec3(f: &mut dyn Write, v: &Vec3) -> std::io::Result<()> {
+    let bytes = [v.z as u8, v.y as u8, v.x as u8];
+    f.write_all(&bytes)?;
+
+    Ok(())
+}

+ 15 - 479
src/main.rs

@@ -1,465 +1,18 @@
-use std::fs::File;
-use std::io::BufReader;
-use std::io::Read;
-use std::io::BufRead;
-
-// ┏━┓┏━┓┏┓╻╺┳┓┏━┓┏┳┓   ┏┓╻╻ ╻┏┳┓┏┓ ┏━╸┏━┓   ┏━╸┏━╸┏┓╻┏━╸┏━┓┏━┓╺┳╸╻┏━┓┏┓╻
-// ┣┳┛┣━┫┃┗┫ ┃┃┃ ┃┃┃┃   ┃┗┫┃ ┃┃┃┃┣┻┓┣╸ ┣┳┛   ┃╺┓┣╸ ┃┗┫┣╸ ┣┳┛┣━┫ ┃ ┃┃ ┃┃┗┫
-// ╹┗╸╹ ╹╹ ╹╺┻┛┗━┛╹ ╹   ╹ ╹┗━┛╹ ╹┗━┛┗━╸╹┗╸   ┗━┛┗━╸╹ ╹┗━╸╹┗╸╹ ╹ ╹ ╹┗━┛╹ ╹
-
-pub struct RNG {
-    random_device: BufReader<File>,
-}
-
-impl RNG {
-    pub fn next_float(&mut self) -> f32 {
-        let mut buf: [u8; 4] = [0, 0, 0, 0];
-
-        self.random_device
-            .read(&mut buf)
-            .expect("Read from /dev/random failed");
-        let val: f32 = u32::from_be_bytes(buf) as f32;
-        f32::from(val) / (u32::max_value() as f32 + 1.0)
-    }
-
-    pub fn create() -> RNG {
-        let f = File::open("/dev/urandom").expect("Did not find source of randomness");
-        let reader: BufReader<File> = BufReader::new(f);
-
-        return RNG {
-            random_device: reader,
-        };
-    }
-}
-
-// ┏━╸┏━╸┏━┓┏┳┓┏━╸╺┳╸┏━┓╻ ╻
-// ┃╺┓┣╸ ┃ ┃┃┃┃┣╸  ┃ ┣┳┛┗┳┛
-// ┗━┛┗━╸┗━┛╹ ╹┗━╸ ╹ ╹┗╸ ╹
-
-#[derive(Copy, Clone)]
-pub struct Vec3 {
-    x: f32,
-    y: f32,
-    z: f32,
-}
-
-impl Vec3 {
-    pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
-        return Vec3 {
-            x: x,
-            y: y,
-            z: z
-        };
-    }
-
-    pub fn length(&self) -> f32 {
-        return (*self * *self).sqrt();
-    }
-    
-    pub fn distance_to(&self, other: Vec3) -> f32 {
-        return (*self - other).length();
-
-    }
-
-    pub fn normalize(&mut self) {
-        let l = self.length();
-        self.x /= l;
-        self.y /= l;
-        self.z /= l;
-    }
-
-    pub fn normalized(&self) -> Vec3 {
-        let mut other = self.clone();
-        other.normalize();
-        return other;
-    }
-
-    pub fn cross(&self, _rhs: Vec3) -> Vec3 {
-        return Vec3 {
-            x: self.y * _rhs.z - self.z * _rhs.y,
-            y: self.z * _rhs.x - self.x * _rhs.z,
-            z: self.x * _rhs.y - self.y * _rhs.x,
-        };
-    }
-}
-
-impl std::fmt::Display for Vec3 {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "({}, {}, {})", self.x, self.y, self.z);
-        Ok(())
-    }
-}
-
-impl std::ops::Add<Vec3> for Vec3 {
-    type Output = Vec3;
-
-    fn add(self, _rhs: Vec3) -> Vec3 {
-        return Vec3 {
-            x: self.x + _rhs.x,
-            y: self.y + _rhs.y,
-            z: self.z + _rhs.z,
-        };
-    }
-}
-
-impl std::ops::AddAssign for Vec3 {
-    fn add_assign(&mut self, other: Vec3) {
-        self.x += other.x;
-        self.y += other.y;
-        self.z += other.z;
-    }
-
-}
-
-impl std::ops::Neg for Vec3 {
-    type Output = Vec3;
-
-    fn neg(self) -> Vec3 {
-        return self * -1.0f32;
-
-    }
-}
-
-impl std::ops::Sub for Vec3 {
-    type Output = Vec3;
-
-    fn sub(self, _rhs: Vec3) -> Vec3 {
-        return self + (-_rhs);
-    }
-
-}
-
-// Dot product
-impl std::ops::Mul<Vec3> for Vec3 {
-    type Output = f32;
-
-    fn mul(self, _rhs: Vec3) -> f32 {
-        self.x * _rhs.x + self.y * _rhs.y + self.z * _rhs.z
-    }
-}
-
-// Scalar product
-impl std::ops::Mul<f32> for Vec3 {
-    type Output = Vec3;
-
-    fn mul(self, _rhs: f32) -> Vec3 {
-        return Vec3 {
-            x: _rhs * self.x,
-            y: _rhs * self.y,
-            z: _rhs * self.z
-        };
-    }
-}
-impl std::ops::Mul<Vec3> for f32 {
-    type Output = Vec3;
-
-    fn mul(self, _rhs: Vec3) -> Vec3 {
-        return _rhs * self;
-    }
-}
-
-pub struct Ray {
-    origin: Vec3,
-    direction: Vec3,
-}
-
-pub struct Intersection {
-    t: f32,
-    pos: Vec3,
-    normal: Vec3,
-}
-
-type IndexedVertex = [usize; 3];
-
-struct Face {
-   a: IndexedVertex,
-   b: IndexedVertex,
-   c: IndexedVertex
-}
-
-struct Material {
-
-}
-struct Mesh {
-    vertices: Vec<Vec3>,
-    color: Vec<Vec3>,
-    texture_coordinates: Vec<Vec3>,
-    faces: Vec<[usize; 3]>,
-    material: Material
-}
-
-
-// ┏┳┓┏━╸┏━┓╻ ╻   ╻┏━┓
-// ┃┃┃┣╸ ┗━┓┣━┫   ┃┃ ┃
-// ╹ ╹┗━╸┗━┛╹ ╹   ╹┗━┛
-impl Mesh {
-    fn load(obj_path: &str) -> Mesh {
-        let file = File::open(obj_path).expect("Could not open OBJ file");
-        let reader = BufReader::new(file);
-
-        let mut line_counter = 0;
-
-        let mut vertices : Vec<Vec3> = Vec::new();
-        let mut normals : Vec<Vec3>= Vec::new();
-        let mut texture_coords : Vec<Vec3> = Vec::new();
-        let mut faces : Vec<Face>= Vec::new();
-
-        for l in reader.lines() {
-            line_counter += 1;
-            let line = l.expect("Could not read line from file");
-            let comment_offset = line.find('#').unwrap_or(line.len());
-
-            // Remove comments
-            let mut stripped_line = line.clone();
-            let instruction = stripped_line.drain(0..comment_offset);
-
-            // Skip if line is empty
-            if instruction.as_str().is_empty() {
-                continue;
-            }
-
-            let arguments : Vec<&str>= instruction.as_str().split(' ').collect();
-            let instruction_code = arguments.get(0).expect(
-                format!("Did not find instruction code in line {}: '{}'", line_counter, line).as_str())
-                .clone();
-
-            fn parse_vec3(a: &[&str]) -> Option<Vec3> {
-                let x = f32::from_str(a[0]?)?;
-                let y = f32::from_str(a[1]?)?;
-                let z = f32::from_str(a[2]?)?;
-
-                return Some(Vec3::new(x,y,z));
-            }
-
-            fn parse_uv(a: &[&str]) -> Option<Vec3> {
-                let x = f32::from_str(a[0])?;
-                let y = f32::from_str(a[1])?;
-
-                return Some(Vec3::new(x,y, 0.0));
-            }
-
-            fn parse_triple(a : &str) -> Option<IndexedVertex> {
-
-            }
-
-            match instruction_code {
-                "v" | "vn" => {
-                    let coords = parse_vec3(&arguments[1..4]).expect(
-                        format!("Could not parse coords in line {}", line_counter).as_str()
-                    );
-
-                    match instruction_code {
-                        "v" => &vertices,
-                        "vt" => texture,
-                        _ => {}
-                    }.push(coords)
-                }
-                "vt" => {
-                    let coords = parse_uv(&arguments[1..3]).expect(
-                        format!("Could not parse uv coords in line {}", line_counter).as_str()
-                    );
-
-                    texture_coords.push(coords);
-                }
-                "f" => {
-                    faces.push()
-
-                }
-
-                _ => {}
-            }
-
-            //let args = line.split(' ').collect();
-            println!("Line {}", arguments.get(0).unwrap_or(&"<EMPTY>"));
-        }
-
-        return Mesh{
-           vertices: vec!() ,
-            color: vec!(),
-            texture_coordinates: vec!(),
-            faces: vec!(),
-            material: Material {}
-        };
-    }
-
-}
-
-// ╻┏┳┓┏━┓┏━╸┏━╸   ╻┏━┓
-// ┃┃┃┃┣━┫┃╺┓┣╸    ┃┃ ┃
-// ╹╹ ╹╹ ╹┗━┛┗━╸   ╹┗━┛
-
-use std::convert::From;
-use std::io::BufWriter;
-use std::io::Write;
-use std::ops::Index;
 use std::path::Path;
-use std::str::FromStr;
 
-pub struct Image {
-    width: u16,
-    height: u16,
-    data: Vec<Vec3>,
-}
-
-impl Image {
-    fn get_pixel(&self, x: usize, y: usize) -> &Vec3 {
-        &self.data[y * self.width as usize + x]
-    }
-
-    fn mod_pixel(&mut self, x: usize, y: usize) -> &mut Vec3 {
-        &mut self.data[y * self.width as usize + x]
-    }
-
-    fn new(width: u16, height: u16) -> Image {
-        let mut data: Vec<Vec3> = Vec::new();
-        data.resize(
-            usize::from(width) * usize::from(height),
-            Vec3 {
-                x: 0.0,
-                y: 0.0,
-                z: 0.0,
-            },
-        );
-
-        Image {
-            width: width,
-            height: height,
-            data: data,
-        }
-    }
-
-    fn save_bmp(self: &Image, p: &Path) -> std::io::Result<()> {
-        let f: File = File::create(p)?;
-        let mut w = BufWriter::new(f);
-
-        // File header
-        w.write_all(&['B' as u8, 'M' as u8])?;
-        write_int(&mut w, 0)?; // size, left blank for now
-        write_int(&mut w, 0)?; // reserved bytes
-        write_int(&mut w, 54)?; // offset
-
-        // Bitmap info header
-        write_int(&mut w, 40)?; // header size in bytes
-        write_int(&mut w, self.width as u32)?;
-        write_int(&mut w, self.height as u32)?;
-        write_int16(&mut w, 1)?; // number of color planes
-        write_int16(&mut w, 24)?; // bits per pixel
-        write_int(&mut w, 0)?; // no compression
-        write_int(&mut w, 0)?; // image size
-        write_int(&mut w, 1000)?; // horizontal resolution in pixels per metre
-        write_int(&mut w, 1000)?; // vertical resolution
-        write_int(&mut w, 0)?; // number of colors
-        write_int(&mut w, 0)?; // number of important colors
-
-        // Pixel data
-        for y in (0..self.height).rev() {
-            for x in 0..self.width {
-                write_vec3(&mut w, self.get_pixel(x.into(), y.into()))?;
-            }
-        }
-
-        Ok(())
-    }
-}
-
-fn read_int(v: &[u8]) -> u32 {
-    let mut result = 0;
-
-    for i in 0..3 {
-        result |= (v[i] as u32) << (i * 8);
-    }
-
-    result
-}
-
-fn read_int16(v: &[u8]) -> u16 {
-    v[1] as u16 >> 8 | v[0] as u16
-}
-
-fn read_vec3(v: &[u8]) -> Vec3 {
-    Vec3 {
-        x: v[2] as f32,
-        y: v[1] as f32,
-        z: v[0] as f32,
-    }
-}
-
-fn round_row_size(width: u32) -> u32 {
-    ((24 * width + 31) / 32) * 4
-}
-
-pub fn load_bmp(path: &Path) -> Image {
-    // See https://en.wikipedia.org/wiki/BMP_file_format for more information.
-    // BMP is little-endian (least-significant bytes first)
-    // Some limitations here:
-    //   - we only support bottom-up bitmaps
-    //   - without compression
-    //   - and a depth of 24 bits per pixel
-
-    let mut buf = Vec::new();
-    let mut f = BufReader::new(File::open(path).expect("Could not find image file"));
-    f.read_to_end(&mut buf).expect("Could not read image file");
-
-    assert!(
-        buf[0] == 'B' as u8 && buf[1] == 'M' as u8,
-        "File is not a bitmap file"
-    );
-
-    let width = read_int(&buf[18..22]);
-    let height = read_int(&buf[22..26]);
-
-    let compression = read_int(&buf[30..34]);
-    assert!(compression == 0, "Only uncompressed BMPs are supported");
-
-    let depth = read_int16(&buf[28..30]);
-    assert!(depth == 24, "Only 24 bits per pixel are supported");
-
-    let offset = read_int(&buf[10..14]);
-
-    let row_size = round_row_size(width);
-
-    let mut image_data = Vec::new();
-
-    for row in 0..height {
-        for col in 0..width {
-            let y = height - row - 1;
-            let x = 3 * col;
-            let idx = (offset + y * row_size + x) as usize;
-            let pixel = read_vec3(&buf[idx..(idx + 3)]);
-            image_data.push(pixel);
-        }
-    }
-
-    assert!(image_data.len() == (width * height) as usize);
-
-    Image {
-        width: width as u16,
-        height: height as u16,
-        data: image_data,
-    }
-}
-
-fn write_int(f: &mut dyn Write, v: u32) -> std::io::Result<()> {
-    let bytes = [v as u8, (v >> 8) as u8, (v >> 16) as u8, (v >> 24) as u8];
-    f.write_all(&bytes)?;
-
-    Ok(())
-}
-
-fn write_int16(f: &mut dyn Write, v: u16) -> std::io::Result<()> {
-    let bytes = [v as u8, (v >> 8) as u8];
-    f.write_all(&bytes)?;
-
-    Ok(())
-}
-
-fn write_vec3(f: &mut dyn Write, v: &Vec3) -> std::io::Result<()> {
-    let bytes = [v.z as u8, v.y as u8, v.x as u8];
-    f.write_all(&bytes)?;
-
-    Ok(())
-}
+mod random;
+mod geometry;
+mod image;
+mod mesh;
+mod bsdf;
+mod bvh;
 
+use crate::geometry::Vec3;
+use crate::random::RNG;
+use crate::mesh::Mesh;
+use crate::geometry::Ray;
+use crate::image::Image;
+use crate::geometry::Intersection;
 
 // ╺┳╸┏━┓┏━┓┏━╸┏━╸   ┏━┓┏━┓╻ ╻┏━┓
 //  ┃ ┣┳┛┣━┫┃  ┣╸    ┣┳┛┣━┫┗┳┛┗━┓
@@ -504,26 +57,6 @@ fn raytrace(r: &Ray) -> Vec3 {
     }
 }
 
-// ┏┓ ┏━┓╺┳┓┏━╸
-// ┣┻┓┗━┓ ┃┃┣╸ 
-// ┗━┛┗━┛╺┻┛╹  
-
-trait BSDF {
-    fn eval(i: Vec3, o: Vec3) -> f32;
-    fn sample(r: &mut RNG) -> Vec3;
-}
-
-
-struct Object {
-    mesh: Mesh,
-
-}
-
-
-struct Scene {
-    objects: Vec<Object>
-}
-
 // ┏┳┓┏━┓╻┏┓╻
 // ┃┃┃┣━┫┃┃┗┫
 // ╹ ╹╹ ╹╹╹ ╹
@@ -531,6 +64,9 @@ struct Scene {
 fn main() {
 
     let m = Mesh::load("benchmarks/suzanne.obj");
+    println!("First vertex: {}", m[0].vertices[m[0].vertices.len() - 1]);
+    let aabb = crate::bvh::AABB::from(&m[0], &m[0].faces[0]);
+    println!("AABB: {} -> {}", aabb.min, aabb.max);
     return;
     let mut rng = RNG::create();
 

+ 288 - 0
src/mesh.rs

@@ -0,0 +1,288 @@
+// ┏┳┓┏━╸┏━┓╻ ╻   ╻┏━┓
+// ┃┃┃┣╸ ┗━┓┣━┫   ┃┃ ┃
+// ╹ ╹┗━╸┗━┛╹ ╹   ╹┗━┛
+
+use std::fs::File;
+use std::io::BufReader;
+use std::io::BufRead;
+use std::collections::HashMap;
+use std::path::Path;
+
+use crate::geometry::Vec3;
+use crate::geometry::Ray;
+use crate::geometry::Intersection;
+
+
+/** Data structure for a single triangle face. */
+pub struct Face {
+    /** Triangle coordinates indexed inside mesh */
+    v: [usize; 3],
+
+    /** Triangle normals for each vertex */
+    n: [usize; 3],
+    
+    /** Texture coordinates */
+    tex: [usize; 3],
+}
+
+#[derive(Copy,Clone,Debug)]
+pub struct Material {
+    pub color: Vec3,
+    pub roughness: f32,
+    pub metallic: f32,
+}
+
+impl Material {
+    pub fn new() -> Material {
+        return Material {
+            color: Vec3::new(0.0, 0.0, 0.0),
+            roughness: 0.2,
+            metallic: 0.0,
+        }
+    }
+
+}
+
+pub struct Mesh {
+    pub name: String,
+    pub vertices: Vec<Vec3>,
+    pub normals: Vec<Vec3>,
+    pub texture_coordinates: Vec<Vec3>,
+    pub faces: Vec<Face>,
+    pub material: Material
+}
+
+fn parse_vec3<'a>(it: &mut impl Iterator<Item = &'a str>) -> Option<Vec3> {
+    let x = it.next()?.parse::<f32>().ok()?;
+    let y = it.next()?.parse::<f32>().ok()?;
+    let z = it.next()?.parse::<f32>().ok()?;
+
+    return Some(Vec3::new(x,y,z));
+}
+
+fn parse_uv<'a>(a: &mut impl Iterator<Item = &'a str>) -> Option<Vec3> {
+    let x = a.next()?.parse::<f32>().ok()?;
+    let y = a.next()?.parse::<f32>().ok()?;
+
+    return Some(Vec3::new(x,y, 0.0));
+}
+
+fn parse_triple(a: &str) -> Option<[usize; 3]> {
+    let v : Vec<usize> = a.split("/")
+        .map(|idx| idx.parse::<usize>().ok())
+        .collect::<Option<Vec<usize>>>()?;
+
+    if v.len() != 3 {
+        return None;
+    } else {
+        return Some([v[0], v[1], v[2]]);
+    }
+}
+
+/* Parses a line into individual arguments.
+ * Returns None if the line is empty.
+ */
+fn parse_line<'a>(line: &'a mut String) -> Option<impl Iterator<Item = &'a str>> {
+    let whitespace_offset = line.find(char::is_alphanumeric).unwrap_or(0);
+    let comment_offset = line.find('#').unwrap_or(line.len());
+
+    if whitespace_offset >= comment_offset {
+        // Line is factually empty except for whitespace and commentary.
+        return None;
+    }
+
+    // Remove comments
+    let instruction = &line[whitespace_offset..comment_offset];
+
+    // Skip if line is empty
+    if instruction.is_empty() {
+        return None;
+    }
+
+    return Some(instruction.split(' '));
+}
+
+pub fn read_mtllib(path: &str) -> HashMap<String, Material> {
+    let mut library = HashMap::new();
+
+    let file = File::open(path).expect("Could not open mtllib");
+    let reader = BufReader::new(file);
+
+
+    let mut name = String::default();
+
+    for (line_counter, l) in reader.lines().enumerate() {
+        let mut line = l.expect("Failed to read line from file");
+        let result = parse_line(&mut line);
+
+        if result.is_none() {
+            // Empty line
+            continue;
+        }
+
+        let mut arguments = result.unwrap();
+
+
+        let command = arguments.next()
+            .expect(format!("Line {} does not contain a command", line_counter).as_str());
+
+
+
+        let error_msg = format!("Invalid instruction in line {} (missing newmtl?)", line_counter);
+        match command {
+            "newmtl" => {
+                name = String::from(arguments.next().expect("Expected name in newmtl instruction"));
+                println!("Creating material {}", name);
+                library.insert(name.clone(), Material::new());
+                println!("Have '{}'", name);
+            },
+            "Kd" => {
+                println!("Contained: {}", library.keys().next().unwrap_or(&String::from("NONE")));
+                println!("Have '{}'", name);
+                let mtl = library.get_mut(&name).expect(error_msg.as_str());
+                let color = parse_vec3(&mut arguments).expect("Unable to parse color vector");
+                mtl.color = color;
+            },
+            _ => {}
+        }
+    }
+
+    return library;
+}
+
+impl Mesh {
+    pub fn load(obj_path: &str) -> Vec<Mesh> {
+        let mut objects : Vec<Mesh> = Vec::new();
+
+        let file = File::open(obj_path).expect("Could not open OBJ file");
+        let reader = BufReader::new(file);
+
+        let mut mtllib : HashMap<String, Material> = HashMap::default();
+        let default_material = Material::new();
+
+
+        // Points to last created object
+        let mut object_count : usize = 0;
+
+
+        for (line_counter, l) in reader.lines().enumerate() {
+            let mut line = l.expect("Could not read line from file");
+
+            let result= parse_line(&mut line);
+
+            // Handle empty line
+            if result.is_none() {
+                continue;
+            }
+
+            let mut arguments = result.unwrap();
+
+            let instruction_code = arguments.next().expect(
+                format!("Did not find instruction code in line {}'", line_counter).as_str());
+
+
+            match instruction_code {
+                "o" => {
+                    let name = arguments.next().expect(format!("Missing name argument in line {}", line_counter).as_str());
+
+                    // Construct new mesh to work on.
+                    objects.push(Mesh {
+                        name: String::from(name),
+                        vertices: Vec::new(),
+                        normals: Vec::new(),
+                        texture_coordinates: Vec::new(),
+                        faces: Vec::new(),
+                        material: default_material
+                    });
+                },
+                "v" | "vn" => {
+                    let o : &mut Mesh = objects.last_mut().expect("Vertices added before object was constructed");
+
+                    let coords = parse_vec3(&mut arguments).expect(
+                        format!("Could not parse coords in line {}", line_counter).as_str()
+                    );
+
+                    if instruction_code == "v" {
+                        o.vertices.push(coords);
+                    } else {
+                        o.normals.push(coords);
+                    }
+                }
+                "vt" => {
+                    let o : &mut Mesh = objects.last_mut().expect("Texture coordinates added before object was constructed");
+                    let coords = parse_uv(&mut arguments).expect(
+                        format!("Could not parse uv coords in line {}", line_counter).as_str()
+                    );
+
+                    o.texture_coordinates.push(coords);
+                }
+                "f" => {
+                    let o : &mut Mesh = objects.last_mut().expect("Face added before object was constructed");
+                    let error_msg = format!("Could not parse face in line {}", line_counter);
+
+                    let t : Vec<[usize; 3]> = arguments
+                        .map(|x| parse_triple(x))
+                        .collect::<Option<Vec<[usize; 3]>>>()
+                        .expect(&error_msg);
+
+                    if t.len() != 3 {
+                        panic!("{}", error_msg);
+                    }
+
+                    // We want the vertices ordered by type, so we have to transpose the triple
+                    // vector. Furthermore, Wavefront OBJ files are 1-based indices, so we subtract
+                    // one to convert them to 0-based indices.
+                    o.faces.push(Face {
+                        v:   [t[0][0] - 1, t[1][0] - 1, t[2][0] - 1],
+                        n:   [t[0][1] - 1, t[1][1] - 1, t[2][1] - 1],
+                        tex: [t[0][2] - 1, t[1][2] - 1, t[2][2] - 1],
+
+                    });
+                }
+                "mtllib" => {
+                    let filename = arguments.next().expect(format!("Missing filename in line {}", line_counter).as_str());
+
+                    let basedir = Path::new(obj_path).parent().expect("Could not find parent directory of obj file");
+                    mtllib = read_mtllib(basedir.join(filename).to_str().expect("Could not construct path to mtl lib"));
+                }
+
+                "usemtl" => {
+                    let o : &mut Mesh = objects.last_mut().expect("Face added before object was constructed");
+                    let material_name = String::from(arguments.next().expect("No material name specified"));
+                    o.material = *mtllib.get(&material_name).expect("Material not found in library");
+                }
+                _ => {}
+            }
+
+        }
+
+        // Print message with number of objects and total number of faces
+        let total_faces : usize = objects.iter()
+            .map(|o| o.faces.len())
+            .sum();
+
+        let object_names = objects.iter()
+            .map(|o| o.name.clone())
+            .collect::<Vec<String>>()
+            .join(",");
+
+        println!("Read {} objects ({}) with a total of {} faces.", objects.len(), object_names, total_faces);
+
+
+        return objects;
+    }
+
+    pub fn intersect(&self, ray: &Ray) -> Option<Intersection> {
+
+
+        None
+    }
+
+    pub fn face_coords(&self, f: &Face) -> [&Vec3; 3] {
+        return [
+            &self.vertices[f.v[0]],
+            &self.vertices[f.v[1]],
+            &self.vertices[f.v[2]]
+        ];
+    }
+}

+ 32 - 0
src/random.rs

@@ -0,0 +1,32 @@
+use std::fs::File;
+use std::io::BufReader;
+use std::io::Read;
+
+// ┏━┓┏━┓┏┓╻╺┳┓┏━┓┏┳┓   ┏┓╻╻ ╻┏┳┓┏┓ ┏━╸┏━┓   ┏━╸┏━╸┏┓╻┏━╸┏━┓┏━┓╺┳╸╻┏━┓┏┓╻
+// ┣┳┛┣━┫┃┗┫ ┃┃┃ ┃┃┃┃   ┃┗┫┃ ┃┃┃┃┣┻┓┣╸ ┣┳┛   ┃╺┓┣╸ ┃┗┫┣╸ ┣┳┛┣━┫ ┃ ┃┃ ┃┃┗┫
+// ╹┗╸╹ ╹╹ ╹╺┻┛┗━┛╹ ╹   ╹ ╹┗━┛╹ ╹┗━┛┗━╸╹┗╸   ┗━┛┗━╸╹ ╹┗━╸╹┗╸╹ ╹ ╹ ╹┗━┛╹ ╹
+
+pub struct RNG {
+    random_device: BufReader<File>,
+}
+
+impl RNG {
+    pub fn next_float(&mut self) -> f32 {
+        let mut buf: [u8; 4] = [0, 0, 0, 0];
+
+        self.random_device
+            .read(&mut buf)
+            .expect("Read from /dev/random failed");
+        let val: f32 = u32::from_be_bytes(buf) as f32;
+        f32::from(val) / (u32::max_value() as f32 + 1.0)
+    }
+
+    pub fn create() -> RNG {
+        let f = File::open("/dev/urandom").expect("Did not find source of randomness");
+        let reader: BufReader<File> = BufReader::new(f);
+
+        return RNG {
+            random_device: reader,
+        };
+    }
+}