diff --git a/Cargo.lock b/Cargo.lock index 2bd1a3c..655eb86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,9 +99,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" [[package]] name = "maybe-uninit" @@ -111,11 +111,11 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memoffset" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" dependencies = [ - "rustc_version", + "autocfg", ] [[package]] @@ -145,6 +145,7 @@ dependencies = [ "rand_chacha", "rand_core", "rand_hc", + "rand_pcg", ] [[package]] @@ -175,6 +176,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.3.0" @@ -208,15 +218,6 @@ dependencies = [ "sdl2", ] -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -245,21 +246,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 9f661c6..8ea7aac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,11 @@ version = "0.1.0" authors = ["ishanjain28 "] edition = "2018" + +[features] +gui = ["sdl2"] + [dependencies] -sdl2 = "0.33.0" -rand = "0.7.3" +sdl2 = { version = "0.33.0", optional = true } +rand = { version = "0.7.3", features = ["small_rng"] } rayon = "1.3.0" diff --git a/src/camera.rs b/src/camera.rs index bed30fe..8901532 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -8,15 +8,15 @@ pub struct Camera { horizontal: Vec3, vertical: Vec3, lower_left_corner: Vec3, - lens_radius: f32, + lens_radius: f64, // position vectors u: Vec3, v: Vec3, w: Vec3, - shutter_open: f32, - shutter_close: f32, + shutter_open: f64, + shutter_close: f64, } impl Camera { @@ -29,15 +29,15 @@ impl Camera { look_from: Vec3, look_at: Vec3, v_up: Vec3, - vertical_fov: f32, - aspect: f32, - aperture: f32, - focus_distance: f32, - shutter_open: f32, - shutter_close: f32, + vertical_fov: f64, + aspect: f64, + aperture: f64, + focus_distance: f64, + shutter_open: f64, + shutter_close: f64, ) -> Self { // convert degree to radian - let angle = vertical_fov * std::f32::consts::PI / 180.0; + let angle = vertical_fov * std::f64::consts::PI / 180.0; let half_height = (angle / 2.0).tan(); let half_width = aspect * half_height; @@ -68,11 +68,10 @@ impl Camera { } } - pub fn get_ray(&self, u: f32, v: f32) -> Ray { - let mut rng = rand::thread_rng(); - let rd = random_in_unit_disk(&mut rng) * self.lens_radius; + pub fn get_ray(&self, u: f64, v: f64, rng: &mut R) -> Ray { + let rd = random_in_unit_disk(rng) * self.lens_radius; let offset = self.u * rd.x() + self.v * rd.y(); - let time = self.shutter_open + rng.gen::() * (self.shutter_close - self.shutter_open); + let time = self.shutter_open + rng.gen::() * (self.shutter_close - self.shutter_open); Ray::new( self.origin + offset, self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin - offset, @@ -81,11 +80,11 @@ impl Camera { } } -fn random_in_unit_disk(rng: &mut rand::rngs::ThreadRng) -> Vec3 { - let mut p = Vec3::new(rng.gen::(), rng.gen::(), 0.0) * 2.0 - Vec3::new(1.0, 1.0, 0.0); +fn random_in_unit_disk(rng: &mut R) -> Vec3 { + let mut p = Vec3::new(rng.gen::(), rng.gen::(), 0.0) * 2.0 - Vec3::new(1.0, 1.0, 0.0); while p.dot(&p) >= 1.0 { - p = Vec3::new(rng.gen::(), rng.gen::(), 0.0) * 2.0 - Vec3::new(1.0, 0.0, 0.0); + p = Vec3::new(rng.gen::(), rng.gen::(), 0.0) * 2.0 - Vec3::new(1.0, 0.0, 0.0); } p } diff --git a/src/demo.rs b/src/demo.rs new file mode 100644 index 0000000..3ae7db7 --- /dev/null +++ b/src/demo.rs @@ -0,0 +1,290 @@ +use { + crate::{ + types::{ + material::{Dielectric, Lambertian, Metal}, + Hitable, HitableList, MovingSphere, Ray, Sphere, Vec3, + }, + Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION, + }, + rand::{rngs::SmallRng, Rng, SeedableRng}, + rayon::prelude::*, + std::{ + fmt::{Display, Formatter, Result as FmtResult}, + fs::File, + io::Write, + sync::{Arc, Mutex}, + }, +}; + +#[derive(Debug)] +pub struct Chunk { + num: usize, + x: usize, + y: usize, + nx: usize, + ny: usize, + start_x: usize, + start_y: usize, + buffer: Vec, +} + +impl Display for Chunk { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!( + f, + "Chunk #{}: Start X = {} Start Y = {} Size X = {} Size = {}", + self.num, self.start_x, self.start_y, self.nx, self.ny + ) + } +} + +pub struct Demo; + +impl Demo { + pub fn name(&self) -> &'static str { + "motion_blur" + } + + fn world(&self) -> HitableList { + let mut world = HitableList { + list: Vec::with_capacity(500), + }; + + let mut rng = rand::thread_rng(); + let mut rng = SmallRng::from_rng(&mut rng).unwrap(); + + world.push(Box::new(Sphere::new( + Vec3::new(0.0, -1000.0, 0.0), + 1000.0, + Lambertian::new(Vec3::new(0.5, 0.5, 0.5)), + ))); + + 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(Box::new(MovingSphere::new( + center, + center + Vec3::new(0.0, 0.5 * rng.gen::(), 0.0), + 0.0, + 1.0, + radius, + Lambertian::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(Box::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(Box::new(Sphere::new(center, radius, Dielectric::new(1.5)))); + } + } + } + } + + world.push(Box::new(Sphere::new( + Vec3::new(0.0, 1.0, 0.0), + 1.0, + Dielectric::new(1.5), + ))); + world.push(Box::new(Sphere::new( + Vec3::new(-4.0, 1.0, 0.0), + 1.0, + Lambertian::new(Vec3::new(0.4, 0.2, 0.1)), + ))); + world.push(Box::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), + ))); + + world + } + + 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: &HitableList, samples: u8) { + let &mut Chunk { + num: _, + x, + y, + nx, + ny, + start_x, + start_y, + ref mut buffer, + } = chunk; + let mut offset = 0; + let mut rng = rand::thread_rng(); + let mut rng = SmallRng::from_rng(&mut rng).unwrap(); + + assert!(buffer.len() >= nx * ny * 4); + + (start_y..start_y + ny).for_each(|j| { + (start_x..start_x + nx).for_each(|i| { + let mut color = Vec3::new(0.0, 0.0, 0.0); + for _s in 0..samples { + let u = (i as f64 + rng.gen::()) / x as f64; + let v = (j as f64 + rng.gen::()) / y as f64; + + let ray = camera.get_ray(u, v, &mut rng); + color += calc_color(ray, &world, 0, &mut rng); + } + + color /= samples as f64; + self.update_rgb(buffer, color, offset); + offset += 4; + }); + }); + } + + pub 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; + let remx = x % VERTICAL_PARTITION; + let remy = y % HORIZONTAL_PARTITION; + + // There can be tiny error here if the canvas height/width is not perfectly divisible + // by vertical/horizontal partitions in the chunks around the edges + // but umm, i'll just ignore those for now. + let camera = self.camera(delta_x as f64 / delta_y as f64); + let buf = Arc::new(Mutex::new(buf)); + + (0..VERTICAL_PARTITION).into_par_iter().for_each(|j| { + let buf = buf.clone(); + (0..HORIZONTAL_PARTITION).into_par_iter().for_each(|i| { + let mut nx = delta_x; + let mut ny = delta_y; + let start_y = j * ny; + let start_x = i * nx; + + match (i + 1, j + 1) { + (HORIZONTAL_PARTITION, VERTICAL_PARTITION) => { + nx += remx; + ny += remy; + } + (HORIZONTAL_PARTITION, _) => nx += remx, + (_, VERTICAL_PARTITION) => ny += remy, + _ => (), + }; + + let mut chunk = Chunk { + num: j * HORIZONTAL_PARTITION + i, + x, + y, + nx, + ny, + start_x, + start_y, + buffer: vec![0; nx * ny * 4], + }; + + println!("{}", chunk); + self.render_chunk(&mut chunk, &camera, &world, samples); + + let mut buf = buf.lock().unwrap(); + let mut temp_offset = 0; + for j in start_y..start_y + ny { + let real_offset = ((y - j - 1) * x + start_x) * 4; + + buf[real_offset..real_offset + nx * 4] + .copy_from_slice(&chunk.buffer[temp_offset..temp_offset + nx * 4]); + + temp_offset += nx * 4; + } + println!("Rendered {}", chunk); + }); + }); + } + + #[inline] + fn update_rgb(&self, buffer: &mut [u8], color: Vec3, offset: usize) { + if let Some(pos) = buffer.get_mut(offset) { + *pos = (255.99 * color.r().sqrt()) as u8; + } + if let Some(pos) = buffer.get_mut(offset + 1) { + *pos = (255.99 * color.g().sqrt()) as u8; + } + if let Some(pos) = buffer.get_mut(offset + 2) { + *pos = (255.99 * color.b().sqrt()) as u8; + } + } + + pub fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize) { + let header = format!("P3\n{} {}\n255\n", width, height); + + let mut file = match File::create(&format!("{}-{}x{}.ppm", self.name(), width, height)) { + Ok(file) => file, + Err(e) => panic!("couldn't create {}: {}", self.name(), e), + }; + file.write_all(header.as_bytes()) + .expect("error in writing file header"); + + for i in buf.chunks(4) { + match file.write_all(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) { + Ok(_) => (), + Err(e) => panic!("couldn't write to {}: {}", self.name(), e), + } + } + } +} + +fn calc_color(ray: Ray, world: &HitableList, depth: u32, rng: &mut SmallRng) -> Vec3 { + if let Some(hit_rec) = world.hit(&ray, 0.001, std::f64::MAX) { + if depth >= 50 { + Vec3::new(0.0, 0.0, 0.0) + } else { + let material = hit_rec.material; + if let (attenuation, Some(scattered_ray)) = material.scatter(&ray, &hit_rec, rng) { + calc_color(scattered_ray, &world, depth + 1, rng) * attenuation + } else { + Vec3::new(0.0, 0.0, 0.0) + } + } + } else { + let unit_direction = ray.direction().unit_vector(); + let t = 0.5 * (unit_direction.y() + 1.0); + Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t + } +} diff --git a/src/demos/mod.rs b/src/demos/mod.rs deleted file mode 100644 index 6caa751..0000000 --- a/src/demos/mod.rs +++ /dev/null @@ -1,117 +0,0 @@ -use { - crate::{ - types::{HitableList, Vec3}, - Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION, - }, - rayon::prelude::*, - std::{ - fs::File, - io::Write, - sync::{Arc, Mutex}, - }, -}; - -mod motion_blur; - -pub use motion_blur::MotionBlur; - -pub struct Chunk { - x: usize, - y: usize, - nx: usize, - ny: usize, - start_x: usize, - start_y: usize, - buffer: Vec, -} - -pub trait Demo: std::marker::Sync { - fn render(&self, buf: &mut Vec, width: usize, height: usize, samples: u8) { - let nx = width / VERTICAL_PARTITION; - let ny = height / HORIZONTAL_PARTITION; - let world = self.world(); - let camera = self.camera(nx as f32 / ny as f32); - - let buf = Arc::new(Mutex::new(buf)); - - (0..VERTICAL_PARTITION).into_par_iter().for_each(|j| { - let buf = buf.clone(); - (0..HORIZONTAL_PARTITION).into_par_iter().for_each(|i| { - let start_y = j * ny; - let start_x = i * nx; - let x = width; - let y = height; - let mut chunk = Chunk { - x, - y, - nx, - ny, - start_x, - start_y, - buffer: vec![0; nx * ny * 4], - }; - self.render_chunk(&mut chunk, camera.as_ref(), world.as_ref(), samples); - - let mut buf = buf.lock().unwrap(); - - let mut temp_offset = 0; - for j in start_y..start_y + ny { - let real_offset = ((y - j - 1) * x + start_x) * 4; - - buf[real_offset..real_offset + nx * 4] - .copy_from_slice(&chunk.buffer[temp_offset..temp_offset + nx * 4]); - - temp_offset += nx * 4; - } - }) - }); - } - - fn world(&self) -> Option { - None - } - - fn camera(&self, aspect_ratio: f32) -> Option { - let lookfrom = Vec3::new(0.0, 0.0, 0.0); - let lookat = Vec3::new(0.0, 0.0, -1.0); - Some(Camera::new( - lookfrom, - lookat, - Vec3::new(0.0, 1.0, 0.0), - 90.0, - aspect_ratio, - 0.0, //aperture - 1.0, // focus_distance - 0.0, // shutter open time - 1.0, // shutter close time - )) - } - - fn render_chunk( - &self, - chunk: &mut Chunk, - camera: Option<&Camera>, - world: Option<&HitableList>, - samples: u8, - ); - - fn name(&self) -> &'static str; - - fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize) { - let header = format!("P3\n{} {}\n255\n", width, height); - - let mut file = match File::create(&format!("{}-{}x{}.ppm", self.name(), width, height)) { - Ok(file) => file, - Err(e) => panic!("couldn't create {}: {}", self.name(), e), - }; - file.write_all(header.as_bytes()) - .expect("error in writing file header"); - - for i in buf.chunks(4) { - match file.write_all(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) { - Ok(_) => (), - Err(e) => panic!("couldn't write to {}: {}", self.name(), e), - } - } - } -} diff --git a/src/demos/motion_blur.rs b/src/demos/motion_blur.rs deleted file mode 100644 index 8cf9afa..0000000 --- a/src/demos/motion_blur.rs +++ /dev/null @@ -1,185 +0,0 @@ -use { - crate::{ - demos::{Chunk, Demo}, - types::{ - material::{Dielectric, Lambertian, Metal}, - Hitable, HitableList, MovingSphere, Ray, Sphere, Vec3, - }, - Camera, - }, - rand::Rng, -}; - -const RANGE: i32 = 10; - -pub struct MotionBlur; - -impl Demo for MotionBlur { - fn name(&self) -> &'static str { - "motion_blur" - } - - fn world(&self) -> Option { - let mut world = HitableList { - list: Vec::with_capacity(500), - }; - - world.push(Box::new(Sphere::new( - Vec3::new(0.0, -1000.0, 0.0), - 1000.0, - Box::new(Lambertian::new(Vec3::new(0.5, 0.5, 0.5))), - ))); - - let mut rng = rand::thread_rng(); - let radius = 0.2; - let l = Vec3::new(4.0, 0.2, 0.0); - - for a in -RANGE..RANGE { - let a = a as f32; - for b in -RANGE..RANGE { - let b = b as f32; - 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(Box::new(MovingSphere::new( - center, - center + Vec3::new(0.0, 0.5 * rng.gen::(), 0.0), - 0.0, - 1.0, - radius, - Box::new(Lambertian::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(Box::new(Sphere::new( - center, - radius, - Box::new(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(Box::new(Sphere::new( - center, - radius, - Box::new(Dielectric::new(1.5)), - ))); - } - } - } - } - - world.push(Box::new(Sphere::new( - Vec3::new(0.0, 1.0, 0.0), - 1.0, - Box::new(Dielectric::new(1.5)), - ))); - world.push(Box::new(Sphere::new( - Vec3::new(-4.0, 1.0, 0.0), - 1.0, - Box::new(Lambertian::new(Vec3::new(0.4, 0.2, 0.1))), - ))); - world.push(Box::new(Sphere::new( - Vec3::new(4.0, 1.0, 0.0), - 1.0, - Box::new(Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0)), - ))); - - Some(world) - } - - fn camera(&self, aspect_ratio: f32) -> Option { - 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; - let camera = Camera::new( - lookfrom, - lookat, - Vec3::new(0.0, 1.0, 0.0), - 20.0, - aspect_ratio, - aperture, - focus_distance, - 0.0, - 1.0, - ); - Some(camera) - } - - fn render_chunk( - &self, - chunk: &mut Chunk, - camera: Option<&Camera>, - world: Option<&HitableList>, - samples: u8, - ) { - let &mut Chunk { - x, - y, - nx, - ny, - start_x, - start_y, - ref mut buffer, - } = chunk; - let camera = camera.unwrap(); - let world = world.unwrap(); - - let mut rng = rand::thread_rng(); - let mut offset = 0; - - for j in start_y..start_y + ny { - for i in start_x..start_x + nx { - let mut color = Vec3::new(0.0, 0.0, 0.0); - for _s in 0..samples { - let u = (i as f32 + rng.gen::()) / x as f32; - let v = (j as f32 + rng.gen::()) / y as f32; - - let ray = camera.get_ray(u, v); - color += calc_color(ray, &world, 0); - } - - color /= samples as f32; - - // gamma 2 corrected - buffer[offset] = (255.99 * color.r().sqrt()) as u8; - buffer[offset + 1] = (255.99 * color.g().sqrt()) as u8; - buffer[offset + 2] = (255.99 * color.b().sqrt()) as u8; - offset += 4; - } - } - } -} - -fn calc_color(ray: Ray, world: &HitableList, depth: u32) -> Vec3 { - if let Some(hit_rec) = world.hit(&ray, 0.001, std::f32::MAX) { - if depth >= 50 { - Vec3::new(0.0, 0.0, 0.0) - } else { - let material = hit_rec.material; - if let (attenuation, Some(scattered_ray)) = material.scatter(&ray, &hit_rec) { - calc_color(scattered_ray, &world, depth + 1) * attenuation - } else { - Vec3::new(0.0, 0.0, 0.0) - } - } - } else { - let unit_direction = ray.direction().unit_vector(); - let t = 0.5 * (unit_direction.y() + 1.0); - Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t - } -} diff --git a/src/main.rs b/src/main.rs index d52c90b..b0259b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,30 +3,33 @@ extern crate test; mod camera; -mod demos; +mod demo; mod types; pub use camera::Camera; -use { - demos::Demo, - sdl2::{ - event::{Event, WindowEvent}, - keyboard::Keycode, - pixels::PixelFormatEnum, - }, - std::time::Instant, -}; +use std::time::Instant; const NUM_SAMPLES: u8 = 100; const VERTICAL_PARTITION: usize = 8; const HORIZONTAL_PARTITION: usize = 8; +const WIDTH: usize = 800; +const HEIGHT: usize = 800; fn main() -> Result<(), String> { + run(WIDTH, HEIGHT) +} + +#[cfg(feature = "gui")] +fn run(mut width: usize, mut height: usize) -> Result<(), String> { + use sdl2::{ + event::{Event, WindowEvent}, + keyboard::Keycode, + pixels::PixelFormatEnum, + }; + let sdl_ctx = sdl2::init()?; let video_subsys = sdl_ctx.video()?; - let (mut width, mut height): (usize, usize) = (2000, 1000); - let window = video_subsys .window("Ray tracing in a weekend", width as u32, height as u32) .position_centered() @@ -49,15 +52,9 @@ fn main() -> Result<(), String> { .create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32) .map_err(|e| e.to_string())?; - //println!("{:?} {:?} {:?}", texture.query(), texture.color_mod(), texture.alpha_mod()); + let active_demo = demo::Demo; - let mut active_demo: &dyn Demo = &demos::MotionBlur; - // TODO: Should update when window is unfocus since the project window retains - // data from overlapped window - // TODO: Maybe consider using condition variable to make loop {} not run at full - // speed at all times pinning a core at 100% let mut should_update = true; - loop { for event in event_pump.poll_iter() { match event { @@ -97,7 +94,7 @@ fn main() -> Result<(), String> { println!( "Demo {} Time Taken(s) = {}", active_demo.name(), - now.elapsed().as_secs_f32() + now.elapsed().as_secs_f64() ); texture.update(None, &buffer, width * 4).unwrap(); @@ -107,3 +104,29 @@ fn main() -> Result<(), String> { } } } + +#[cfg(not(feature = "gui"))] +fn run(width: usize, height: usize) -> Result<(), String> { + let mut buffer = vec![0; width * height * 4]; + + let demo = demo::Demo; + + println!( + "Starting {} at {}x{} with {} samples", + demo.name(), + width, + height, + NUM_SAMPLES + ); + let now = Instant::now(); + demo.render(&mut buffer, width, height, NUM_SAMPLES); + println!( + "Rendered Demo {}. Time Taken(s) = {}", + demo.name(), + now.elapsed().as_secs_f64() + ); + + demo.save_as_ppm(&buffer, width, height); + + Ok(()) +} diff --git a/src/types/mod.rs b/src/types.rs similarity index 100% rename from src/types/mod.rs rename to src/types.rs diff --git a/src/types/hitable.rs b/src/types/hitable.rs index 99935a5..c67dd9a 100644 --- a/src/types/hitable.rs +++ b/src/types/hitable.rs @@ -1,7 +1,4 @@ -use { - crate::types::Vec3, - crate::types::{Material, Ray}, -}; +use crate::types::{Material, Ray, Vec3}; pub struct HitRecord<'a> { /// Rays are represented by A + t * B @@ -11,7 +8,7 @@ pub struct HitRecord<'a> { /// 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: f32, + 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 @@ -21,11 +18,11 @@ pub struct HitRecord<'a> { pub normal: Vec3, /// material if any of the surface - pub material: &'a Box, + pub material: &'a dyn Material, } pub trait Hitable: Send + Sync { - fn hit(&self, _ray: &Ray, _t_min: f32, _t_max: f32) -> Option { + fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option { None } } diff --git a/src/types/hitable_list.rs b/src/types/hitable_list.rs index cec3490..f2fac57 100644 --- a/src/types/hitable_list.rs +++ b/src/types/hitable_list.rs @@ -5,7 +5,7 @@ pub struct HitableList { } impl Hitable for HitableList { - fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option { + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { let mut closest_so_far = t_max; let mut hit_rec: Option = None; for obj in &self.list { diff --git a/src/types/material.rs b/src/types/material.rs index 2362bdf..6da58c9 100644 --- a/src/types/material.rs +++ b/src/types/material.rs @@ -1,10 +1,10 @@ use { crate::types::{HitRecord, Ray, Vec3}, - rand::Rng, + rand::{rngs::SmallRng, Rng}, }; pub trait Material: Send + Sync { - fn scatter(&self, ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option); + fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option); } pub struct Lambertian { @@ -18,9 +18,8 @@ impl Lambertian { } impl Material for Lambertian { - fn scatter(&self, ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option) { - let mut rng = rand::thread_rng(); - let target = hit_rec.p + hit_rec.normal + random_point_in_unit_sphere(&mut rng); + fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option) { + let target = hit_rec.p + hit_rec.normal + random_point_in_unit_sphere(rng); let scattered_ray = Ray::new(hit_rec.p, target - hit_rec.p, ray.time()); (self.albedo, Some(scattered_ray)) @@ -29,26 +28,29 @@ impl Material for Lambertian { pub struct Metal { albedo: Vec3, - fuzz: f32, + fuzz: f64, } impl Metal { pub fn new(albedo: Vec3) -> Self { Self { albedo, fuzz: 0.0 } } - pub fn with_fuzz(albedo: Vec3, fuzz: f32) -> Self { + pub fn with_fuzz(albedo: Vec3, fuzz: f64) -> Self { Self { albedo, fuzz } } } impl Material for Metal { - fn scatter(&self, ray_in: &Ray, hit_rec: &HitRecord) -> (Vec3, Option) { - let mut rng = rand::thread_rng(); - + fn scatter( + &self, + ray_in: &Ray, + hit_rec: &HitRecord, + rng: &mut SmallRng, + ) -> (Vec3, Option) { 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(&mut rng) * self.fuzz, + reflected_ray + random_point_in_unit_sphere(rng) * self.fuzz, ray_in.time(), ); @@ -61,21 +63,25 @@ impl Material for Metal { } pub struct Dielectric { - reflection_index: f32, + reflection_index: f64, } impl Dielectric { - pub fn new(reflection_index: f32) -> Self { + pub fn new(reflection_index: f64) -> Self { Self { reflection_index } } } impl Material for Dielectric { - fn scatter(&self, ray_in: &Ray, hit_rec: &HitRecord) -> (Vec3, Option) { + fn scatter( + &self, + ray_in: &Ray, + 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 mut rng = rand::thread_rng(); let (outward_normal, ni_over_nt, cosine) = if ray_in.direction().dot(&hit_rec.normal) > 0.0 { @@ -96,7 +102,7 @@ impl Material for Dielectric { if let Some(refracted_ray) = refract(ray_in.direction(), outward_normal, ni_over_nt) { let reflect_prob = schlick(cosine, self.reflection_index); - if rng.gen::() < reflect_prob { + if rng.gen::() < reflect_prob { ( attenuation, Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())), @@ -118,7 +124,7 @@ impl Material for Dielectric { // Christophe Schlick's Polynomial approximation to figure out reflectivity as the angle changes // See Fresnel Equations, https://en.wikipedia.org/wiki/Fresnel_equations -fn schlick(cosine: f32, reflection_index: f32) -> f32 { +fn schlick(cosine: f64, reflection_index: f64) -> f64 { let mut r0 = (1.0 - reflection_index) / (1.0 + reflection_index); r0 = r0 * r0; r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0) @@ -129,7 +135,7 @@ fn reflect(incident: Vec3, normal: Vec3) -> Vec3 { } // Snell's Law -fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f32) -> Option { +fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f64) -> Option { let uv = incident.unit_vector(); let dt = uv.dot(&normal); let discriminant = 1.0 - ni_over_nt * ni_over_nt * (1.0 - dt * dt); @@ -140,11 +146,11 @@ fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f32) -> Option { } } -fn random_point_in_unit_sphere(rng: &mut rand::rngs::ThreadRng) -> Vec3 { - let mut point = Vec3::new(rng.gen::(), rng.gen::(), rng.gen::()) * 2.0 +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); while point.sq_len() >= 1.0 { - point = Vec3::new(rng.gen::(), rng.gen::(), rng.gen::()) * 2.0 + point = Vec3::new(rng.gen::(), rng.gen::(), rng.gen::()) * 2.0 - Vec3::new(1.0, 1.0, 1.0); } point diff --git a/src/types/moving_sphere.rs b/src/types/moving_sphere.rs index 72f2b4f..2791a4e 100644 --- a/src/types/moving_sphere.rs +++ b/src/types/moving_sphere.rs @@ -1,22 +1,22 @@ use crate::types::{HitRecord, Hitable, Material, Ray, Vec3}; -pub struct MovingSphere { - radius: f32, +pub struct MovingSphere { + radius: f64, center_start: Vec3, center_end: Vec3, - time_start: f32, - time_end: f32, - material: Box, + time_start: f64, + time_end: f64, + material: T, } -impl MovingSphere { +impl MovingSphere { pub fn new( center_start: Vec3, center_end: Vec3, - time_start: f32, - time_end: f32, - radius: f32, - material: Box, + time_start: f64, + time_end: f64, + radius: f64, + material: T, ) -> Self { Self { center_start, @@ -28,15 +28,15 @@ impl MovingSphere { } } - fn center(&self, time: f32) -> Vec3 { + fn center(&self, time: f64) -> Vec3 { self.center_start + (self.center_end - self.center_start) * ((time - self.time_start) / (self.time_end - self.time_start)) } } -impl Hitable for MovingSphere { - fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option { +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()); diff --git a/src/types/ray.rs b/src/types/ray.rs index d776020..c94a2b4 100644 --- a/src/types/ray.rs +++ b/src/types/ray.rs @@ -3,11 +3,11 @@ use crate::types::Vec3; pub struct Ray { a: Vec3, b: Vec3, - time: f32, + time: f64, } impl Ray { - pub fn new(a: Vec3, b: Vec3, time: f32) -> Ray { + pub fn new(a: Vec3, b: Vec3, time: f64) -> Ray { Ray { a, b, time } } #[inline] @@ -19,11 +19,11 @@ impl Ray { self.b } #[inline] - pub fn point_at_parameter(&self, t: f32) -> Vec3 { + pub fn point_at_parameter(&self, t: f64) -> Vec3 { self.a + self.b * t } #[inline] - pub const fn time(&self) -> f32 { + pub const fn time(&self) -> f64 { self.time } } diff --git a/src/types/sphere.rs b/src/types/sphere.rs index e799943..10a6b87 100644 --- a/src/types/sphere.rs +++ b/src/types/sphere.rs @@ -1,13 +1,13 @@ use crate::types::{HitRecord, Hitable, Material, Ray, Vec3}; -pub struct Sphere { +pub struct Sphere { center: Vec3, - radius: f32, - material: Box, + radius: f64, + material: T, } -impl Sphere { - pub fn new(center: Vec3, radius: f32, material: Box) -> Self { +impl Sphere { + pub fn new(center: Vec3, radius: f64, material: T) -> Self { Self { center, radius, @@ -16,8 +16,8 @@ impl Sphere { } } -impl Hitable for Sphere { - fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option { +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()); diff --git a/src/types/vec3.rs b/src/types/vec3.rs index 163eba0..12d931e 100644 --- a/src/types/vec3.rs +++ b/src/types/vec3.rs @@ -4,50 +4,50 @@ use std::{ }; #[derive(Copy, Clone)] -pub struct Vec3([f32; 3]); +pub struct Vec3([f64; 3]); impl Vec3 { #[inline] - pub const fn new(a: f32, b: f32, c: f32) -> Vec3 { + pub const fn new(a: f64, b: f64, c: f64) -> Vec3 { Vec3([a, b, c]) } #[inline] - pub fn x(&self) -> f32 { + pub fn x(&self) -> f64 { self[0] } #[inline] - pub fn y(&self) -> f32 { + pub fn y(&self) -> f64 { self[1] } #[inline] - pub fn z(&self) -> f32 { + pub fn z(&self) -> f64 { self[2] } #[inline] - pub fn r(&self) -> f32 { + pub fn r(&self) -> f64 { self[0] } #[inline] - pub fn g(&self) -> f32 { + pub fn g(&self) -> f64 { self[1] } #[inline] - pub fn b(&self) -> f32 { + pub fn b(&self) -> f64 { self[2] } #[inline] - pub fn length(&self) -> f32 { + pub fn length(&self) -> f64 { self.sq_len().sqrt() } #[inline] - pub fn sq_len(&self) -> f32 { + pub fn sq_len(&self) -> f64 { self[0] * self[0] + self[1] * self[1] + self[2] * self[2] } #[inline] - pub fn dot(&self, v: &Vec3) -> f32 { + pub fn dot(&self, v: &Vec3) -> f64 { self[0] * v[0] + self[1] * v[1] + self[2] * v[2] } @@ -62,7 +62,7 @@ impl Vec3 { #[inline] pub fn make_unit_vector(&mut self) { - let k = 1.0f32 / (self[0] * self[0] + self[1] * self[1] + self[2] * self[2]); + 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; @@ -123,17 +123,17 @@ impl MulAssign for Vec3 { } } -impl MulAssign for Vec3 { - fn mul_assign(&mut self, o: f32) { +impl MulAssign for Vec3 { + fn mul_assign(&mut self, o: f64) { self[0] *= o; self[1] *= o; self[2] *= o; } } -impl Mul for Vec3 { +impl Mul for Vec3 { type Output = Vec3; - fn mul(self, o: f32) -> Vec3 { + fn mul(self, o: f64) -> Vec3 { Vec3([self[0] * o, self[1] * o, self[2] * o]) } } @@ -153,17 +153,17 @@ impl Div for Vec3 { } } -impl Div for Vec3 { +impl Div for Vec3 { type Output = Vec3; - fn div(self, o: f32) -> Vec3 { + fn div(self, o: f64) -> Vec3 { let o = 1.0 / o; Vec3([self[0] * o, self[1] * o, self[2] * o]) } } -impl DivAssign for Vec3 { - fn div_assign(&mut self, o: f32) { +impl DivAssign for Vec3 { + fn div_assign(&mut self, o: f64) { let o = 1.0 / o; self.0[0] *= o; self.0[1] *= o; @@ -172,15 +172,15 @@ impl DivAssign for Vec3 { } impl Index for Vec3 { - type Output = f32; + type Output = f64; - fn index(&self, q: usize) -> &f32 { + fn index(&self, q: usize) -> &f64 { &self.0[q] } } impl IndexMut for Vec3 { - fn index_mut(&mut self, q: usize) -> &mut f32 { + fn index_mut(&mut self, q: usize) -> &mut f64 { &mut self.0[q] } }