diff --git a/scenes/withTriangle.json b/scenes/withTriangle.json new file mode 100644 index 0000000..07b7f1c --- /dev/null +++ b/scenes/withTriangle.json @@ -0,0 +1,38 @@ +{ + "camera": { + "image_width": 400, + "image_height": 300, + "anti_alias_rate": 23, + "max_depth": 10, + "fov": 20.0, + "look_from": { "x": 20, "y": 25, "z": 40 }, + "look_at": { "x": 0.0, "y": 0.0, "z": -1.0 }, + "vup": { "x": 0.0, "y": 1.0, "z": 0.0 }, + "defocus_blur": false, + "defocus_angle": 0, + "focus_dist": 15.68 + }, + "materials": [ + { "type": "metal", "albedo": { "x": 0.9, "y": 0.4, "z": 0.2 }, "prob": 1.0, "fuzz": 0.1 }, + { "type": "metal", "albedo": { "x": 0.7, "y": 0.4, "z": 0.2 }, "prob": 1.0, "fuzz": 0.1 }, + { "type": "lambertian", "albedo": { "x": 0.8, "y": 0.8, "z": 0.0 }, "prob": 1.0 }, + { "type": "lambertian", "albedo": { "x": 0.1, "y": 0.2, "z": 0.5 }, "prob": 1.0 }, + { "type": "dielectric", "refraction_index": 1.5}, + { "type": "dielectric", "refraction_index": 0.67}, + { "type": "metal", "albedo": { "x": 0.8, "y": 0.6, "z": 0.2 }, "prob": 1.0, "fuzz": 1.0 } + + ], + "objects": [ + { "type": "sphere", "center": { "x": 0, "y": 0.7, "z": -0.4 }, "radius": 0.2, "material": 0}, + { "type": "sphere", "center": { "x": 0.0, "y": 0.5, "z": -0.8 }, "radius": 0.1, "material": 1}, + { "type": "sphere", "center": { "x": 0.0, "y": -100.5, "z": -1.0 }, "radius": 100.0, "material": 2}, + { "type": "sphere", "center": { "x": 0.0, "y": 0.0, "z": -1.2 }, "radius": 0.5, "material": 3}, + { "type": "sphere", "center": { "x": -1, "y": 0, "z": -1 }, "radius": 0.4, "material": 5}, + { "type": "sphere", "center": { "x": 1, "y": 0, "z": -1 }, "radius": 0.5, "material": 6}, + { "type": "sphere", "center": { "x": 20, "y": 7, "z": -15 }, "radius": 10.5, "material": 0}, + { "type": "triangle", "p1": { "x": 0, "y": 0, "z": -4}, "p2": { "x": 0, "y": 0, "z": 4 }, "p3": { "x": 0, "y": 7, "z": 0 }, "material": 0} + ] +} + + + diff --git a/src/camera.rs b/src/camera.rs index e5089da..268d635 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -112,7 +112,7 @@ impl Camera { self.v = self.w.cross(&self.u); // viewport - let viewport_height = 2. * h * self.focus_dist; + let viewport_height = 2. * h * self.focus_dist; let viewport_width = viewport_height * (self.image_width as f32 / self.image_height as f32); let viewport_u = viewport_width * self.u; let viewport_v = viewport_height * -self.v; @@ -125,7 +125,7 @@ impl Camera { self.dirty = false; if self.defocus_angle != 0. { - let defocus_radius = self.focus_dist * deg_to_rad(self.defocus_angle/2.).tan(); + let defocus_radius = self.focus_dist * deg_to_rad(self.defocus_angle / 2.).tan(); self.defocus_disk_u = self.u * defocus_radius; self.defocus_disk_v = self.v * defocus_radius; } @@ -180,7 +180,7 @@ impl Camera { self.dirty = true; } } - + fn defocus_disk_sample(&self) -> Vec3 { let p = Vec3::random_in_unit_disk(); self.look_from + (p.x() * self.defocus_disk_u) + (p.y() * self.defocus_disk_v) @@ -206,7 +206,11 @@ impl Camera { let pixel_loc = pixel_tl + (x * self.pixel_delta_u / (self.anti_alias_rate + 1) as f32) + (y * self.pixel_delta_v / (self.anti_alias_rate + 1) as f32); - let ray_orig = if self.defocus_angle > 0. { self.defocus_disk_sample() } else {self.look_from}; + let ray_orig = if self.defocus_angle > 0. { + self.defocus_disk_sample() + } else { + self.look_from + }; let ray_dir = pixel_loc - ray_orig; let r = Ray::new(ray_orig, ray_dir); pixel_colour += self.ray_colour(hittables, &r, self.max_depth); @@ -220,7 +224,7 @@ impl Camera { } info!("Writing image file..."); - imgbuf.save("output2.png").unwrap(); + imgbuf.save("output.png").unwrap(); } fn ray_colour(&self, hittables: &Vec>, r: &Ray, depth: u32) -> Colour { diff --git a/src/main.rs b/src/main.rs index a0fec5d..09fc42f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ use crate::ray::Ray; use crate::scenes::scene::Scene; use crate::vec3::Vec3; use dotenv::dotenv; +use log::warn; use rand::RngExt; // TODO: implement scene serialization @@ -26,7 +27,8 @@ fn main() { pretty_env_logger::init(); // TODO: use cli arg for scenefile - let json_file = "./scenes/scene.json"; + let json_file = "./scenes/withTriangle.json"; + // let json_file = "./scenes/scene.json"; let json_str = fs::read_to_string(json_file).expect("Reading specified scene file failed!"); let mut scene: Scene = serde_json::from_str(&json_str).unwrap(); scene.render(); diff --git a/src/objects.rs b/src/objects.rs index be146ee..b7b495c 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -1,4 +1,5 @@ pub mod hit; pub mod materials; pub mod sphere; +pub mod triangle; pub mod traits; diff --git a/src/objects/triangle.rs b/src/objects/triangle.rs new file mode 100644 index 0000000..7d41ef8 --- /dev/null +++ b/src/objects/triangle.rs @@ -0,0 +1,79 @@ +use crate::objects::hit::Hit; +use crate::objects::traits::Hittable; +use crate::ray::Ray; +use crate::{objects::materials::traits::Material, vec3::Vec3}; +use is_close::default; +use std::fmt::Debug; +use std::sync::Arc; + +pub struct Triangle { + p1: Vec3, + p2: Vec3, + p3: Vec3, + material: Arc, + normal: Vec3, +} + +impl Triangle { + pub fn new(p1: Vec3, p2: Vec3, p3: Vec3, material: Arc) -> Self { + Self { + p1, + p2, + p3, + material, + normal: (p2 - p1).cross(&(p3 - p1)), + } + } +} + +impl Debug for Triangle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Triangle") + .field("p1", &self.p1) + .field("p2", &self.p2) + .field("p3", &self.p3) + .field("material", &self.material) + .finish() + } +} + +impl Hittable for Triangle { + fn hit(&self, r: &Ray) -> Option { + // check if ray parallel to plane + let dot = self.normal.dot(r.dir()); + if default().is_close(dot, 0.) { + return None; + } + + let d = (-self.normal).dot(&self.p1); + // hitpoint on plane + let t = -(self.normal.dot(&(r.origin() + d))) / dot; + // hits behind camera + if t < 0. { + return None; + }; + + let p = *r.origin() + t * r.dir(); + let a4 = (self.p3 - self.p1).cross(&(self.p2 - self.p1)).length(); + let a3 = (self.p3 - p).cross(&(self.p2 - p)).length(); + let a2 = (self.p3 - p).cross(&(self.p1 - p)).length(); + let a1 = (self.p2 - p).cross(&(self.p1 - p)).length(); + + let mut normal_copy = self.normal.clone(); + let diff = (a4 - a1 - a2 - a3).abs(); + if diff < 0.01 { + if self.normal.dot(&-r.dir()) < 0. { + normal_copy *= -1.; // TODO: vec3 * integer function + } + return Some(Hit::new(t, p, self.normal, self.material.clone(), true)); // TODO: + // front_face calculation; have to change with if check up there iggg + } else { + return None; + } + } + + fn normal_at(&self, _p: &Vec3) -> Vec3 { + // FIXME: might cause ownership issues + return self.normal; + } +} diff --git a/src/scenes/scene.rs b/src/scenes/scene.rs index 4a5739d..c98dce1 100644 --- a/src/scenes/scene.rs +++ b/src/scenes/scene.rs @@ -1,4 +1,5 @@ use std::sync::Arc; +use std::fmt::Debug; use log::warn; use serde::Deserialize; @@ -13,6 +14,7 @@ use crate::{ }, sphere::Sphere, traits::Hittable, + triangle::Triangle, }, scenes::raw_camera::RawCamera, vec3::Vec3, @@ -24,7 +26,29 @@ pub struct Scene { pub objects: Vec>, } +impl Debug for Scene { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Scene").field("camera", &self.camera).field("materials", &self.materials).field("objects", &self.objects).finish() + } +} + impl Scene { + pub fn new(image_width: u32, image_height: u32) -> Self { + Self { + camera: Camera::new(image_width, image_height), + materials: vec![], + objects: vec![], + } + } + + pub fn camera(&mut self) -> &mut Camera { + &mut self.camera + } + + pub fn add_hittable(&mut self, hittable: Arc) { + self.objects.push(hittable); + } + pub fn render(&mut self) { self.camera.render(&self.objects); } @@ -68,10 +92,19 @@ struct RawSphere { pub material: u32, } +#[derive(Deserialize)] +struct RawTriangle { + pub p1: Vec3, + pub p2: Vec3, + pub p3: Vec3, + pub material: u32, +} + #[derive(Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] enum HittableDef { Sphere(RawSphere), + Triangle(RawTriangle), } impl HittableDef { @@ -92,6 +125,21 @@ impl HittableDef { materials.get(s.material as usize).unwrap().clone(), ))) } + HittableDef::Triangle(t) => { + if t.material as usize >= materials.len() { + warn!( + "Triangle specified nonexistent material {}; skipping...", + t.material + ); + return None; + } + Some(Arc::new(Triangle::new( + t.p1, + t.p2, + t.p3, + materials.get(t.material as usize).unwrap().clone(), + ))) + } } } } diff --git a/src/vec3.rs b/src/vec3.rs index 84fe159..94f887b 100644 --- a/src/vec3.rs +++ b/src/vec3.rs @@ -59,11 +59,7 @@ impl Vec3 { pub fn random_unit_hemisphere(n: &Self) -> Self { let v = Self::random_unit(); - if n.dot(&v) > 0.0 { - v - } else { - -v - } + if n.dot(&v) > 0.0 { v } else { -v } } pub fn x(&self) -> &f32 { @@ -190,6 +186,18 @@ impl Neg for Vec3 { } } +impl Add<&Vec3> for &Vec3 { + type Output = Vec3; + + fn add(self, rhs: &Vec3) -> Vec3 { + Vec3 { + x: self.x + rhs.x, + y: self.y + rhs.y, + z: self.z + rhs.z, + } + } +} + impl Add for Vec3 { type Output = Self; @@ -202,6 +210,19 @@ impl Add for Vec3 { } } +impl Add for &Vec3 { + type Output = Vec3; + + fn add(self, f: f32) -> Vec3 { + Vec3 { + x: self.x + f, + y: self.y + f, + z: self.z + f, + } + } +} + + impl Add for Vec3 { type Output = Self; @@ -360,7 +381,6 @@ impl Mul for &f32 { z: rhs.z * self, } } - } impl MulAssign for Vec3 {