diff --git a/src/demos/cornell_box.rs b/src/demos/instances.rs similarity index 80% rename from src/demos/cornell_box.rs rename to src/demos/instances.rs index 506ff9c..03521f1 100644 --- a/src/demos/cornell_box.rs +++ b/src/demos/instances.rs @@ -4,20 +4,23 @@ use rand::{prelude::SmallRng, SeedableRng}; use crate::{ demos::{Demo, ParallelHit}, - hitable::shapes::{Cuboid, RectBuilder}, + hitable::{ + shapes::{Cuboid, RectBuilder}, + Hitable, + }, materials::{DiffuseLight, Lambertian, MaterialBuilder}, texture::Solid, types::Vec3, BvhNode, Camera, }; -pub struct CornellBox {} +pub struct Instances {} -impl Demo for CornellBox { +impl Demo for Instances { type DemoT = BvhNode>; fn name(&self) -> &'static str { - "cornell_box" + "instances" } fn world(&self) -> Self::DemoT { @@ -76,16 +79,20 @@ impl Demo for CornellBox { )); // Add the two boxes - world.push(Arc::new(Cuboid::new( - Vec3::new(136.0, 0.0, 65.0), - Vec3::new(295.0, 165.0, 230.0), - white, - ))); - world.push(Arc::new(Cuboid::new( - Vec3::new(265.0, 0.0, 295.0), - Vec3::new(430.0, 330.0, 460.0), - white, - ))); + world.push(Arc::new( + Cuboid::new( + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(165.0, 330.0, 165.0), + white, + ) + .rotate_y(15.0) + .translate(Vec3::new(265.0, 0.0, 295.0)), + )); + world.push(Arc::new( + Cuboid::new(Vec3::new(0.0, 0.0, 0.0), Vec3::splat(165.0), white) + .rotate_y(-18.0) + .translate(Vec3::new(130.0, 0.0, 65.0)), + )); BvhNode::new(&mut rng, &mut world, 0.0, 1.0) } diff --git a/src/demos/mod.rs b/src/demos/mod.rs index 6b0e0fa..78c55f4 100644 --- a/src/demos/mod.rs +++ b/src/demos/mod.rs @@ -13,15 +13,15 @@ use std::{ }; mod checkered_motion_blur; -mod cornell_box; mod image_texture; +mod instances; mod perlin_noise_ball; mod simple_light; mod two_spheres; pub use checkered_motion_blur::CheckeredMotionBlur; -pub use cornell_box::CornellBox; pub use image_texture::ImageTextureDemo; +pub use instances::Instances; pub use perlin_noise_ball::PerlinNoiseBall; pub use simple_light::SimpleLight; pub use two_spheres::TwoSpheres; diff --git a/src/hitable/hitable.rs b/src/hitable/hitable.rs deleted file mode 100644 index fc3a0ea..0000000 --- a/src/hitable/hitable.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::sync::Arc; - -use crate::{ - types::{Ray, Vec3}, - Aabb, Material, -}; - -pub struct HitRecord<'a> { - /// Rays are represented by A + t * B - /// where A is the source point and B destination point - /// by adjusting t we can move forward/back on the ray - /// - /// t is the point at which a ray intersected another object. - /// As in, If we put this value of t in A + t * B equation, We'll get the exact - /// point at which a ray intersects some other object - pub t: f64, - /// Ray object otherwise is represented by the Source/Destination points - /// p is what we get when we perform the operation, A + t * B - /// i.e. A vector from Ray source to the point t - pub p: Vec3, - - /// unit outward facing normal - pub normal: Vec3, - - /// material if any of the surface - pub material: &'a dyn Material, - - /// texture coordinates for an object - pub u: f64, - pub v: f64, - - pub front_face: bool, -} - -impl<'a> HitRecord<'a> { - pub fn new( - t: f64, - p: Vec3, - normal: Vec3, - material: &'a dyn Material, - (u, v): (f64, f64), - ) -> Self { - Self { - t, - p, - normal, - material, - u, - v, - front_face: false, - } - } - - pub fn set_face_normal(&mut self, ray: &Ray) { - self.front_face = ray.direction.dot(&self.normal) < 0.0; - - self.normal = if self.front_face { - self.normal - } else { - -self.normal - } - } -} - -pub trait Hitable { - fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option; - - fn bounding_box(&self, _t0: f64, _t1: f64) -> Option; -} - -impl Hitable for Arc { - fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { - self.as_ref().hit(ray, t_min, t_max) - } - fn bounding_box(&self, t0: f64, t1: f64) -> Option { - self.as_ref().bounding_box(t0, t1) - } -} - -pub struct Translate { - object: T, - offset: Vec3, -} - -impl Translate { - pub fn new(object: T, offset: Vec3) -> Self { - Self { object, offset } - } -} - -impl Hitable for Translate { - fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { - let moved_ray = Ray::new(ray.origin - self.offset, ray.direction, ray.time()); - - if let Some(mut hit) = self.object.hit(&moved_ray, t_min, t_max) { - hit.p += self.offset; - hit.set_face_normal(&moved_ray); - - Some(hit) - } else { - None - } - } - - fn bounding_box(&self, t0: f64, t1: f64) -> Option { - if let Some(bbox) = self.object.bounding_box(t0, t1) { - Some(Aabb::new(bbox.min + self.offset, bbox.max + self.offset)) - } else { - None - } - } -} diff --git a/src/hitable/mod.rs b/src/hitable/mod.rs index 39f6404..d1ab0e3 100644 --- a/src/hitable/mod.rs +++ b/src/hitable/mod.rs @@ -1,7 +1,116 @@ pub mod bvh; -pub mod hitable; pub mod hitable_list; +mod rotate; pub mod shapes; +mod translate; pub use bvh::*; -pub use hitable::*; +pub use translate::*; + +use std::sync::Arc; + +use crate::{ + hitable::rotate::Rotate, + types::{Ray, Vec3}, + Aabb, Material, X, Y, Z, +}; + +pub struct HitRecord<'a> { + /// Rays are represented by A + t * B + /// where A is the source point and B destination point + /// by adjusting t we can move forward/back on the ray + /// + /// t is the point at which a ray intersected another object. + /// As in, If we put this value of t in A + t * B equation, We'll get the exact + /// point at which a ray intersects some other object + pub t: f64, + /// Ray object otherwise is represented by the Source/Destination points + /// p is what we get when we perform the operation, A + t * B + /// i.e. A vector from Ray source to the point t + pub p: Vec3, + + /// unit outward facing normal + pub normal: Vec3, + + /// material if any of the surface + pub material: &'a dyn Material, + + /// texture coordinates for an object + pub u: f64, + pub v: f64, + + pub front_face: bool, +} + +impl<'a> HitRecord<'a> { + pub fn new( + t: f64, + p: Vec3, + normal: Vec3, + material: &'a dyn Material, + (u, v): (f64, f64), + ) -> Self { + Self { + t, + p, + normal, + material, + u, + v, + front_face: false, + } + } + + pub fn set_face_normal(&mut self, ray: &Ray) { + self.front_face = ray.direction.dot(&self.normal) < 0.0; + + self.normal = if self.front_face { + self.normal + } else { + -self.normal + } + } +} + +pub trait Hitable { + fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option; + + fn bounding_box(&self, _t0: f64, _t1: f64) -> Option; + + fn translate(self, offset: impl Into) -> Translate + where + Self: Sized, + { + Translate::new(self, offset.into()) + } + + fn rotate_x(self, angle: f64) -> Rotate + where + Self: Sized, + { + Rotate::new(self, angle) + } + + fn rotate_y(self, angle: f64) -> Rotate + where + Self: Sized, + { + Rotate::new(self, angle) + } + + fn rotate_z(self, angle: f64) -> Rotate + where + Self: Sized, + { + Rotate::new(self, angle) + } +} + +impl Hitable for Arc { + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { + self.as_ref().hit(ray, t_min, t_max) + } + fn bounding_box(&self, t0: f64, t1: f64) -> Option { + self.as_ref().bounding_box(t0, t1) + } +} diff --git a/src/hitable/rotate.rs b/src/hitable/rotate.rs new file mode 100644 index 0000000..a7b8b73 --- /dev/null +++ b/src/hitable/rotate.rs @@ -0,0 +1,130 @@ +use std::marker::PhantomData; + +use crate::{ + hitable::{HitRecord, Hitable}, + types::{Ray, Vec3}, + Aabb, Dimension, X, Y, Z, +}; + +pub struct Rotate { + hitable: T, + sin_theta: f64, + cos_theta: f64, + bbox: Option, + + _tag: PhantomData<(D1, D2, D3)>, +} + +impl Rotate +where + D1: Dimension, + D2: Dimension, + D3: Dimension, + T: Hitable, +{ + pub fn new(object: T, angle: f64) -> Rotate { + let radians = angle.to_radians(); + let sin_theta = radians.sin(); + let cos_theta = radians.cos(); + + let mut min = Vec3::splat(f64::MAX); + let mut max = Vec3::splat(-f64::MAX); + + let bbox = if let Some(bbox) = object.bounding_box(0.0, 1.0) { + for i in 0..2 { + let i = i as f64; + for j in 0..2 { + let j = j as f64; + for k in 0..2 { + let k = k as f64; + + // D1 will be the axis about which we are rotating + let d1 = i * bbox.max.get::() + (1.0 - i) * bbox.min.get::(); + + let d2 = j * bbox.max.get::() + (1.0 - j) * bbox.min.get::(); + let d3 = k * bbox.max.get::() + (1.0 - k) * bbox.min.get::(); + + let new_d2 = cos_theta * d2 + sin_theta * d3; + let new_d3 = -sin_theta * d2 + cos_theta * d3; + + let tester = Vec3::splat(0.0) + .set::(d1) + .set::(new_d2) + .set::(new_d3); + + min = Vec3::min(tester, min); + max = Vec3::max(tester, max); + } + } + } + + Aabb::new(min, max) + } else { + Aabb::new(min, max) + }; + + Rotate { + hitable: object, + sin_theta, + cos_theta, + bbox: Some(bbox), + _tag: PhantomData, + } + } +} + +impl Hitable for Rotate +where + D1: Dimension, + D2: Dimension, + D3: Dimension, + T: Hitable, +{ + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { + let origin = ray + .origin + .set::( + self.cos_theta * ray.origin.get::() - self.sin_theta * ray.origin.get::(), + ) + .set::( + self.sin_theta * ray.origin.get::() + self.cos_theta * ray.origin.get::(), + ); + + let direction = ray + .direction + .set::( + self.cos_theta * ray.direction.get::() + - self.sin_theta * ray.direction.get::(), + ) + .set::( + self.sin_theta * ray.direction.get::() + + self.cos_theta * ray.direction.get::(), + ); + + let rotated_ray = Ray::new(origin, direction, ray.time()); + + let mut hit = self.hitable.hit(&rotated_ray, t_min, t_max)?; + + hit.p = hit + .p + .set::(self.cos_theta * hit.p.get::() + self.sin_theta * hit.p.get::()) + .set::(-self.sin_theta * hit.p.get::() + self.cos_theta * hit.p.get::()); + + hit.normal = hit + .normal + .set::( + self.cos_theta * hit.normal.get::() + self.sin_theta * hit.normal.get::(), + ) + .set::( + -self.sin_theta * hit.normal.get::() + self.cos_theta * hit.normal.get::(), + ); + + hit.set_face_normal(&rotated_ray); + + Some(hit) + } + + fn bounding_box(&self, _t0: f64, _t1: f64) -> Option { + self.bbox + } +} diff --git a/src/hitable/translate.rs b/src/hitable/translate.rs new file mode 100644 index 0000000..5ad11d0 --- /dev/null +++ b/src/hitable/translate.rs @@ -0,0 +1,37 @@ +use crate::{ + hitable::{HitRecord, Hitable}, + types::{Ray, Vec3}, + Aabb, +}; + +pub struct Translate { + object: T, + offset: Vec3, +} + +impl Translate { + pub const fn new(object: T, offset: Vec3) -> Self { + Self { object, offset } + } +} + +impl Hitable for Translate { + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { + let moved_ray = Ray::new(ray.origin - self.offset, ray.direction, ray.time()); + + if let Some(mut hit) = self.object.hit(&moved_ray, t_min, t_max) { + hit.p += self.offset; + hit.set_face_normal(&moved_ray); + + Some(hit) + } else { + None + } + } + + fn bounding_box(&self, t0: f64, t1: f64) -> Option { + self.object + .bounding_box(t0, t1) + .map(|bbox| Aabb::new(bbox.min + self.offset, bbox.max + self.offset)) + } +} diff --git a/src/main.rs b/src/main.rs index b4e4f78..e4e60bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,7 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { .create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32) .map_err(|e| e.to_string())?; - let mut active_demo: &dyn Demo>> = &demos::CornellBox {}; + let mut active_demo: &dyn Demo>> = &demos::Instances {}; let mut should_update = true; loop { @@ -104,7 +104,7 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { should_update = true; } Some(Keycode::Num6) => { - active_demo = &demos::CornellBox {}; + active_demo = &demos::Instances {}; should_update = true; } None => unreachable!(),