diff --git a/Cargo.lock b/Cargo.lock index 8d273eb..42b1426 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,6 +331,7 @@ name = "rtnw" version = "0.1.0" dependencies = [ "image", + "num-traits", "rand", "rayon", "sdl2", diff --git a/Cargo.toml b/Cargo.toml index a0a9a11..3a1c286 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,14 +3,14 @@ authors = ["ishanjain28 "] edition = "2018" name = "rtnw" version = "0.1.0" - [dependencies] +num-traits = "*" rayon = "1.5.0" [dependencies.image] +default-features = false features = ["jpeg", "png"] version = "0.23.14" -default-features = false [dependencies.rand] features = ["small_rng"] diff --git a/src/aabb.rs b/src/aabb.rs index 15b9e15..bbf7277 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -13,9 +13,9 @@ impl Aabb { pub fn hit(&self, ray: &Ray, mut t_min: f64, mut t_max: f64) -> bool { for i in 0..=2 { - let inverse_dir = 1.0 / ray.direction()[i]; - let mut t0 = (self.min[i] - ray.origin()[i]) * inverse_dir; - let mut t1 = (self.max[i] - ray.origin()[i]) * inverse_dir; + let inverse_dir = 1.0 / ray.direction[i]; + let mut t0 = (self.min[i] - ray.origin[i]) * inverse_dir; + let mut t1 = (self.max[i] - ray.origin[i]) * inverse_dir; if inverse_dir < 0.0 { std::mem::swap(&mut t0, &mut t1); } @@ -31,17 +31,8 @@ impl Aabb { } pub fn surrounding_box(box0: Aabb, box1: Aabb) -> Self { - let smol_box = Vec3::new( - box0.min.x().min(box1.min.x()), - box0.min.y().min(box1.min.y()), - box0.min.z().min(box1.min.z()), - ); - - let big_box = Vec3::new( - box0.max.x().max(box1.max.x()), - box0.max.y().max(box1.max.y()), - box0.max.z().max(box1.max.z()), - ); + let smol_box = Vec3::min(box0.min, box1.min); + let big_box = Vec3::max(box0.max, box1.max); Self { min: smol_box, diff --git a/src/demos/checkered_motion_blur.rs b/src/demos/checkered_motion_blur.rs index 725c6f4..83330ea 100644 --- a/src/demos/checkered_motion_blur.rs +++ b/src/demos/checkered_motion_blur.rs @@ -1,10 +1,13 @@ use crate::{ demos::{Demo, ParallelHit}, + hitable::{ + shapes::{MovingSphere, Sphere}, + BvhNode, + }, materials::{Dielectric, Lambertian, Metal}, - shapes::{MovingSphere, Sphere}, texture::{Checker, Solid}, types::Vec3, - BvhNode, Camera, + Camera, }; use rand::{rngs::SmallRng, Rng, SeedableRng}; use std::sync::Arc; @@ -18,6 +21,10 @@ impl Demo for CheckeredMotionBlur { "checkered_motion_blur" } + fn get_background(&self) -> Vec3 { + Vec3::new(0.7, 0.8, 1.0) + } + fn world(&self) -> Self::DemoT { let mut world: Vec> = Vec::with_capacity(500); diff --git a/src/demos/cornell_box.rs b/src/demos/cornell_box.rs new file mode 100644 index 0000000..506ff9c --- /dev/null +++ b/src/demos/cornell_box.rs @@ -0,0 +1,110 @@ +use std::sync::Arc; + +use rand::{prelude::SmallRng, SeedableRng}; + +use crate::{ + demos::{Demo, ParallelHit}, + hitable::shapes::{Cuboid, RectBuilder}, + materials::{DiffuseLight, Lambertian, MaterialBuilder}, + texture::Solid, + types::Vec3, + BvhNode, Camera, +}; + +pub struct CornellBox {} + +impl Demo for CornellBox { + type DemoT = BvhNode>; + + fn name(&self) -> &'static str { + "cornell_box" + } + + fn world(&self) -> Self::DemoT { + let mut world: Vec> = Vec::with_capacity(8); + + let mut rng = rand::thread_rng(); + let mut rng = SmallRng::from_rng(&mut rng).unwrap(); + + let red = Lambertian::new(Solid::new(Vec3::new(0.65, 0.05, 0.05))); + let white = Lambertian::new(Solid::new(Vec3::splat(0.73))); + let green = Lambertian::new(Solid::new(Vec3::new(0.12, 0.45, 0.15))); + let light = DiffuseLight::new(Solid::new(Vec3::splat(15.0))); + + world.push(Arc::new( + RectBuilder + .y(0.0..=555.0) + .z(0.0..=555.0) + .x(555.0) + .material(green), + )); + world.push(Arc::new( + RectBuilder + .y(0.0..=555.0) + .z(0.0..=555.0) + .x(0.0) + .material(red), + )); + world.push(Arc::new( + RectBuilder + .x(213.0..=343.0) + .z(227.0..=332.0) + .y(554.0) + .material(light), + )); + + world.push(Arc::new( + RectBuilder + .x(0.0..=555.0) + .z(0.0..=555.0) + .y(0.0) + .material(white), + )); + world.push(Arc::new( + RectBuilder + .x(0.0..=555.0) + .z(0.0..=555.0) + .y(555.0) + .material(white), + )); + world.push(Arc::new( + RectBuilder + .x(0.0..=555.0) + .y(0.0..=555.0) + .z(555.0) + .material(white), + )); + + // 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, + ))); + + BvhNode::new(&mut rng, &mut world, 0.0, 1.0) + } + + fn camera(&self, aspect_ratio: f64) -> Camera { + let lookfrom = Vec3::new(278.0, 278.0, -800.0); + let lookat = Vec3::new(278.0, 278.0, 0.0); + let aperture = 0.1; + let focus_distance = 40.0; + Camera::new( + lookfrom, + lookat, + Vec3::new(0.0, 1.0, 0.0), + 40.0, + aspect_ratio, + aperture, + focus_distance, + 0.0, + 1.0, + ) + } +} diff --git a/src/demos/image_texture.rs b/src/demos/image_texture.rs index 2934a19..612c8bc 100644 --- a/src/demos/image_texture.rs +++ b/src/demos/image_texture.rs @@ -4,8 +4,8 @@ use rand::{prelude::SmallRng, SeedableRng}; use crate::{ demos::{Demo, ParallelHit}, + hitable::shapes::Sphere, materials::Lambertian, - shapes::Sphere, texture::ImageTexture, types::Vec3, BvhNode, Camera, @@ -20,6 +20,10 @@ impl Demo for ImageTextureDemo { "image_texture" } + fn get_background(&self) -> Vec3 { + Vec3::new(0.7, 0.8, 1.0) + } + fn world(&self) -> Self::DemoT { let mut world: Vec> = Vec::with_capacity(1); diff --git a/src/demos/mod.rs b/src/demos/mod.rs index 91cedbb..6b0e0fa 100644 --- a/src/demos/mod.rs +++ b/src/demos/mod.rs @@ -1,4 +1,8 @@ -use crate::{types::Vec3, Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION}; +use crate::{ + hitable::Hitable, + types::{Color, Vec3}, + Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION, +}; use rand::{rngs::SmallRng, Rng, SeedableRng}; use rayon::prelude::*; use std::{ @@ -9,12 +13,14 @@ use std::{ }; mod checkered_motion_blur; +mod cornell_box; mod image_texture; 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 perlin_noise_ball::PerlinNoiseBall; pub use simple_light::SimpleLight; @@ -55,7 +61,7 @@ pub trait Demo: Send + Sync { fn camera(&self, aspect_ratio: f64) -> Camera; fn get_background(&self) -> Vec3 { - Vec3::new(0.7, 0.8, 1.0) + Vec3::new(0.0, 0.0, 0.0) } fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &Self::DemoT, samples: u16) { @@ -156,14 +162,16 @@ pub trait Demo: Send + Sync { #[inline] fn update_rgb(&self, buffer: &mut [u8], color: Vec3, offset: usize) { + let color: Color = color.into(); + if let Some(pos) = buffer.get_mut(offset) { - *pos = (255.99 * color.r().sqrt()) as u8; + *pos = color.0; } if let Some(pos) = buffer.get_mut(offset + 1) { - *pos = (255.99 * color.g().sqrt()) as u8; + *pos = color.1 } if let Some(pos) = buffer.get_mut(offset + 2) { - *pos = (255.99 * color.b().sqrt()) as u8; + *pos = color.2; } } diff --git a/src/demos/perlin_noise_ball.rs b/src/demos/perlin_noise_ball.rs index 8b71beb..299e88a 100644 --- a/src/demos/perlin_noise_ball.rs +++ b/src/demos/perlin_noise_ball.rs @@ -4,11 +4,11 @@ use rand::{prelude::SmallRng, SeedableRng}; use crate::{ demos::{Demo, ParallelHit}, + hitable::{shapes::Sphere, BvhNode}, materials::Lambertian, - shapes::Sphere, texture::PerlinNoise, types::Vec3, - BvhNode, Camera, + Camera, }; pub struct PerlinNoiseBall {} @@ -20,6 +20,10 @@ impl Demo for PerlinNoiseBall { "perlin_noise" } + fn get_background(&self) -> Vec3 { + Vec3::new(0.7, 0.8, 1.0) + } + fn world(&self) -> Self::DemoT { let mut world: Vec> = Vec::with_capacity(2); diff --git a/src/demos/simple_light.rs b/src/demos/simple_light.rs index ad544dd..9c67786 100644 --- a/src/demos/simple_light.rs +++ b/src/demos/simple_light.rs @@ -4,11 +4,14 @@ use rand::{prelude::SmallRng, SeedableRng}; use crate::{ demos::{Demo, ParallelHit}, - materials::{DiffuseLight, Lambertian}, - shapes::{Sphere, XyRectangle}, + hitable::{ + shapes::{RectBuilder, Sphere}, + BvhNode, + }, + materials::{DiffuseLight, Lambertian, MaterialBuilder}, texture::{PerlinNoise, Solid}, types::Vec3, - BvhNode, Camera, + Camera, }; pub struct SimpleLight {} @@ -20,10 +23,6 @@ impl Demo for SimpleLight { "simple_light" } - fn get_background(&self) -> Vec3 { - Vec3::new(0.0, 0.0, 0.0) - } - fn world(&self) -> Self::DemoT { let mut world: Vec> = Vec::with_capacity(5); let mut rng = rand::thread_rng(); @@ -39,14 +38,14 @@ impl Demo for SimpleLight { 2.0, Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)), ))); - world.push(Arc::new(XyRectangle::new( - 3.0, - 5.0, - 1.0, - 3.0, - -2.0, - DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))), - ))); + + world.push(Arc::new( + RectBuilder + .x(3.0..=5.0) + .y(1.0..=3.0) + .z(-2.0) + .material(DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0)))), + )); world.push(Arc::new(Sphere::new( Vec3::new(0.0, 7.0, 0.0), 2.0, diff --git a/src/demos/two_spheres.rs b/src/demos/two_spheres.rs index de3af1b..76dfe3e 100644 --- a/src/demos/two_spheres.rs +++ b/src/demos/two_spheres.rs @@ -4,11 +4,11 @@ use rand::{prelude::SmallRng, SeedableRng}; use crate::{ demos::{Demo, ParallelHit}, + hitable::{shapes::Sphere, BvhNode}, materials::Lambertian, - shapes::Sphere, texture::{Checker, Solid}, types::Vec3, - BvhNode, Camera, + Camera, }; pub struct TwoSpheres {} @@ -20,6 +20,10 @@ impl Demo for TwoSpheres { "two_checkered_sphere" } + fn get_background(&self) -> Vec3 { + Vec3::new(0.7, 0.8, 1.0) + } + fn world(&self) -> Self::DemoT { let mut world: Vec> = Vec::with_capacity(2); diff --git a/src/bvh.rs b/src/hitable/bvh.rs similarity index 98% rename from src/bvh.rs rename to src/hitable/bvh.rs index 5db3983..fa4b6f9 100644 --- a/src/bvh.rs +++ b/src/hitable/bvh.rs @@ -2,7 +2,11 @@ use std::cmp::Ordering; use rand::{prelude::SliceRandom, Rng}; -use crate::{types::Ray, Aabb, HitRecord, Hitable}; +use crate::{ + hitable::{HitRecord, Hitable}, + types::Ray, + Aabb, +}; pub struct BvhNode { bounding_box: Aabb, diff --git a/src/hitable.rs b/src/hitable/hitable.rs similarity index 59% rename from src/hitable.rs rename to src/hitable/hitable.rs index cc1c77d..fc3a0ea 100644 --- a/src/hitable.rs +++ b/src/hitable/hitable.rs @@ -28,6 +28,8 @@ pub struct HitRecord<'a> { /// texture coordinates for an object pub u: f64, pub v: f64, + + pub front_face: bool, } impl<'a> HitRecord<'a> { @@ -45,6 +47,17 @@ impl<'a> HitRecord<'a> { 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 } } } @@ -63,3 +76,37 @@ impl Hitable for Arc { 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_list.rs b/src/hitable/hitable_list.rs similarity index 92% rename from src/hitable_list.rs rename to src/hitable/hitable_list.rs index b8ff514..4eff7b6 100644 --- a/src/hitable_list.rs +++ b/src/hitable/hitable_list.rs @@ -1,6 +1,11 @@ use std::sync::Arc; -use crate::{demos::ParallelHit, types::Ray, Aabb, HitRecord, Hitable}; +use crate::{ + demos::ParallelHit, + hitable::{HitRecord, Hitable}, + types::Ray, + Aabb, +}; pub struct HitableList { pub list: Vec>, diff --git a/src/hitable/mod.rs b/src/hitable/mod.rs new file mode 100644 index 0000000..39f6404 --- /dev/null +++ b/src/hitable/mod.rs @@ -0,0 +1,7 @@ +pub mod bvh; +pub mod hitable; +pub mod hitable_list; +pub mod shapes; + +pub use bvh::*; +pub use hitable::*; diff --git a/src/hitable/shapes/cuboid.rs b/src/hitable/shapes/cuboid.rs new file mode 100644 index 0000000..767a690 --- /dev/null +++ b/src/hitable/shapes/cuboid.rs @@ -0,0 +1,87 @@ +use std::sync::Arc; + +use crate::{ + hitable::{hitable_list::HitableList, shapes::RectBuilder, HitRecord, Hitable}, + materials::{Material, MaterialBuilder}, + types::{Ray, Vec3}, + Aabb, +}; + +pub struct Cuboid { + min: Vec3, + max: Vec3, + sides: HitableList, +} + +impl Cuboid { + pub fn new(p0: Vec3, p1: Vec3, mat: impl Material + Clone + 'static) -> Self { + Self { + min: p0, + max: p1, + sides: Self::build_cuboid(p0, p1, mat), + } + } + + fn build_cuboid(p0: Vec3, p1: Vec3, mat: impl Material + Clone + 'static) -> HitableList { + let mut sides = HitableList { + list: Vec::with_capacity(6), + }; + + sides.push(Arc::new( + RectBuilder + .x(p0.x()..=p1.x()) + .y(p0.y()..=p1.y()) + .z(p1.z()) + .material(mat.clone()), + )); + sides.push(Arc::new( + RectBuilder + .x(p0.x()..=p1.x()) + .y(p0.y()..=p1.y()) + .z(p0.z()) + .material(mat.clone()), + )); + + sides.push(Arc::new( + RectBuilder + .x(p0.x()..=p1.x()) + .z(p0.z()..=p1.z()) + .y(p1.y()) + .material(mat.clone()), + )); + sides.push(Arc::new( + RectBuilder + .x(p0.x()..=p1.x()) + .z(p0.z()..=p1.z()) + .y(p0.y()) + .material(mat.clone()), + )); + + sides.push(Arc::new( + RectBuilder + .y(p0.y()..=p1.y()) + .z(p0.z()..=p1.z()) + .x(p1.x()) + .material(mat.clone()), + )); + sides.push(Arc::new( + RectBuilder + .y(p0.y()..=p1.y()) + .z(p0.z()..=p1.z()) + .x(p0.x()) + .material(mat), + )); + + sides + } +} + +impl Hitable for Cuboid { + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { + self.sides.hit(ray, t_min, t_max) + } + + fn bounding_box(&self, _t0: f64, _t1: f64) -> Option { + Some(Aabb::new(self.min, self.max)) + } +} diff --git a/src/shapes/mod.rs b/src/hitable/shapes/mod.rs similarity index 52% rename from src/shapes/mod.rs rename to src/hitable/shapes/mod.rs index 9a84ea6..2e0a28a 100644 --- a/src/shapes/mod.rs +++ b/src/hitable/shapes/mod.rs @@ -1,7 +1,9 @@ +mod cuboid; mod moving_sphere; +mod rectangle; mod sphere; -mod xy_rectangle; +pub use cuboid::Cuboid; pub use moving_sphere::MovingSphere; +pub use rectangle::RectBuilder; pub use sphere::Sphere; -pub use xy_rectangle::XyRectangle; diff --git a/src/shapes/moving_sphere.rs b/src/hitable/shapes/moving_sphere.rs similarity index 92% rename from src/shapes/moving_sphere.rs rename to src/hitable/shapes/moving_sphere.rs index 2c6a583..5588ad5 100644 --- a/src/shapes/moving_sphere.rs +++ b/src/hitable/shapes/moving_sphere.rs @@ -1,6 +1,7 @@ use crate::{ + hitable::{HitRecord, Hitable}, types::{Ray, Vec3}, - Aabb, HitRecord, Hitable, Material, + Aabb, Material, }; pub struct MovingSphere { @@ -53,9 +54,9 @@ impl MovingSphere { impl Hitable for MovingSphere { fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { - let oc = ray.origin() - self.center(ray.time()); - let a = ray.direction().dot(&ray.direction()); - let b = oc.dot(&ray.direction()); + let oc = ray.origin - self.center(ray.time()); + let a = ray.direction.dot(&ray.direction); + let b = oc.dot(&ray.direction); let c = oc.dot(&oc) - self.radius * self.radius; let discriminant = b * b - a * c; diff --git a/src/hitable/shapes/rectangle.rs b/src/hitable/shapes/rectangle.rs new file mode 100644 index 0000000..f26ffd4 --- /dev/null +++ b/src/hitable/shapes/rectangle.rs @@ -0,0 +1,175 @@ +use std::{marker::PhantomData, ops::RangeInclusive}; + +use crate::{ + hitable::{HitRecord, Hitable}, + materials::MaterialBuilder, + types::{Ray, Vec3}, + Aabb, Dimension, Material, X, Y, Z, +}; + +type DimRange = RangeInclusive; + +pub struct Rectangle { + d1_range: DimRange, + d2_range: DimRange, + d3: f64, + material: T, + tag: PhantomData<(D1, D2, D3)>, +} + +impl Hitable for Rectangle +where + T: Material, + D1: Dimension, + D2: Dimension, + D3: Dimension, +{ + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { + let t = (self.d3 - ray.origin.get::()) / ray.direction.get::(); + + if t < t_min || t > t_max { + return None; + } + + let d1 = ray.origin.get::() + t * ray.direction.get::(); + let d2 = ray.origin.get::() + t * ray.direction.get::(); + + if !self.d1_range.contains(&d1) || !self.d2_range.contains(&d2) { + return None; + } + + let u = (d1 - self.d1_range.start()) / (self.d1_range.end() - self.d1_range.start()); + let v = (d2 - self.d2_range.start()) / (self.d2_range.end() - self.d2_range.start()); + + let mut hit_rec = HitRecord::new( + t, + ray.point_at_parameter(t), + Vec3::splat(0.0).set::(1.0), + &self.material, + (u, v), + ); + + hit_rec.set_face_normal(ray); + + Some(hit_rec) + } + + fn bounding_box(&self, _t0: f64, _t1: f64) -> Option { + // Since this is a axis aligned Rectangle and we are using AABB BVH, Gap between the rectangle and + // the bounding box will be infinitely small + + let (&d1_0, &d1_1) = (self.d1_range.start(), self.d1_range.end()); + let (&d2_0, &d2_1) = (self.d2_range.start(), self.d2_range.end()); + + let min = Vec3::splat(self.d3 - 0.0001) + .set::(d1_0) + .set::(d2_0); + let max = Vec3::splat(self.d3 + 0.0001) + .set::(d1_1) + .set::(d2_1); + + Some(Aabb::new(min, max)) + } +} + +// taken from, https://github.com/Globidev/toy-rt/blob/master/trt-core/src/hit/rect.rs#L74 +// because it's amazing! + +pub struct RectBuilder; + +macro_rules! builder { + ($name:ident, $dim:ty) => { + pub fn $name(self, range: RangeInclusive) -> OneBoundedRectBuilder<$dim> { + OneBoundedRectBuilder { + range, + tag: PhantomData, + } + } + }; +} + +pub struct OneBoundedRectBuilder { + range: DimRange, + tag: PhantomData, +} + +impl RectBuilder { + builder!(x, X); + builder!(y, Y); + builder!(z, Z); +} + +macro_rules! one_bounded_rect_builder { + ($name:ident, $dim1: ty, $dim2: ty) => { + pub fn $name(self, d2_range: DimRange) -> TwoBoundedRectBuilder<$dim1, $dim2> { + TwoBoundedRectBuilder { + d1_range: self.range, + d2_range, + tag: PhantomData, + } + } + }; +} + +impl OneBoundedRectBuilder { + one_bounded_rect_builder!(y, X, Y); + one_bounded_rect_builder!(z, X, Z); +} +impl OneBoundedRectBuilder { + one_bounded_rect_builder!(x, Y, X); + one_bounded_rect_builder!(z, Y, Z); +} +impl OneBoundedRectBuilder { + one_bounded_rect_builder!(x, Z, X); + one_bounded_rect_builder!(y, Z, Y); +} + +pub struct TwoBoundedRectBuilder { + d1_range: DimRange, + d2_range: DimRange, + tag: PhantomData<(D1, D2)>, +} + +macro_rules! two_bounded_rect_builder { + ($name:ident, $dim1: ty, $dim2: ty, $dim3: ty) => { + pub fn $name(self, $name: f64) -> ThreeBoundedRectBuilder<$dim1, $dim2, $dim3> { + ThreeBoundedRectBuilder { + d1_range: self.d1_range, + d2_range: self.d2_range, + d3: $name, + tag: PhantomData, + } + } + }; +} + +impl TwoBoundedRectBuilder { + two_bounded_rect_builder!(z, X, Y, Z); +} +impl TwoBoundedRectBuilder { + two_bounded_rect_builder!(y, X, Z, Y); +} +impl TwoBoundedRectBuilder { + two_bounded_rect_builder!(x, Y, Z, X); +} + +pub struct ThreeBoundedRectBuilder { + d1_range: DimRange, + d2_range: DimRange, + d3: f64, + tag: PhantomData<(D1, D2, D3)>, +} + +impl MaterialBuilder for ThreeBoundedRectBuilder { + type Finished = Rectangle; + + fn material(self, material: T) -> Self::Finished { + Rectangle { + d1_range: self.d1_range, + d2_range: self.d2_range, + d3: self.d3, + material, + tag: PhantomData, + } + } +} diff --git a/src/shapes/sphere.rs b/src/hitable/shapes/sphere.rs similarity index 92% rename from src/shapes/sphere.rs rename to src/hitable/shapes/sphere.rs index 1fad9b6..79dc1fc 100644 --- a/src/shapes/sphere.rs +++ b/src/hitable/shapes/sphere.rs @@ -1,6 +1,7 @@ use crate::{ + hitable::{HitRecord, Hitable}, types::{Ray, Vec3}, - Aabb, HitRecord, Hitable, Material, + Aabb, Material, }; pub struct Sphere { @@ -34,9 +35,9 @@ impl Sphere { impl Hitable for Sphere { fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { - let oc = ray.origin() - self.center; - let a = ray.direction().dot(&ray.direction()); - let b = oc.dot(&ray.direction()); + let oc = ray.origin - self.center; + let a = ray.direction.dot(&ray.direction); + let b = oc.dot(&ray.direction); let c = oc.dot(&oc) - self.radius * self.radius; // The discriminant is calculated using b^2 - 4 * a * c diff --git a/src/main.rs b/src/main.rs index 331673f..b4e4f78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,33 +1,32 @@ #![allow(clippy::suspicious_arithmetic_impl)] mod aabb; -mod bvh; mod camera; mod demos; mod hitable; -mod hitable_list; mod materials; -mod shapes; mod texture; mod types; pub use aabb::Aabb; -pub use bvh::BvhNode; pub use camera::Camera; -pub use hitable::{HitRecord, Hitable}; -pub use hitable_list::HitableList; pub use materials::Material; pub use texture::Texture; +pub use types::{Dimension, X, Y, Z}; +use crate::hitable::BvhNode; use demos::Demo; use std::time::Instant; -const NUM_SAMPLES: u16 = 1000; +pub trait Asf64: num_traits::AsPrimitive {} +impl> Asf64 for T {} + +const NUM_SAMPLES: u16 = 100; const VERTICAL_PARTITION: usize = 12; const HORIZONTAL_PARTITION: usize = 12; -const WIDTH: usize = 2560; -const HEIGHT: usize = 1440; +const WIDTH: usize = 1500; +const HEIGHT: usize = 1500; fn main() -> Result<(), String> { run(WIDTH, HEIGHT) @@ -67,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::SimpleLight {}; + let mut active_demo: &dyn Demo>> = &demos::CornellBox {}; let mut should_update = true; loop { @@ -104,6 +103,10 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { active_demo = &demos::SimpleLight {}; should_update = true; } + Some(Keycode::Num6) => { + active_demo = &demos::CornellBox {}; + should_update = true; + } None => unreachable!(), _ => (), }; diff --git a/src/materials/dielectric.rs b/src/materials/dielectric.rs index 78743ef..8a8257e 100644 --- a/src/materials/dielectric.rs +++ b/src/materials/dielectric.rs @@ -1,18 +1,19 @@ use rand::{prelude::SmallRng, Rng}; use crate::{ + hitable::HitRecord, materials::{reflect, refract, schlick}, types::{Ray, Vec3}, - HitRecord, Material, + Material, }; pub struct Dielectric { - reflection_index: f64, + refraction_index: f64, } impl Dielectric { - pub fn new(reflection_index: f64) -> Self { - Self { reflection_index } + pub fn new(refraction_index: f64) -> Self { + Self { refraction_index } } } @@ -23,44 +24,37 @@ impl Material for Dielectric { hit_rec: &HitRecord, rng: &mut SmallRng, ) -> (Vec3, Option) { - let reflected_ray = reflect(ray_in.direction(), hit_rec.normal); // Glass absorbs nothing! So, Attenuation is always going to be 1.0 for this let attenuation = Vec3::new(1.0, 1.0, 1.0); - let (outward_normal, ni_over_nt, cosine) = if ray_in.direction().dot(&hit_rec.normal) > 0.0 - { - ( - -hit_rec.normal, - self.reflection_index, - (ray_in.direction().dot(&hit_rec.normal) * self.reflection_index) - / ray_in.direction().length(), - ) + let refraction_ratio = if hit_rec.front_face { + 1.0 / self.refraction_index } else { - ( - hit_rec.normal, - 1.0 / self.reflection_index, - (-ray_in.direction().dot(&hit_rec.normal)) / ray_in.direction().length(), - ) + self.refraction_index }; - if let Some(refracted_ray) = refract(ray_in.direction(), outward_normal, ni_over_nt) { - let reflect_prob = schlick(cosine, self.reflection_index); + let unit_direction = ray_in.direction.unit_vector(); + let cosine = (-unit_direction).dot(&hit_rec.normal).min(1.0); + let sin_theta = (1.0 - cosine * cosine).sqrt(); - if rng.gen::() < reflect_prob { - ( - attenuation, - Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())), - ) - } else { - ( - attenuation, - Some(Ray::new(hit_rec.p, refracted_ray, ray_in.time())), - ) - } - } else { + let cannot_refract = refraction_ratio * sin_theta > 1.0; + + if cannot_refract || schlick(cosine, refraction_ratio) > rng.gen::() { + let direction = reflect(unit_direction, hit_rec.normal); ( attenuation, - Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())), + Some(Ray::new(hit_rec.p, direction, ray_in.time())), + ) + } else if let Some(direction) = refract(unit_direction, hit_rec.normal, refraction_ratio) { + ( + attenuation, + Some(Ray::new(hit_rec.p, direction, ray_in.time())), + ) + } else { + let direction = reflect(unit_direction, hit_rec.normal); + ( + attenuation, + Some(Ray::new(hit_rec.p, direction, ray_in.time())), ) } } diff --git a/src/materials/lambertian.rs b/src/materials/lambertian.rs index 9a8c245..56ceb36 100644 --- a/src/materials/lambertian.rs +++ b/src/materials/lambertian.rs @@ -1,11 +1,13 @@ use rand::prelude::SmallRng; use crate::{ + hitable::HitRecord, materials::random_point_in_unit_sphere, types::{Ray, Vec3}, - HitRecord, Material, Texture, + Material, Texture, }; +#[derive(Copy, Clone)] pub struct Lambertian { albedo: T, } diff --git a/src/materials/metal.rs b/src/materials/metal.rs index 1e4ae9e..37b954e 100644 --- a/src/materials/metal.rs +++ b/src/materials/metal.rs @@ -1,9 +1,10 @@ use rand::prelude::SmallRng; use crate::{ + hitable::HitRecord, materials::{random_point_in_unit_sphere, reflect}, types::{Ray, Vec3}, - HitRecord, Material, + Material, }; pub struct Metal { @@ -28,14 +29,14 @@ impl Material for Metal { hit_rec: &HitRecord, rng: &mut SmallRng, ) -> (Vec3, Option) { - let reflected_ray = reflect(ray_in.direction().unit_vector(), hit_rec.normal); + let reflected_ray = reflect(ray_in.direction.unit_vector(), hit_rec.normal); let scattered_ray = Ray::new( hit_rec.p, reflected_ray + random_point_in_unit_sphere(rng) * self.fuzz, ray_in.time(), ); - if scattered_ray.direction().dot(&hit_rec.normal) > 0.0 { + if scattered_ray.direction.dot(&hit_rec.normal) > 0.0 { (self.albedo, Some(scattered_ray)) } else { (self.albedo, None) diff --git a/src/materials/mod.rs b/src/materials/mod.rs index 996f7ff..871400a 100644 --- a/src/materials/mod.rs +++ b/src/materials/mod.rs @@ -10,8 +10,8 @@ pub use metal::Metal; use rand::{prelude::SmallRng, Rng}; use crate::{ + hitable::HitRecord, types::{Ray, Vec3}, - HitRecord, }; pub trait Material: Send + Sync { @@ -23,11 +23,11 @@ pub trait Material: Send + Sync { _hit_rec: &HitRecord, _rng: &mut SmallRng, ) -> (Vec3, Option) { - (Vec3::new(0.0, 0.0, 0.0), None) + (Vec3::splat(0.0), None) } fn emit(&self, _u: f64, _v: f64, _p: Vec3) -> Vec3 { - Vec3::new(0.0, 0.0, 0.0) + Vec3::splat(0.0) } } @@ -56,11 +56,15 @@ fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f64) -> Option { } fn random_point_in_unit_sphere(rng: &mut R) -> Vec3 { - let mut point = Vec3::new(rng.gen::(), rng.gen::(), rng.gen::()) * 2.0 - - Vec3::new(1.0, 1.0, 1.0); + let mut point = Vec3::random(rng) * 2.0 - Vec3::splat(1.0); while point.sq_len() >= 1.0 { - point = Vec3::new(rng.gen::(), rng.gen::(), rng.gen::()) * 2.0 - - Vec3::new(1.0, 1.0, 1.0); + point = Vec3::random(rng) * 2.0 - Vec3::splat(1.0); } point } + +pub trait MaterialBuilder { + type Finished; + + fn material(self, material: T) -> Self::Finished; +} diff --git a/src/shapes/xy_rectangle.rs b/src/shapes/xy_rectangle.rs deleted file mode 100644 index 29bfd8f..0000000 --- a/src/shapes/xy_rectangle.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::{ - types::{Ray, Vec3}, - Aabb, HitRecord, Hitable, Material, -}; - -pub struct XyRectangle { - x0: f64, - x1: f64, - y0: f64, - y1: f64, - k: f64, - material: T, -} - -impl XyRectangle { - pub fn new(x0: f64, x1: f64, y0: f64, y1: f64, k: f64, material: T) -> Self { - Self { - x0, - x1, - y0, - y1, - k, - material, - } - } -} - -impl Hitable for XyRectangle { - fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { - let t = (self.k - ray.origin().z()) / ray.direction().z(); - - if t < t_min || t > t_max { - None - } else { - let x = ray.origin().x() + t * ray.direction().x(); - let y = ray.origin().y() + t * ray.direction().y(); - - if x < self.x0 || x > self.x1 || y < self.y0 || y > self.y1 { - None - } else { - let u = (x - self.x0) / (self.x1 - self.x0); - let v = (y - self.y0) / (self.y1 - self.y0); - - Some(HitRecord::new( - t, - ray.point_at_parameter(t), - Vec3::new(0.0, 0.0, 1.0), - &self.material, - (u, v), - )) - } - } - } - - fn bounding_box(&self, _t0: f64, _t1: f64) -> Option { - // Since this is a axis aligned Rectangle and we are using AABB BVH, Gap between the rectangle and - // the bounding box will be infinitely small - - Some(Aabb::new( - Vec3::new(self.x0, self.y0, self.k - 0.0001), - Vec3::new(self.x1, self.y1, self.k + 0.0001), - )) - } -} diff --git a/src/texture/solid.rs b/src/texture/solid.rs index 9839394..d3bce08 100644 --- a/src/texture/solid.rs +++ b/src/texture/solid.rs @@ -1,5 +1,6 @@ use crate::{types::Vec3, Texture}; +#[derive(Copy, Clone)] pub struct Solid { color: Vec3, } diff --git a/src/types/color.rs b/src/types/color.rs new file mode 100644 index 0000000..938d08d --- /dev/null +++ b/src/types/color.rs @@ -0,0 +1,12 @@ +use crate::types::Vec3; + +pub struct Color(pub u8, pub u8, pub u8); + +impl From for Color { + fn from(v: Vec3) -> Self { + let v = v.sqrt() * 255.99; + let (r, g, b) = (v.x(), v.y(), v.z()); + + Self(r as u8, g as u8, b as u8) + } +} diff --git a/src/types/dimension.rs b/src/types/dimension.rs new file mode 100644 index 0000000..89b8937 --- /dev/null +++ b/src/types/dimension.rs @@ -0,0 +1,21 @@ +// Taken from, https://github.com/Globidev/toy-rt/tree/master/trt-core/src/dimension.rs + +pub trait Dimension { + const INDEX: usize; +} + +pub struct X; +pub struct Y; +pub struct Z; + +impl Dimension for X { + const INDEX: usize = 0; +} + +impl Dimension for Y { + const INDEX: usize = 1; +} + +impl Dimension for Z { + const INDEX: usize = 2; +} diff --git a/src/types/mod.rs b/src/types/mod.rs index d5dd4ac..5fca40d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,9 @@ +mod color; +mod dimension; mod ray; mod vec3; +pub use color::Color; +pub use dimension::{Dimension, X, Y, Z}; pub use ray::Ray; pub use vec3::Vec3; diff --git a/src/types/ray.rs b/src/types/ray.rs index 35a1613..d38d153 100644 --- a/src/types/ray.rs +++ b/src/types/ray.rs @@ -1,28 +1,25 @@ use rand::prelude::SmallRng; -use crate::{types::Vec3, Hitable}; +use crate::{hitable::Hitable, types::Vec3}; pub struct Ray { - a: Vec3, - b: Vec3, + pub origin: Vec3, + pub direction: Vec3, time: f64, } impl Ray { - pub fn new(a: Vec3, b: Vec3, time: f64) -> Ray { - Ray { a, b, time } - } - #[inline] - pub const fn origin(&self) -> Vec3 { - self.a - } - #[inline] - pub const fn direction(&self) -> Vec3 { - self.b + pub fn new(origin: Vec3, direction: Vec3, time: f64) -> Ray { + Ray { + origin, + direction, + time, + } } + #[inline] pub fn point_at_parameter(&self, t: f64) -> Vec3 { - self.a + self.b * t + self.origin + self.direction * t } #[inline] pub const fn time(&self) -> f64 { @@ -38,7 +35,7 @@ impl Ray { ) -> Vec3 { if let Some(hit_rec) = world.hit(self, 0.001, std::f64::MAX) { if depth >= 50 { - Vec3::new(0.0, 0.0, 0.0) + Vec3::splat(0.0f64) } else { let material = hit_rec.material; let emitted_color = hit_rec.material.emit(hit_rec.u, hit_rec.v, hit_rec.p); diff --git a/src/types/vec3.rs b/src/types/vec3.rs index 84896c1..28d473b 100644 --- a/src/types/vec3.rs +++ b/src/types/vec3.rs @@ -5,37 +5,45 @@ use std::{ use rand::Rng; -#[derive(Debug, Copy, Clone)] +use crate::{Asf64, Dimension, X, Y, Z}; + +#[derive(Default, Debug, Copy, Clone)] pub struct Vec3([f64; 3]); impl Vec3 { #[inline] - pub const fn new(a: f64, b: f64, c: f64) -> Vec3 { - Vec3([a, b, c]) + pub fn new(a: impl Asf64, b: impl Asf64, c: impl Asf64) -> Vec3 { + Vec3([a.as_(), b.as_(), c.as_()]) } + + pub fn splat(xyz: impl Asf64) -> Self { + Self::new(xyz, xyz, xyz) + } + + pub fn random(rng: &mut R) -> Self { + Self(rng.gen()) + } + #[inline] pub fn x(&self) -> f64 { - self[0] + self.get::() } #[inline] pub fn y(&self) -> f64 { - self[1] + self.get::() } #[inline] pub fn z(&self) -> f64 { - self[2] + self.get::() } - #[inline] - pub fn r(&self) -> f64 { - self[0] + + pub fn get(&self) -> f64 { + self.0[D::INDEX] } - #[inline] - pub fn g(&self) -> f64 { - self[1] - } - #[inline] - pub fn b(&self) -> f64 { - self[2] + + pub fn set(mut self, value: f64) -> Self { + self.0[D::INDEX] = value; + self } #[inline] @@ -45,40 +53,55 @@ impl Vec3 { #[inline] pub fn sq_len(&self) -> f64 { - self[0] * self[0] + self[1] * self[1] + self[2] * self[2] + self.x() * self.x() + self.y() * self.y() + self.z() * self.z() } #[inline] pub fn dot(&self, v: &Vec3) -> f64 { - self[0] * v[0] + self[1] * v[1] + self[2] * v[2] + self.x() * v.x() + self.y() * v.y() + self.z() * v.z() } #[inline] pub fn cross(&self, v: &Vec3) -> Vec3 { Vec3([ - self[1] * v[2] - self[2] * v[1], - self[2] * v[0] - self[0] * v[2], - self[0] * v[1] - self[1] * v[0], + self.y() * v.z() - self.z() * v.y(), + self.z() * v.x() - self.x() * v.z(), + self.x() * v.y() - self.y() * v.x(), ]) } #[inline] - pub fn make_unit_vector(&mut self) { - let k = 1.0f64 / (self[0] * self[0] + self[1] * self[1] + self[2] * self[2]); - self[0] *= k; - self[1] *= k; - self[2] *= k; + pub fn unit_vector(self) -> Vec3 { + self / self.length() + } + + pub fn min(self, other: Self) -> Vec3 { + Self([ + self.x().min(other.x()), + self.y().min(other.y()), + self.z().min(other.z()), + ]) + } + + pub fn max(self, other: Self) -> Vec3 { + Self([ + self.x().max(other.x()), + self.y().max(other.y()), + self.z().max(other.z()), + ]) + } + + pub fn min_element(self) -> f64 { + self.x().min(self.y()).min(self.z()) + } + + pub fn max_element(self) -> f64 { + self.x().max(self.y()).max(self.z()) } #[inline] - pub fn unit_vector(&self) -> Vec3 { - let length = self.length(); - Vec3([self[0] / length, self[1] / length, self[2] / length]) - } - - #[inline] - pub fn random(rng: &mut R) -> Vec3 { - Vec3([rng.gen(), rng.gen(), rng.gen()]) + pub fn sqrt(self) -> Self { + Vec3::new(self.x().sqrt(), self.y().sqrt(), self.z().sqrt()) } } @@ -86,7 +109,7 @@ impl Add for Vec3 { type Output = Vec3; fn add(self, o: Vec3) -> Vec3 { - Vec3([self[0] + o[0], self[1] + o[1], self[2] + o[2]]) + Vec3([self.x() + o.x(), self.y() + o.y(), self.z() + o.z()]) } } @@ -102,15 +125,15 @@ impl Sub for Vec3 { type Output = Vec3; fn sub(self, o: Vec3) -> Vec3 { - Vec3([self[0] - o[0], self[1] - o[1], self[2] - o[2]]) + Vec3([self.x() - o.x(), self.y() - o.y(), self.z() - o.z()]) } } impl SubAssign for Vec3 { fn sub_assign(&mut self, o: Vec3) { - self[0] -= o[0]; - self[1] -= o[1]; - self[2] -= o[2]; + self.0[0] -= o.0[0]; + self.0[1] -= o.0[1]; + self.0[2] -= o.0[2]; } } @@ -118,37 +141,37 @@ impl Neg for Vec3 { type Output = Vec3; fn neg(self) -> Vec3 { - Vec3([-self[0], -self[1], -self[2]]) + Vec3([-self.x(), -self.y(), -self.z()]) } } impl MulAssign for Vec3 { fn mul_assign(&mut self, o: Vec3) { - self[0] *= o[0]; - self[1] *= o[1]; - self[2] *= o[2]; + self.0[0] *= o.0[0]; + self.0[1] *= o.0[1]; + self.0[2] *= o.0[2]; } } impl MulAssign for Vec3 { fn mul_assign(&mut self, o: f64) { - self[0] *= o; - self[1] *= o; - self[2] *= o; + self.0[0] *= o; + self.0[1] *= o; + self.0[2] *= o; } } impl Mul for Vec3 { type Output = Vec3; fn mul(self, o: f64) -> Vec3 { - Vec3([self[0] * o, self[1] * o, self[2] * o]) + Vec3([self.x() * o, self.y() * o, self.z() * o]) } } impl Mul for Vec3 { type Output = Vec3; fn mul(self, o: Vec3) -> Vec3 { - Vec3([self[0] * o[0], self[1] * o[1], self[2] * o[2]]) + Vec3([self.x() * o.x(), self.y() * o.y(), self.z() * o.z()]) } } @@ -156,7 +179,7 @@ impl Div for Vec3 { type Output = Vec3; fn div(self, o: Vec3) -> Vec3 { - Vec3([self[0] / o[0], self[1] / o[1], self[2] / o[2]]) + Vec3([self.x() / o.x(), self.y() / o.y(), self.z() / o.z()]) } } @@ -165,7 +188,7 @@ impl Div for Vec3 { fn div(self, o: f64) -> Vec3 { let o = 1.0 / o; - Vec3([self[0] * o, self[1] * o, self[2] * o]) + Vec3([self.x() * o, self.y() * o, self.z() * o]) } } @@ -194,6 +217,17 @@ impl IndexMut for Vec3 { impl Display for Vec3 { fn fmt(&self, f: &mut Formatter) -> FmtResult { - f.write_fmt(format_args!("{} {} {}", self[0], self[1], self[2])) + f.write_fmt(format_args!( + "{} {} {}", + self.get::(), + self.get::(), + self.get::() + )) + } +} + +impl From<(A, B, C)> for Vec3 { + fn from((x, y, z): (A, B, C)) -> Self { + Self::new(x, y, z) } }