diff --git a/src/camera.rs b/src/camera.rs index e202352..a70646f 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -8,17 +8,31 @@ pub struct Camera { } impl Camera { - pub const fn new( - origin: Vec3, - horizontal: Vec3, - vertical: Vec3, - lower_left_corner: Vec3, - ) -> Self { + // vertical_fov is the viewable angle from top->bottom + // look_from is basically camera position + // look_at is the point where camera is looking + // v_up is camera's up vector. i.e. it points upwards from the camera + // orthogonal to look_from - look_at vector + pub fn new(look_from: Vec3, look_at: Vec3, v_up: Vec3, vertical_fov: f64, aspect: f64) -> Self { + // convert degree to radian + let angle = vertical_fov * std::f64::consts::PI / 180.0; + let half_height = (angle / 2.0).tan(); + let half_width = aspect * half_height; + + let origin = look_from; + let w = (look_from - look_at).unit_vector(); + let u = v_up.cross(&w).unit_vector(); + let v = w.cross(&u); + + let lower_left_corner = origin - u * half_width - v * half_height - w; + let horizontal = u * half_width * 2.0; + let vertical = v * half_height * 2.0; + Self { - origin, + lower_left_corner, horizontal, vertical, - lower_left_corner, + origin, } } @@ -32,12 +46,13 @@ impl Camera { impl std::default::Default for Camera { fn default() -> Self { - Camera { - origin: Vec3::new(0.0, 0.0, 0.0), - // Because canvas is in 2:1 ratio - horizontal: Vec3::new(4.0, 0.0, 0.0), - vertical: Vec3::new(0.0, 2.0, 0.0), - lower_left_corner: Vec3::new(-2.0, -1.0, -1.0), - } + Camera::new( + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, 0.0, -1.0), + Vec3::new(0.0, 1.0, 0.0), + 90.0, + // 2:1 aspect ratio width:height + 2.0, + ) } } diff --git a/src/demos/diffuse_materials.rs b/src/demos/diffuse_materials.rs index 8ee015d..4009335 100644 --- a/src/demos/diffuse_materials.rs +++ b/src/demos/diffuse_materials.rs @@ -11,7 +11,7 @@ pub struct DiffuseMaterials; impl Demo for DiffuseMaterials { fn name(&self) -> &'static str { - "Diffuse Materials" + "diffuse-materials" } fn render_chunk(&self, chunk: &mut Chunk, samples: u8) { diff --git a/src/demos/hitable_sphere.rs b/src/demos/hitable_sphere.rs index 362b1e9..81ecfdd 100644 --- a/src/demos/hitable_sphere.rs +++ b/src/demos/hitable_sphere.rs @@ -6,7 +6,7 @@ pub struct HitableSphere; impl Demo for HitableSphere { fn name(&self) -> &'static str { - "Sphere using Hit table" + "sphere-using-hit-table" } fn render_chunk(&self, chunk: &mut Chunk, _samples: u8) { diff --git a/src/demos/linear_gradient_rectangle.rs b/src/demos/linear_gradient_rectangle.rs index 279aeca..30ef268 100644 --- a/src/demos/linear_gradient_rectangle.rs +++ b/src/demos/linear_gradient_rectangle.rs @@ -7,7 +7,7 @@ use crate::{ impl Demo for LinearGradientRectangle { fn name(&self) -> &'static str { - "Linear Gradient Rectangle" + "linear-gradient-rectangle" } fn render_chunk(&self, chunk: &mut Chunk, _samples: u8) { diff --git a/src/demos/mod.rs b/src/demos/mod.rs index f110147..be77f75 100644 --- a/src/demos/mod.rs +++ b/src/demos/mod.rs @@ -3,6 +3,7 @@ mod diffuse_materials; mod hitable_sphere; mod linear_gradient_rectangle; mod materials; +mod positionable_camera; mod simple_antialiasing; mod simple_rectangle; mod simple_sphere; @@ -13,6 +14,7 @@ pub use diffuse_materials::DiffuseMaterials; pub use hitable_sphere::HitableSphere; pub use linear_gradient_rectangle::LinearGradientRectangle; pub use materials::Materials; +pub use positionable_camera::PositionableCamera; pub use simple_antialiasing::SimpleAntialiasing; pub use simple_rectangle::SimpleRectangle; pub use simple_sphere::SimpleSphere; diff --git a/src/demos/positionable_camera.rs b/src/demos/positionable_camera.rs new file mode 100644 index 0000000..b13a360 --- /dev/null +++ b/src/demos/positionable_camera.rs @@ -0,0 +1,105 @@ +use { + crate::{ + demos::{Chunk, Demo}, + types::{ + material::{Dielectric, Lambertian, Metal}, + Hitable, HitableList, Ray, Sphere, Vec3, + }, + Camera, + }, + rand::Rng, +}; + +pub struct PositionableCamera; + +impl Demo for PositionableCamera { + fn name(&self) -> &'static str { + "positionable-camera" + } + + fn render_chunk(&self, chunk: &mut Chunk, samples: u8) { + let x = chunk.x; + let y = chunk.y; + let nx = chunk.nx; + let ny = chunk.ny; + let start_x = chunk.start_x; + let start_y = chunk.start_y; + let buffer = &mut chunk.buffer; + let radius = (std::f64::consts::PI / 4.0).cos(); + + let world = HitableList { + list: vec![ + Box::new(Sphere::with_material( + Vec3::new(-radius, 0.0, -1.0), + radius, + Box::new(Lambertian::new(Vec3::new(0.0, 0.0, 1.0))), + )), + Box::new(Sphere::with_material( + Vec3::new(radius, 0.0, -1.0), + radius, + Box::new(Metal::with_fuzz(Vec3::new(0.5, 0.5, 0.0), 0.2)), + )), + Box::new(Sphere::with_material( + Vec3::new(-3.0 * radius, 0.0, -1.0), + radius, + Box::new(Dielectric::new(1.5)), + )), + Box::new(Sphere::with_material( + Vec3::new(0.0, -100.5, -1.0), + 100.0, + Box::new(Lambertian::new(Vec3::new(0.8, 0.8, 0.0))), + )), + ], + }; + + let camera = Camera::new( + Vec3::new(-2.0, 2.0, 1.0), + Vec3::new(0.0, 0.0, -1.0), + Vec3::new(0.0, 1.0, 0.0), + 68.0, + nx as f64 / ny as f64, + ); + 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 f64 + rng.gen::()) / x as f64; + let v = (j as f64 + rng.gen::()) / y as f64; + + let ray = camera.get_ray(u, v); + color += calc_color(ray, &world, 0); + } + + color /= samples as f64; + + // 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::f64::MAX) { + if depth >= 50 { + Vec3::new(0.0, 0.0, 0.0) + } else { + let material = hit_rec.material.as_ref(); + if let (attenuation, Some(scattered_ray)) = material.unwrap().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/demos/simple_antialiasing.rs b/src/demos/simple_antialiasing.rs index ca239ae..1d2b3d6 100644 --- a/src/demos/simple_antialiasing.rs +++ b/src/demos/simple_antialiasing.rs @@ -10,7 +10,7 @@ pub struct SimpleAntialiasing; impl Demo for SimpleAntialiasing { fn name(&self) -> &'static str { - "A simple antialiasing implementation" + "simple-antialiasing" } fn render_chunk(&self, chunk: &mut Chunk, samples: u8) { let x = chunk.x; diff --git a/src/main.rs b/src/main.rs index 245df70..c185ffe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,6 @@ use { }; const NUM_SAMPLES: u8 = 100; - const VERTICAL_PARTITION: usize = 8; const HORIZONTAL_PARTITION: usize = 8; @@ -80,6 +79,7 @@ fn main() -> Result<(), String> { Some(Keycode::Num7) => active_demo = Box::new(demos::DiffuseMaterials), Some(Keycode::Num8) => active_demo = Box::new(demos::Materials), Some(Keycode::Num9) => active_demo = Box::new(demos::DielectricMaterial), + Some(Keycode::Num0) => active_demo = Box::new(demos::PositionableCamera), None => unreachable!(), _ => (), };