diff --git a/examples/all-materials-spheres.rs b/examples/all-materials-spheres.rs index 9f78566..cd45bec 100644 --- a/examples/all-materials-spheres.rs +++ b/examples/all-materials-spheres.rs @@ -22,31 +22,31 @@ fn main() -> io::Result<()> { fuzz: 0.0, }; - world.push(Sphere { - center: DVec3::new(0.0, -100.5, -1.0), - radius: 100.0, - material: material_ground, - }); - world.push(Sphere { - center: DVec3::new(0.0, 0.0, -1.0), - radius: 0.5, - material: material_center, - }); - world.push(Sphere { - center: DVec3::new(-1.0, 0.0, -1.0), - radius: 0.5, - material: material_left.clone(), - }); - world.push(Sphere { - center: DVec3::new(-1.0, 0.0, -1.0), - radius: -0.4, - material: material_left, - }); - world.push(Sphere { - center: DVec3::new(1.0, 0.0, -1.0), - radius: 0.5, - material: material_right, - }); + world.push(Sphere::new( + DVec3::new(0.0, -100.5, -1.0), + 100.0, + material_ground, + )); + world.push(Sphere::new( + DVec3::new(0.0, 0.0, -1.0), + 0.5, + material_center, + )); + world.push(Sphere::new( + DVec3::new(-1.0, 0.0, -1.0), + 0.5, + material_left.clone(), + )); + world.push(Sphere::new( + DVec3::new(-1.0, 0.0, -1.0), + -0.4, + material_left, + )); + world.push(Sphere::new( + DVec3::new(1.0, 0.0, -1.0), + 0.5, + material_right, + )); let camera = Camera::init() .image_width(800) diff --git a/examples/all-shapes.rs b/examples/all-shapes.rs index 4ddeedf..f122304 100644 --- a/examples/all-shapes.rs +++ b/examples/all-shapes.rs @@ -25,26 +25,26 @@ fn main() -> io::Result<()> { fuzz: 0.0, }; - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(0.0, -100.5, -1.0), - radius: 100.0, - material: material_ground, - })); + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(0.0, -100.5, -1.0), + 100.0, + material_ground, + ))); world.push(Shapes::Box(a_box::Box { center: DVec3::new(0.0, 0.0, -1.0), size: DVec3::splat(0.2), material: material_center, })); - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(-1.0, 0.0, -1.0), - radius: 0.5, - material: material_left.clone(), - })); - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(-1.0, 0.0, -1.0), - radius: -0.4, - material: material_left, - })); + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(-1.0, 0.0, -1.0), + 0.5, + material_left.clone(), + ))); + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(-1.0, 0.0, -1.0), + -0.4, + material_left, + ))); world.push(Shapes::Cylinder(Cylinder { start: DVec3::splat(-1.), end: DVec3::splat(-2.), diff --git a/examples/box.rs b/examples/box.rs index 6aeb272..f8c2866 100644 --- a/examples/box.rs +++ b/examples/box.rs @@ -19,11 +19,11 @@ fn main() -> io::Result<()> { albedo: DVec3::new(0.1, 0.2, 0.5), }; - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(0.0, -100.5, -1.0), - radius: 100.0, - material: material_ground, - })); + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(0.0, -100.5, -1.0), + 100.0, + material_ground, + ))); world.push(Shapes::Box(a_box::Box { center: DVec3::new(3.0, 0.0, -1.0), size: DVec3::ONE * 9., diff --git a/examples/cylinder.rs b/examples/cylinder.rs index b4df2a0..5d5d0e6 100644 --- a/examples/cylinder.rs +++ b/examples/cylinder.rs @@ -19,11 +19,11 @@ fn main() -> io::Result<()> { albedo: DVec3::new(0., 0., 1.), }; - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(0.0, -100.5, -1.0), - radius: 100.0, - material: material_ground, - })); + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(0.0, -100.5, -1.0), + 100.0, + material_ground, + ))); world.push(Shapes::Cylinder(Cylinder { start: DVec3::new(0.1, 0.1, -1.), end: DVec3::new(0.1, -0.1, -1.), diff --git a/examples/raytracing-in-one-weekend-final-scene-more.rs b/examples/raytracing-in-one-weekend-final-scene-more.rs index f2a778b..6b7cea2 100644 --- a/examples/raytracing-in-one-weekend-final-scene-more.rs +++ b/examples/raytracing-in-one-weekend-final-scene-more.rs @@ -13,13 +13,13 @@ fn main() -> io::Result<()> { let mut world = vec![]; - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(0., -1000., 0.), - radius: 1000., - material: Material::Lambertian { + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(0., -1000., 0.), + 1000., + Material::Lambertian { albedo: DVec3::new(0.5, 0.5, 0.5), }, - })); + ))); for (a, b) in (-11..11).cartesian_product(-11..11).into_iter() @@ -78,30 +78,30 @@ fn main() -> io::Result<()> { } } - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(0., 1., 0.), - radius: 1.0, - material: Material::Dielectric { + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(0., 1., 0.), + 1.0, + Material::Dielectric { index_of_refraction: 1.5, }, - })); + ))); - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(-4., 1., 0.), - radius: 1.0, - material: Material::Lambertian { + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(-4., 1., 0.), + 1.0, + Material::Lambertian { albedo: DVec3::new(0.4, 0.2, 0.1), }, - })); + ))); - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(4., 1., 0.), - radius: 1.0, - material: Material::Metal { + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(4., 1., 0.), + 1.0, + Material::Metal { albedo: DVec3::new(0.7, 0.6, 0.5), fuzz: 0.0, }, - })); + ))); let camera = Camera::init() .image_width(400) diff --git a/examples/raytracing-in-one-weekend-final-scene.rs b/examples/raytracing-in-one-weekend-final-scene.rs index d748b52..efb0f56 100644 --- a/examples/raytracing-in-one-weekend-final-scene.rs +++ b/examples/raytracing-in-one-weekend-final-scene.rs @@ -12,13 +12,13 @@ fn main() -> io::Result<()> { let mut world = vec![]; - world.push(Sphere { - center: DVec3::new(0., -1000., 0.), - radius: 1000., - material: Material::Lambertian { + world.push(Sphere::new( + DVec3::new(0., -1000., 0.), + 1000., + Material::Lambertian { albedo: DVec3::new(0.5, 0.5, 0.5), }, - }); + )); for (a, b) in (-11..11).cartesian_product(-11..11).into_iter() @@ -61,38 +61,34 @@ fn main() -> io::Result<()> { } }; - world.push(Sphere { - center, - radius: 0.2, - material, - }); + world.push(Sphere::new(center, 0.2, material)); } } - world.push(Sphere { - center: DVec3::new(0., 1., 0.), - radius: 1.0, - material: Material::Dielectric { + world.push(Sphere::new( + DVec3::new(0., 1., 0.), + 1.0, + Material::Dielectric { index_of_refraction: 1.5, }, - }); + )); - world.push(Sphere { - center: DVec3::new(-4., 1., 0.), - radius: 1.0, - material: Material::Lambertian { + world.push(Sphere::new( + DVec3::new(-4., 1., 0.), + 1.0, + Material::Lambertian { albedo: DVec3::new(0.4, 0.2, 0.1), }, - }); + )); - world.push(Sphere { - center: DVec3::new(4., 1., 0.), - radius: 1.0, - material: Material::Metal { + world.push(Sphere::new( + DVec3::new(4., 1., 0.), + 1.0, + Material::Metal { albedo: DVec3::new(0.7, 0.6, 0.5), fuzz: 0.0, }, - }); + )); let camera = Camera::init() .image_width(400) diff --git a/examples/rounded-box.rs b/examples/rounded-box.rs index 6dfbd14..0a29021 100644 --- a/examples/rounded-box.rs +++ b/examples/rounded-box.rs @@ -18,11 +18,11 @@ fn main() -> io::Result<()> { albedo: DVec3::new(0.1, 0.2, 0.5), }; - world.push(Shapes::Sphere(Sphere { - center: DVec3::new(0.0, -100.5, -1.0), - radius: 100.0, - material: material_ground, - })); + world.push(Shapes::Sphere(Sphere::new( + DVec3::new(0.0, -100.5, -1.0), + 100.0, + material_ground, + ))); world.push(Shapes::RoundedBox(RoundedBox { center: DVec3::new(0.0, 0.0, -1.0), radius: 0.2, diff --git a/examples/week-motion-blur.rs b/examples/week-motion-blur.rs new file mode 100644 index 0000000..4712254 --- /dev/null +++ b/examples/week-motion-blur.rs @@ -0,0 +1,125 @@ +use glam::DVec3; +use itertools::Itertools; +use rand::prelude::*; +use raytracer::{ + camera::Camera, material::Material, + shapes::sphere::Sphere, +}; +use std::io; + +fn main() -> io::Result<()> { + let mut rng = rand::thread_rng(); + + let mut world = vec![]; + + world.push(Sphere::new( + DVec3::new(0., -1000., 0.), + 1000., + Material::Lambertian { + albedo: DVec3::new(0.5, 0.5, 0.5), + }, + )); + + for (a, b) in + (-11..11).cartesian_product(-11..11).into_iter() + { + let choose_mat = rng.gen::(); + let center = DVec3::new( + a as f64 + 0.9 * rng.gen::(), + 0.2, + b as f64 + 0.9 * rng.gen::(), + ); + + if (center - DVec3::new(4., 0.2, 0.)).length() > 0.9 + { + if choose_mat < 0.8 { + // diffuse + let albedo = DVec3::new( + rng.gen_range(0f64..1.), + rng.gen_range(0f64..1.), + rng.gen_range(0f64..1.), + ) * DVec3::new( + rng.gen_range(0f64..1.), + rng.gen_range(0f64..1.), + rng.gen_range(0f64..1.), + ); + let material = + Material::Lambertian { albedo: albedo }; + let center2 = center + + DVec3::new( + 0., + rng.gen_range(0f64..0.5), + 0., + ); + world.push( + Sphere::new(center, 0.2, material) + .with_move_to(center2), + ); + } else if choose_mat < 0.95 { + // metal + let albedo = DVec3::new( + rng.gen_range(0.5..1.), + rng.gen_range(0.5..1.), + rng.gen_range(0.5..1.), + ); + let fuzz = rng.gen_range(0f64..0.5); + + let material = + Material::Metal { albedo, fuzz }; + world.push(Sphere::new( + center, 0.2, material, + )); + } else { + // glass + let material = Material::Dielectric { + index_of_refraction: 1.5, + }; + world.push(Sphere::new( + center, 0.2, material, + )); + }; + } + } + + world.push(Sphere::new( + DVec3::new(0., 1., 0.), + 1.0, + Material::Dielectric { + index_of_refraction: 1.5, + }, + )); + + world.push(Sphere::new( + DVec3::new(-4., 1., 0.), + 1.0, + Material::Lambertian { + albedo: DVec3::new(0.4, 0.2, 0.1), + }, + )); + + world.push(Sphere::new( + DVec3::new(4., 1., 0.), + 1.0, + Material::Metal { + albedo: DVec3::new(0.7, 0.6, 0.5), + fuzz: 0.0, + }, + )); + + let camera = Camera::init() + .image_width(800) + .aspect_ratio(16.0 / 9.0) + .look_from(DVec3::new(13., 2., 3.)) + .look_at(DVec3::ZERO) + .vup(DVec3::Y) + .focus_dist(10.0) + .defocus_angle(0.0) + .samples_per_pixel(500) + .max_depth(50) + .vfov(20.) + .build(); + + camera.render_to_disk("week-motion-blur", world)?; + + Ok(()) +} diff --git a/src/camera.rs b/src/camera.rs index 37e0216..ba56534 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -70,6 +70,7 @@ impl Camera { } fn get_ray(&self, i: i32, j: i32) -> Ray { + let mut rng = rand::thread_rng(); // Get a randomly sampled camera ray for the pixel at location i,j. let pixel_center = self.pixel00_loc @@ -86,9 +87,11 @@ impl Camera { let ray_direction = pixel_sample - ray_origin; + let ray_time = rng.gen(); Ray { origin: self.center, direction: ray_direction, + time: ray_time, } } fn defocus_disk_sample(&self) -> DVec3 { diff --git a/src/material.rs b/src/material.rs index d3e5476..bb0fc47 100644 --- a/src/material.rs +++ b/src/material.rs @@ -44,6 +44,7 @@ impl Material { scattered: Ray { origin: hit_record.point, direction: scatter_direction, + time: r_in.time, }, }) } @@ -56,6 +57,7 @@ impl Material { origin: hit_record.point, direction: reflected + *fuzz * random_unit_vector(), + time: r_in.time, }; // absorb any scatter that is below the surface if scattered @@ -120,6 +122,7 @@ impl Material { scattered: Ray { origin: hit_record.point, direction: direction, + time: r_in.time, }, }) } diff --git a/src/ray.rs b/src/ray.rs index d1c1509..f9ff582 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -5,6 +5,17 @@ use crate::{hittable::Hittable, material::Scattered}; pub struct Ray { pub origin: DVec3, pub direction: DVec3, + pub time: f64, +} + +impl Default for Ray { + fn default() -> Self { + Self { + origin: Default::default(), + direction: Default::default(), + time: Default::default(), + } + } } impl Ray { diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index 62a6a82..2c6ddab 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -8,10 +8,58 @@ use crate::{ ray::Ray, }; +// pub struct Sphere { +// pub center: DVec3, +// pub radius: f64, +// pub material: Material, +// } + pub struct Sphere { - pub center: DVec3, - pub radius: f64, - pub material: Material, + center: DVec3, + radius: f64, + material: Material, + move_to: Option, +} + +impl Default for Sphere { + fn default() -> Self { + Self { + center: Default::default(), + radius: Default::default(), + material: Material::Lambertian { + albedo: DVec3::new(0.0, 1., 1.), + }, + move_to: None, + } + } +} +impl Sphere { + pub fn new( + center: DVec3, + radius: f64, + material: Material, + ) -> Self { + Self { + center, + radius, + material, + move_to: None, + } + } + pub fn with_move_to(mut self, to: DVec3) -> Self { + // the raytracing series has this odd constructor that pre-calculates + // a value, then stores it. + self.move_to = Some(to - self.center); + self + } + fn center(&self, time: f64) -> DVec3 { + match self.move_to { + Some(center_vec) => { + self.center + time * center_vec + } + None => self.center, + } + } } impl Hittable for Sphere { @@ -20,7 +68,9 @@ impl Hittable for Sphere { ray: &Ray, interval: Range, ) -> Option { - let oc = ray.origin - self.center; + let center = self.center(ray.time); + + let oc = ray.origin - center; let a = ray.direction.length_squared(); let half_b = oc.dot(ray.direction); let c = @@ -43,8 +93,7 @@ impl Hittable for Sphere { let t = root; let point = ray.at(t); - let outward_normal = - (point - self.center) / self.radius; + let outward_normal = (point - center) / self.radius; let rec = HitRecord::with_face_normal( self.material.clone(),