diff --git a/src/demos/cornell_smoke_and_fog.rs b/src/demos/cornell_smoke_and_fog.rs new file mode 100644 index 0000000..a6d3c68 --- /dev/null +++ b/src/demos/cornell_smoke_and_fog.rs @@ -0,0 +1,119 @@ +use std::sync::Arc; + +use rand::{prelude::SmallRng, SeedableRng}; + +use crate::{ + demos::{Demo, ParallelHit}, + hitable::{ + shapes::{Cuboid, RectBuilder}, + volume::ConstantMedium, + Hitable, + }, + materials::{DiffuseLight, Isotropic, Lambertian, MaterialBuilder}, + texture::Solid, + types::Vec3, + BvhNode, Camera, +}; + +pub struct CornellSmokeAndFog {} + +impl Demo for CornellSmokeAndFog { + type DemoT = BvhNode>; + + fn name(&self) -> &'static str { + "cornell_smoke_and_fog" + } + + 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(7.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(113.0..=443.0) + .z(127.0..=432.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(ConstantMedium::new( + Cuboid::new(Vec3::splat(0.0), Vec3::new(165.0, 330.0, 165.0), white) + .rotate_y(15.0) + .translate(Vec3::new(265.0, 0.0, 295.0)), + Isotropic::new(Solid::new(Vec3::splat(0.0))), + 0.01, + ))); + world.push(Arc::new(ConstantMedium::new( + Cuboid::new(Vec3::splat(0.0), Vec3::splat(165.0), white) + .rotate_y(-18.0) + .translate(Vec3::new(130.0, 0.0, 65.0)), + Isotropic::new(Solid::new(Vec3::splat(1.0))), + 0.01, + ))); + + 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/mod.rs b/src/demos/mod.rs index 78c55f4..30620a5 100644 --- a/src/demos/mod.rs +++ b/src/demos/mod.rs @@ -13,6 +13,7 @@ use std::{ }; mod checkered_motion_blur; +mod cornell_smoke_and_fog; mod image_texture; mod instances; mod perlin_noise_ball; @@ -20,6 +21,7 @@ mod simple_light; mod two_spheres; pub use checkered_motion_blur::CheckeredMotionBlur; +pub use cornell_smoke_and_fog::CornellSmokeAndFog; pub use image_texture::ImageTextureDemo; pub use instances::Instances; pub use perlin_noise_ball::PerlinNoiseBall; diff --git a/src/hitable/mod.rs b/src/hitable/mod.rs index d1ab0e3..efd0bb1 100644 --- a/src/hitable/mod.rs +++ b/src/hitable/mod.rs @@ -3,6 +3,7 @@ pub mod hitable_list; mod rotate; pub mod shapes; mod translate; +pub mod volume; pub use bvh::*; pub use translate::*; diff --git a/src/hitable/rotate.rs b/src/hitable/rotate.rs index a7b8b73..60deb4d 100644 --- a/src/hitable/rotate.rs +++ b/src/hitable/rotate.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use crate::{ hitable::{HitRecord, Hitable}, types::{Ray, Vec3}, - Aabb, Dimension, X, Y, Z, + Aabb, Dimension, }; pub struct Rotate { @@ -28,7 +28,7 @@ where let cos_theta = radians.cos(); let mut min = Vec3::splat(f64::MAX); - let mut max = Vec3::splat(-f64::MAX); + let mut max = Vec3::splat(f64::MIN); let bbox = if let Some(bbox) = object.bounding_box(0.0, 1.0) { for i in 0..2 { diff --git a/src/hitable/volume/constant_medium.rs b/src/hitable/volume/constant_medium.rs new file mode 100644 index 0000000..ecf0c98 --- /dev/null +++ b/src/hitable/volume/constant_medium.rs @@ -0,0 +1,63 @@ +use crate::{ + hitable::{HitRecord, Hitable}, + types::{Ray, Vec3}, + Aabb, Material, +}; + +pub struct ConstantMedium { + neg_inv_density: f64, + boundary: A, + phase_function: B, +} + +impl ConstantMedium { + pub fn new(boundary: A, phase_function: B, d: f64) -> Self { + Self { + boundary, + phase_function, + neg_inv_density: -1.0 / d, + } + } +} + +impl Hitable for ConstantMedium { + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { + let mut hit1 = self.boundary.hit(ray, f64::MIN, f64::MAX)?; + let mut hit2 = self.boundary.hit(ray, hit1.t + 0.0001, f64::MAX)?; + + hit1.t = hit1.t.max(t_min); + hit2.t = hit2.t.min(t_max); + + if hit1.t >= hit2.t { + return None; + }; + + hit1.t = hit1.t.max(0.0); + + let ray_length = ray.direction.length(); + let distance_inside_boundary = (hit2.t - hit1.t) * ray_length; + let hit_distance = self.neg_inv_density * rand::random::().ln(); + + if hit_distance > distance_inside_boundary { + return None; + } + + let t = hit1.t + hit_distance / ray_length; + + Some(HitRecord { + t, + p: ray.point_at_parameter(t), + material: &self.phase_function, + u: 0.0, + v: 0.0, + + // Arbitrary + front_face: true, + normal: Vec3::new(1.0, 0.0, 0.0), + }) + } + + fn bounding_box(&self, t0: f64, t1: f64) -> Option { + self.boundary.bounding_box(t0, t1) + } +} diff --git a/src/hitable/volume/mod.rs b/src/hitable/volume/mod.rs new file mode 100644 index 0000000..e97ff87 --- /dev/null +++ b/src/hitable/volume/mod.rs @@ -0,0 +1,3 @@ +mod constant_medium; + +pub use constant_medium::ConstantMedium; diff --git a/src/main.rs b/src/main.rs index e4e60bb..be187a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,8 @@ 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::Instances {}; + let mut active_demo: &dyn Demo>> = + &demos::CornellSmokeAndFog {}; let mut should_update = true; loop { @@ -107,6 +108,10 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { active_demo = &demos::Instances {}; should_update = true; } + Some(Keycode::Num7) => { + active_demo = &demos::CornellSmokeAndFog {}; + should_update = true; + } None => unreachable!(), _ => (), }; diff --git a/src/materials/isotropic.rs b/src/materials/isotropic.rs new file mode 100644 index 0000000..8440c75 --- /dev/null +++ b/src/materials/isotropic.rs @@ -0,0 +1,31 @@ +use rand::prelude::SmallRng; + +use crate::{ + hitable::HitRecord, + materials::random_point_in_unit_sphere, + types::{Ray, Vec3}, + Material, Texture, +}; + +pub struct Isotropic { + texture: T, +} + +impl Isotropic { + pub fn new(texture: T) -> Self { + Self { texture } + } +} + +impl Material for Isotropic { + fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option) { + ( + self.texture.value(hit_rec.u, hit_rec.v, hit_rec.p), + Some(Ray::new( + hit_rec.p, + random_point_in_unit_sphere(rng), + ray.time(), + )), + ) + } +} diff --git a/src/materials/mod.rs b/src/materials/mod.rs index 871400a..9904cd0 100644 --- a/src/materials/mod.rs +++ b/src/materials/mod.rs @@ -1,10 +1,12 @@ mod dielectric; mod diffuse_light; +mod isotropic; mod lambertian; mod metal; pub use dielectric::Dielectric; pub use diffuse_light::DiffuseLight; +pub use isotropic::Isotropic; pub use lambertian::Lambertian; pub use metal::Metal; use rand::{prelude::SmallRng, Rng};