diff --git a/src/demos/checkered_motion_blur.rs b/src/demos/checkered_motion_blur.rs new file mode 100644 index 0000000..725c6f4 --- /dev/null +++ b/src/demos/checkered_motion_blur.rs @@ -0,0 +1,119 @@ +use crate::{ + demos::{Demo, ParallelHit}, + materials::{Dielectric, Lambertian, Metal}, + shapes::{MovingSphere, Sphere}, + texture::{Checker, Solid}, + types::Vec3, + BvhNode, Camera, +}; +use rand::{rngs::SmallRng, Rng, SeedableRng}; +use std::sync::Arc; + +pub struct CheckeredMotionBlur {} + +impl Demo for CheckeredMotionBlur { + type DemoT = BvhNode>; + + fn name(&self) -> &'static str { + "checkered_motion_blur" + } + + fn world(&self) -> Self::DemoT { + let mut world: Vec> = Vec::with_capacity(500); + + let mut rng = rand::thread_rng(); + let mut rng = SmallRng::from_rng(&mut rng).unwrap(); + + world.push(Arc::new(Sphere::new( + Vec3::new(0.0, -1000.0, 0.0), + 1000.0, + Lambertian::new(Checker::new( + Solid::new(Vec3::new(0.2, 0.3, 0.1)), + Solid::new(Vec3::new(0.9, 0.9, 0.9)), + )), + ))); + + let radius = 0.2; + let l = Vec3::new(4.0, 0.2, 0.0); + + for a in -10..10 { + let a = a as f64; + for b in -10..10 { + let b = b as f64; + let choose_material_probability = rng.gen::(); + let center = Vec3::new(a + 0.9 * rng.gen::(), 0.2, b + 0.9 * rng.gen::()); + + if (center - l).length() > 0.9 { + if choose_material_probability < 0.8 { + // diffuse material + world.push(Arc::new(MovingSphere::new( + center, + center + Vec3::new(0.0, 0.5 * rng.gen::(), 0.0), + 0.0, + 1.0, + radius, + Lambertian::new(Solid::new(Vec3::new( + rng.gen::() * rng.gen::(), + rng.gen::() * rng.gen::(), + rng.gen::() * rng.gen::(), + ))), + ))); + } else if choose_material_probability < 0.95 { + // metal material + world.push(Arc::new(Sphere::new( + center, + radius, + Metal::with_fuzz( + Vec3::new( + (1.0 + rng.gen::()) * 0.5, + (1.0 + rng.gen::()) * 0.5, + (1.0 + rng.gen::()) * 0.5, + ), + 0.5 * rng.gen::(), + ), + ))); + } else { + // glass material + world.push(Arc::new(Sphere::new(center, radius, Dielectric::new(1.5)))); + } + } + } + } + + world.push(Arc::new(Sphere::new( + Vec3::new(0.0, 1.0, 0.0), + 1.0, + Dielectric::new(1.5), + ))); + world.push(Arc::new(Sphere::new( + Vec3::new(-4.0, 1.0, 0.0), + 1.0, + Lambertian::new(Solid::new(Vec3::new(0.4, 0.2, 0.1))), + ))); + world.push(Arc::new(Sphere::new( + Vec3::new(4.0, 1.0, 0.0), + 1.0, + Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0), + ))); + + BvhNode::new(&mut rng, &mut world, 0.0, 1.0) + } + + fn camera(&self, aspect_ratio: f64) -> Camera { + let lookfrom = Vec3::new(13.0, 2.0, 3.0); + let lookat = Vec3::new(0.0, 0.0, 0.0); + let aperture = 0.1; + let focus_distance = 10.0; + Camera::new( + lookfrom, + lookat, + Vec3::new(0.0, 1.0, 0.0), + 20.0, + aspect_ratio, + aperture, + focus_distance, + 0.0, + 1.0, + ) + } +} diff --git a/src/demo.rs b/src/demos/mod.rs similarity index 58% rename from src/demo.rs rename to src/demos/mod.rs index 4b29897..a0ea916 100644 --- a/src/demo.rs +++ b/src/demos/mod.rs @@ -1,9 +1,6 @@ use crate::{ - materials::{Dielectric, Lambertian, Metal}, - shapes::{MovingSphere, Sphere}, - texture::{Checker, Solid}, types::{Ray, Vec3}, - BvhNode, Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION, + Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION, }; use rand::{rngs::SmallRng, Rng, SeedableRng}; use rayon::prelude::*; @@ -14,6 +11,12 @@ use std::{ sync::{Arc, Mutex}, }; +mod checkered_motion_blur; +mod two_spheres; + +pub use checkered_motion_blur::CheckeredMotionBlur; +pub use two_spheres::TwoSpheres; + #[derive(Debug)] pub struct Chunk { num: usize, @@ -39,113 +42,16 @@ impl Display for Chunk { pub trait ParallelHit: Hitable + Send + Sync {} impl ParallelHit for T {} -pub struct Demo; +pub trait Demo: Send + Sync { + type DemoT: Hitable + Send + Sync; -impl Demo { - pub fn name(&self) -> &'static str { - "motion_blur" - } + fn name(&self) -> &'static str; - fn world(&self) -> impl Hitable { - let mut world: Vec> = Vec::with_capacity(500); + fn world(&self) -> Self::DemoT; - let mut rng = rand::thread_rng(); - let mut rng = SmallRng::from_rng(&mut rng).unwrap(); + fn camera(&self, aspect_ratio: f64) -> Camera; - world.push(Arc::new(Sphere::new( - Vec3::new(0.0, -1000.0, 0.0), - 1000.0, - Lambertian::new(Checker::new( - Solid::new(Vec3::new(0.2, 0.3, 0.1)), - Solid::new(Vec3::new(0.9, 0.9, 0.9)), - )), - ))); - - let radius = 0.2; - let l = Vec3::new(4.0, 0.2, 0.0); - - for a in -10..10 { - let a = a as f64; - for b in -10..10 { - let b = b as f64; - let choose_material_probability = rng.gen::(); - let center = Vec3::new(a + 0.9 * rng.gen::(), 0.2, b + 0.9 * rng.gen::()); - - if (center - l).length() > 0.9 { - if choose_material_probability < 0.8 { - // diffuse material - world.push(Arc::new(MovingSphere::new( - center, - center + Vec3::new(0.0, 0.5 * rng.gen::(), 0.0), - 0.0, - 1.0, - radius, - Lambertian::new(Solid::new(Vec3::new( - rng.gen::() * rng.gen::(), - rng.gen::() * rng.gen::(), - rng.gen::() * rng.gen::(), - ))), - ))); - } else if choose_material_probability < 0.95 { - // metal material - world.push(Arc::new(Sphere::new( - center, - radius, - Metal::with_fuzz( - Vec3::new( - (1.0 + rng.gen::()) * 0.5, - (1.0 + rng.gen::()) * 0.5, - (1.0 + rng.gen::()) * 0.5, - ), - 0.5 * rng.gen::(), - ), - ))); - } else { - // glass material - world.push(Arc::new(Sphere::new(center, radius, Dielectric::new(1.5)))); - } - } - } - } - - world.push(Arc::new(Sphere::new( - Vec3::new(0.0, 1.0, 0.0), - 1.0, - Dielectric::new(1.5), - ))); - world.push(Arc::new(Sphere::new( - Vec3::new(-4.0, 1.0, 0.0), - 1.0, - Lambertian::new(Solid::new(Vec3::new(0.4, 0.2, 0.1))), - ))); - world.push(Arc::new(Sphere::new( - Vec3::new(4.0, 1.0, 0.0), - 1.0, - Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0), - ))); - - BvhNode::new(&mut rng, &mut world, 0.0, 1.0) - } - - fn camera(&self, aspect_ratio: f64) -> Camera { - let lookfrom = Vec3::new(13.0, 2.0, 3.0); - let lookat = Vec3::new(0.0, 0.0, 0.0); - let aperture = 0.1; - let focus_distance = 10.0; - Camera::new( - lookfrom, - lookat, - Vec3::new(0.0, 1.0, 0.0), - 20.0, - aspect_ratio, - aperture, - focus_distance, - 0.0, - 1.0, - ) - } - - fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &impl Hitable, samples: u8) { + fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &Self::DemoT, samples: u8) { let &mut Chunk { num: _, x, @@ -180,7 +86,7 @@ impl Demo { }); } - pub fn render(&self, buf: &mut Vec, x: usize, y: usize, samples: u8) { + fn render(&self, buf: &mut Vec, x: usize, y: usize, samples: u8) { let world = self.world(); let delta_x = x / VERTICAL_PARTITION; let delta_y = y / HORIZONTAL_PARTITION; @@ -253,10 +159,16 @@ impl Demo { } } - pub fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize) { + fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize, samples: u8) { let header = format!("P3\n{} {}\n255\n", width, height); - let mut file = match File::create(&format!("{}-{}x{}.ppm", self.name(), width, height)) { + let mut file = match File::create(&format!( + "{}-{}x{}_{}.ppm", + self.name(), + width, + height, + samples, + )) { Ok(file) => file, Err(e) => panic!("couldn't create {}: {}", self.name(), e), }; diff --git a/src/demos/two_spheres.rs b/src/demos/two_spheres.rs new file mode 100644 index 0000000..1baec28 --- /dev/null +++ b/src/demos/two_spheres.rs @@ -0,0 +1,67 @@ +use std::sync::Arc; + +use rand::{prelude::SmallRng, SeedableRng}; + +use crate::{ + demos::{Demo, ParallelHit}, + materials::Lambertian, + shapes::Sphere, + texture::{Checker, Solid}, + types::Vec3, + BvhNode, Camera, +}; + +pub struct TwoSpheres {} + +impl Demo for TwoSpheres { + type DemoT = BvhNode>; + + fn name(&self) -> &'static str { + "two_checkered_sphere" + } + + fn world(&self) -> Self::DemoT { + let mut world: Vec> = Vec::with_capacity(500); + + let mut rng = rand::thread_rng(); + let mut rng = SmallRng::from_rng(&mut rng).unwrap(); + + world.push(Arc::new(Sphere::new( + Vec3::new(0.0, -10.0, 0.0), + 10.0, + Lambertian::new(Checker::new( + Solid::new(Vec3::new(0.2, 0.3, 0.1)), + Solid::new(Vec3::new(0.9, 0.9, 0.9)), + )), + ))); + + world.push(Arc::new(Sphere::new( + Vec3::new(0.0, 10.0, 0.0), + 10.0, + Lambertian::new(Checker::new( + Solid::new(Vec3::new(0.2, 0.3, 0.1)), + Solid::new(Vec3::new(0.9, 0.9, 0.9)), + )), + ))); + + BvhNode::new(&mut rng, &mut world, 0.0, 1.0) + } + + fn camera(&self, aspect_ratio: f64) -> Camera { + let lookfrom = Vec3::new(13.0, 2.0, 3.0); + let lookat = Vec3::new(0.0, 0.0, 0.0); + let aperture = 0.1; + let focus_distance = 10.0; + Camera::new( + lookfrom, + lookat, + Vec3::new(0.0, 1.0, 0.0), + 20.0, + aspect_ratio, + aperture, + focus_distance, + 0.0, + 1.0, + ) + } +} diff --git a/src/hitable_list.rs b/src/hitable_list.rs index 26fd415..b8ff514 100644 --- a/src/hitable_list.rs +++ b/src/hitable_list.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{demo::ParallelHit, types::Ray, Aabb, HitRecord, Hitable}; +use crate::{demos::ParallelHit, types::Ray, Aabb, HitRecord, Hitable}; pub struct HitableList { pub list: Vec>, diff --git a/src/main.rs b/src/main.rs index bf3877d..f1519d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod aabb; mod bvh; mod camera; -mod demo; +mod demos; mod hitable; mod hitable_list; mod materials; @@ -11,17 +11,19 @@ mod shapes; mod texture; mod types; -pub use camera::Camera; - 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; -use std::time::Instant; pub use texture::Texture; -const NUM_SAMPLES: u8 = 20; +use demos::Demo; + +use std::time::Instant; + +const NUM_SAMPLES: u8 = 25; const VERTICAL_PARTITION: usize = 12; const HORIZONTAL_PARTITION: usize = 12; const WIDTH: usize = 1920; @@ -33,16 +35,18 @@ fn main() -> Result<(), String> { #[cfg(feature = "gui")] fn run(mut width: usize, mut height: usize) -> Result<(), String> { + use demos::ParallelHit; use sdl2::{ event::{Event, WindowEvent}, keyboard::Keycode, pixels::PixelFormatEnum, }; + use std::sync::Arc; let sdl_ctx = sdl2::init()?; let video_subsys = sdl_ctx.video()?; let window = video_subsys - .window("Ray tracing in a weekend", width as u32, height as u32) + .window("Ray tracing the Next Week", width as u32, height as u32) .position_centered() .build() .map_err(|e| e.to_string())?; @@ -63,9 +67,10 @@ 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 active_demo = demo::Demo; - + let mut active_demo: &dyn Demo>> = + &demos::CheckeredMotionBlur {}; let mut should_update = true; + loop { for event in event_pump.poll_iter() { match event { @@ -77,9 +82,17 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { Event::KeyUp { keycode, .. } => { match keycode { Some(Keycode::S) => { - active_demo.save_as_ppm(&buffer, width, height); + active_demo.save_as_ppm(&buffer, width, height, NUM_SAMPLES); should_update = false; } + Some(Keycode::Num1) => { + active_demo = &demos::CheckeredMotionBlur {}; + should_update = true; + } + Some(Keycode::Num2) => { + active_demo = &demos::TwoSpheres {}; + should_update = true; + } None => unreachable!(), _ => (), }; @@ -107,7 +120,6 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { active_demo.name(), now.elapsed().as_secs_f64() ); - texture.update(None, &buffer, width * 4).unwrap(); canvas.copy(&texture, None, None).unwrap(); canvas.present(); @@ -118,9 +130,16 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { #[cfg(not(feature = "gui"))] fn run(width: usize, height: usize) -> Result<(), String> { - let mut buffer = vec![0; width * height * 4]; + run_and_save_demo(demos::CheckeredMotionBlur {}, width, height); - let demo = demo::Demo; + run_and_save_demo(demos::TwoSpheres {}, width, height); + + Ok(()) +} + +#[cfg(not(feature = "gui"))] +fn run_and_save_demo(demo: impl Demo, width: usize, height: usize) { + let mut buffer = vec![0; width * height * 4]; println!( "Starting {} at {}x{} with {} samples", @@ -129,6 +148,7 @@ fn run(width: usize, height: usize) -> Result<(), String> { height, NUM_SAMPLES ); + let now = Instant::now(); demo.render(&mut buffer, width, height, NUM_SAMPLES); println!( @@ -137,7 +157,5 @@ fn run(width: usize, height: usize) -> Result<(), String> { now.elapsed().as_secs_f64() ); - demo.save_as_ppm(&buffer, width, height); - - Ok(()) + demo.save_as_ppm(&buffer, width, height, NUM_SAMPLES); }