diff --git a/ria-weekend/Cargo.toml b/ria-weekend/Cargo.toml index 5580030..217bdc0 100644 --- a/ria-weekend/Cargo.toml +++ b/ria-weekend/Cargo.toml @@ -5,5 +5,6 @@ authors = ["ishanjain28 "] edition = "2018" [dependencies] -sdl2 = "*" -rand = "*" +sdl2 = "0.33.0" +rand = "0.7.3" + diff --git a/ria-weekend/src/camera.rs b/ria-weekend/src/camera.rs deleted file mode 100644 index 6452785..0000000 --- a/ria-weekend/src/camera.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::types::{Ray, Vec3}; - -#[derive(Debug)] -pub struct Camera { - origin: Vec3, - vertical: Vec3, - horizontal: Vec3, - lower_left_corner: Vec3, -} - -impl Camera { - pub fn new(origin: Vec3, vertical: Vec3, horizontal: Vec3, lower_left_corner: Vec3) -> Camera { - Camera { - origin, - vertical, - horizontal, - lower_left_corner, - } - } - - pub fn get_ray(&self, u: f32, v: f32) -> Ray { - Ray::new( - self.origin, - self.lower_left_corner + self.horizontal * u + self.vertical * v, - ) - } -} - -impl Default for Camera { - fn default() -> Camera { - Camera { - origin: Vec3::new(0.0, 0.0, 0.0), - vertical: Vec3::new(0.0, 2.0, 0.0), - horizontal: Vec3::new(4.0, 0.0, 0.0), - lower_left_corner: Vec3::new(-2.0, -1.0, -1.0), - } - } -} diff --git a/ria-weekend/src/demo.rs b/ria-weekend/src/demo.rs deleted file mode 100644 index ca4f2f8..0000000 --- a/ria-weekend/src/demo.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::error::Error; -use std::fs::File; -use std::io::Write; - -pub trait Demo { - fn render(&self, buf: &mut Vec, width: usize, height: usize, samples: u8); - fn name(&self) -> String; - - 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.description()), - }; - file.write(header.as_bytes()) - .expect("error in writing file header"); - - for i in buf.chunks(4) { - match file.write(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) { - Ok(_) => (), - Err(e) => panic!("couldn't write to {}: {}", self.name(), e.description()), - } - } - } -} diff --git a/ria-weekend/src/demos/antialiasing.rs b/ria-weekend/src/demos/antialiasing.rs deleted file mode 100644 index f4e7c80..0000000 --- a/ria-weekend/src/demos/antialiasing.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::camera::Camera; -use crate::hitable::{HitRecord, Hitable, HitableList}; -use crate::shapes; -use crate::types::{Ray, Vec3}; -use rand::Rng; - -pub struct Antialiasing; - -impl crate::Demo for Antialiasing { - fn name(&self) -> String { - "antialiasing".to_owned() - } - - fn render(&self, buf: &mut Vec, w: usize, h: usize, ns: u8) { - let list: Vec> = vec![ - Box::new(shapes::Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)), - Box::new(shapes::Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)), - ]; - let world = HitableList::new(list); - let camera = Camera::default(); - let mut rng = rand::thread_rng(); - } -} - -fn calculate_color(ray: Ray, world: &HitableList) -> Vec3 { - let mut hit_rec = HitRecord { - t: 0.0, - normal: Vec3::new(0.0, 0.0, 0.0), - point: Vec3::new(0.0, 0.0, 0.0), - }; - if world.hit(&ray, 0.001, std::f32::MAX, &mut hit_rec) { - Vec3::new( - hit_rec.normal.x() + 1.0, - hit_rec.normal.y() + 1.0, - hit_rec.normal.z() + 1.0, - ) * 0.5 - } 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/ria-weekend/src/demos/hitable_surface_normal_sphere.rs b/ria-weekend/src/demos/hitable_surface_normal_sphere.rs deleted file mode 100644 index 0f7891d..0000000 --- a/ria-weekend/src/demos/hitable_surface_normal_sphere.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::{ - hitable::{HitRecord, Hitable, HitableList}, - shapes, - types::{Ray, Vec3}, -}; - -const RADIUS: f32 = 0.5; -pub struct HitableSurfaceNormalSphere; - -impl crate::Demo for HitableSurfaceNormalSphere { - fn name(&self) -> String { - "hit-table_surface_normal_sphere".to_owned() - } - - fn render(&self, buf: &mut Vec, w: usize, h: usize, _ns: u8) { - // in my case, The resolution is 1200x800 - // These numbers are calculated by first calculating the aspect ratio - // and then just figuring out lower left corner, Width(2 x aspect ratio width) - // Height(2 x aspect ratio height) - let lower_left_corner = Vec3::new(-2.0, -1.0, -1.0); - let horizontal = Vec3::new(4.0, 0.0, 0.0); - let vertical = Vec3::new(0.0, 2.0, 0.0); - let origin = Vec3::new(0.0, 0.0, 0.0); - - let list: Vec> = vec![ - Box::new(shapes::Sphere::new(Vec3::new(0.0, 0.0, -1.0), RADIUS)), - Box::new(shapes::Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)), - ]; - let world = HitableList::new(list); - - let mut offset = 0; - for j in (0..h).rev() { - for i in 0..w { - let u = i as f32 / w as f32; - let v = j as f32 / h as f32; - - let ray = Ray::new(origin, lower_left_corner + horizontal * u + vertical * v); - - let color = calculate_color(ray, &world); - let ir = (255.99 * color.r()) as u8; - let ig = (255.99 * color.g()) as u8; - let ib = (255.99 * color.b()) as u8; - - buf[offset] = ir; - buf[offset + 1] = ig; - buf[offset + 2] = ib; - offset += 4; - } - } - } -} - -fn calculate_color(ray: Ray, world: &HitableList) -> Vec3 { - let mut hit_rec = HitRecord { - t: 0.0, - normal: Vec3::new(0.0, 0.0, 0.0), - point: Vec3::new(0.0, 0.0, 0.0), - }; - if world.hit(&ray, 0.001, std::f32::MAX, &mut hit_rec) { - Vec3::new( - hit_rec.normal.x() + 1.0, - hit_rec.normal.y() + 1.0, - hit_rec.normal.z() + 1.0, - ) * 0.5 - } 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/ria-weekend/src/demos/linear_gradient_rectangle.rs b/ria-weekend/src/demos/linear_gradient_rectangle.rs new file mode 100644 index 0000000..1764c64 --- /dev/null +++ b/ria-weekend/src/demos/linear_gradient_rectangle.rs @@ -0,0 +1,44 @@ +pub struct LinearGradientRectangle; + +use crate::{ + types::{Ray, Vec3}, + Demo, +}; + +impl Demo for LinearGradientRectangle { + fn name(&self) -> &'static str { + "Linear Gradient Rectangle" + } + + fn render(&self, buf: &mut [u8], width: usize, height: usize, _samples: u8) { + let mut offset = 0; + + // -2.0 and 4.0 in lower_left_corner and horizontal respectively + // because our canvas is in 2:1 ratio + let lower_left_corner = Vec3::new(-2.0, -1.0, -1.0); + let horizontal = Vec3::new(4.0, 0.0, 0.0); + let vertical = Vec3::new(0.0, 2.0, 0.0); + let origin = Vec3::new(0.0, 0.0, 0.0); + + for j in (0..height).rev() { + for i in 0..width { + let u = i as f32 / width as f32; + let v = j as f32 / height as f32; + let ray = Ray::new(origin, lower_left_corner + horizontal * u + vertical * v); + + let c = color(ray); + + buf[offset] = (255.99 * c.r()) as u8; + buf[offset + 1] = (255.99 * c.g()) as u8; + buf[offset + 2] = (255.99 * c.b()) as u8; + offset += 4; + } + } + } +} + +fn color(ray: Ray) -> Vec3 { + 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/ria-weekend/src/demos/mod.rs b/ria-weekend/src/demos/mod.rs index e945c6e..6d0782d 100644 --- a/ria-weekend/src/demos/mod.rs +++ b/ria-weekend/src/demos/mod.rs @@ -1,9 +1,34 @@ -mod antialiasing; -mod hitable_surface_normal_sphere; +mod linear_gradient_rectangle; +mod simple_rectangle; mod simple_sphere; mod surface_normal_sphere; -pub use antialiasing::Antialiasing; -pub use hitable_surface_normal_sphere::HitableSurfaceNormalSphere; +pub use linear_gradient_rectangle::LinearGradientRectangle; +pub use simple_rectangle::SimpleRectangle; pub use simple_sphere::SimpleSphere; pub use surface_normal_sphere::SurfaceNormalSphere; + +use std::{fs::File, io::Write}; + +pub trait Demo { + fn render(&self, buf: &mut [u8], width: usize, height: usize, 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(header.as_bytes()) + .expect("error in writing file header"); + + for i in buf.chunks(4) { + match file.write(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) { + Ok(_) => (), + Err(e) => panic!("couldn't write to {}: {}", self.name(), e), + } + } + } +} diff --git a/ria-weekend/src/demos/simple_rectangle.rs b/ria-weekend/src/demos/simple_rectangle.rs new file mode 100644 index 0000000..2c0fb0c --- /dev/null +++ b/ria-weekend/src/demos/simple_rectangle.rs @@ -0,0 +1,24 @@ +use crate::{demos::Demo, types::Vec3}; + +pub struct SimpleRectangle; + +impl Demo for SimpleRectangle { + fn name(&self) -> &'static str { + "simple_rectangle" + } + + fn render(&self, buf: &mut [u8], width: usize, height: usize, _samples: u8) { + let mut offset = 0; + for j in (0..height).rev() { + for i in 0..width { + let color = Vec3::new(i as f32 / width as f32, j as f32 / width as f32, 0.2); + + buf[offset] = (255.99 * color.r()) as u8; + buf[offset + 1] = (255.99 * color.g()) as u8; + buf[offset + 2] = (255.99 * color.b()) as u8; + + offset += 4; + } + } + } +} diff --git a/ria-weekend/src/demos/simple_sphere.rs b/ria-weekend/src/demos/simple_sphere.rs index cade118..000cd1e 100644 --- a/ria-weekend/src/demos/simple_sphere.rs +++ b/ria-weekend/src/demos/simple_sphere.rs @@ -1,23 +1,33 @@ -use crate::types::{Ray, Vec3}; +use crate::{ + types::{Ray, Vec3}, + Demo, +}; const RADIUS: f32 = 0.5; pub struct SimpleSphere; -impl crate::Demo for SimpleSphere { - fn name(&self) -> String { - "simple_sphere".to_owned() +impl Demo for SimpleSphere { + fn name(&self) -> &'static str { + "simple_sphere" } - fn render(&self, buf: &mut Vec, w: usize, h: usize, _ns: u8) { - // in my case, The resolution is 1200x800 - // These numbers are calculated by first calculating the aspect ratio - // and then just figuring out lower left corner, Width(2 x aspect ratio width) - // Height(2 x aspect ratio height) + fn render(&self, buf: &mut [u8], w: usize, h: usize, _ns: u8) { + // Usually, lower_left_corner should've been -1.0,-1.0,-1.0 and + // horizontal should've been 2.0,0.0,0.0 + // but we are working with a canvas that is 2:1 in size. + // So, If we had used aforementioned values then, We would've gotten + // a ellipse instead of a circle + // Since, we are using the same number of coordinates/values to + // represent twice as many points in x axis, The generated image is also + // stretched horizontally. + // To prevent this from happening, Since our dimensions are in 2:1 ratio, + // We adjust the lower_left_corner and horizontal values to scale + let lower_left_corner = Vec3::new(-2.0, -1.0, -1.0); let horizontal = Vec3::new(4.0, 0.0, 0.0); let vertical = Vec3::new(0.0, 2.0, 0.0); - // Observer position + // Observer's position let origin = Vec3::new(0.0, 0.0, 0.0); let mut offset = 0; @@ -30,13 +40,9 @@ impl crate::Demo for SimpleSphere { let ray = Ray::new(origin, lower_left_corner + horizontal * u + vertical * v); let color = calc_color(ray); - let ir = (255.99 * color.r()) as u8; - let ig = (255.99 * color.g()) as u8; - let ib = (255.99 * color.b()) as u8; - - buf[offset] = ir; - buf[offset + 1] = ig; - buf[offset + 2] = ib; + buf[offset] = (255.99 * color.r()) as u8; + buf[offset + 1] = (255.99 * color.g()) as u8; + buf[offset + 2] = (255.99 * color.b()) as u8; offset += 4; } } @@ -46,18 +52,22 @@ impl crate::Demo for SimpleSphere { fn ray_hit_sphere(center: Vec3, radius: f32, ray: &Ray) -> bool { // For a point to lie on a circle, // (x-cx)^2 + (y-cy)^2 + (z-cz)^2 = R * R - // should hold true - // Aforementioned equation can be rewritten as, - // dot(p-c, p-c) since the dot product of dis-similar axises will be zero + // should hold true. This equation can be rewritten as, + // dot(p(t)-C, p(t)-C) + // where p(t) => A + B * t. p(t) represents a point on ray + // Putting p(t) back in equation + // dot((A + t*B - C), (A + t*B - C)) = R * R + // This can be written as, + // dot((t*B + (A-C)), (t*B + (A-C)) => (t*B + (A-C))^2 // the expansion of this dot product will result in the same equation - // i.e. t * t * dot(B,B) + 2 * t * dot(B, A-C) + dot(A-C, A-C) + // i.e. t * t * dot(B,B) + 2 * t * dot(B, A-C) + dot(A-C, A-C) - R * R // Vector from circle center to point - let pc = ray.origin() - center; + let ac = ray.origin() - center; let a = ray.direction().dot(&ray.direction()); - let b = 2.0 * pc.dot(&ray.direction()); - let c = pc.dot(&pc) - radius * radius; + let b = 2.0 * ray.direction().dot(&ac); + let c = ac.dot(&ac) - radius * radius; let discriminant = b * b - 4.0 * a * c; discriminant > 0.0 @@ -66,7 +76,9 @@ fn ray_hit_sphere(center: Vec3, radius: f32, ray: &Ray) -> bool { fn calc_color(ray: Ray) -> Vec3 { // linear interpolation based on y coordinate // top to down - // center at z=-1. xy axis cuts sphere in 4 parts + // z == -1 because the observer is at 0.0,0.0,0.0 and the circle is being + // drawn in the z = -1 plane. So, The intersection will happen in this plane + // since circle is 2D if ray_hit_sphere(Vec3::new(0.0, 0.0, -1.0), RADIUS, &ray) { // For all rays that hit sphere, return red color // This will result in a sphere that is red in color @@ -75,7 +87,7 @@ fn calc_color(ray: Ray) -> Vec3 { let unit_direction = ray.direction().unit_vector(); // For rays that don't hit sphere, It'll paint the gradient as the background // Linear gradient depends on y - let t = 0.5 * (unit_direction.y() + 1.0); + let t = 0.5 * unit_direction.y() + 1.0; // start color + end color Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t diff --git a/ria-weekend/src/demos/surface_normal_sphere.rs b/ria-weekend/src/demos/surface_normal_sphere.rs index d7aabdb..d796604 100644 --- a/ria-weekend/src/demos/surface_normal_sphere.rs +++ b/ria-weekend/src/demos/surface_normal_sphere.rs @@ -4,15 +4,21 @@ const RADIUS: f32 = 0.5; pub struct SurfaceNormalSphere; impl crate::Demo for SurfaceNormalSphere { - fn name(&self) -> String { - "surface_normal_sphere".to_owned() + fn name(&self) -> &'static str { + "surface_normal_sphere" } - fn render(&self, buf: &mut Vec, w: usize, h: usize, _ns: u8) { - // in my case, The resolution is 1200x800 - // These numbers are calculated by first calculating the aspect ratio - // and then just figuring out lower left corner, Width(2 x aspect ratio width) - // Height(2 x aspect ratio height) + fn render(&self, buf: &mut [u8], w: usize, h: usize, _ns: u8) { + // Usually, lower_left_corner should've been -1.0,-1.0,-1.0 and + // horizontal should've been 2.0,0.0,0.0 + // but we are working with a canvas that is 2:1 in size. + // So, If we had used aforementioned values then, We would've gotten + // a ellipse instead of a circle + // Since, we are using the same number of coordinates/values to + // represent twice as many points in x axis, The generated image is also + // stretched horizontally. + // To prevent this from happening, Since our dimensions are in 2:1 ratio, + // We adjust the lower_left_corner and horizontal values to scale let lower_left_corner = Vec3::new(-2.0, -1.0, -1.0); let horizontal = Vec3::new(4.0, 0.0, 0.0); let vertical = Vec3::new(0.0, 2.0, 0.0); @@ -41,29 +47,21 @@ impl crate::Demo for SurfaceNormalSphere { } fn calculate_color(ray: Ray) -> Vec3 { - // center at z=-1. xy axis cuts sphere in half - // blending parameter let t = ray_hit_sphere(Vec3::new(0.0, 0.0, -1.0), RADIUS, &ray); if t > 0.0 { - // For all rays that hit sphere, return red color - // This will result in a sphere that is red in color let n = (ray.point_at_parameter(t) - Vec3::new(0.0, 0.0, -1.0)).unit_vector(); return Vec3::new(n.x() + 1.0, n.y() + 1.0, n.z() + 1.0) * 0.5; } let unit_direction = ray.direction().unit_vector(); // For rays that don't hit sphere, It'll paint the gradient as the background // Linear gradient depends on y - let t = 0.5 * (unit_direction.y() + 1.0); + let t = 0.5 * unit_direction.y() + 1.0; // start color + end color Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t } fn ray_hit_sphere(center: Vec3, radius: f32, ray: &Ray) -> f32 { - // dot(A + t*B - C, A + t*B - C) = R*R - // when expanded we get - // t * t * dot(B, B) + 2 * t * dot(B, A-C) + dot(A-C, A-C) - R*R = 0 - let pc = ray.origin() - center; let a = ray.direction().dot(&ray.direction()); let b = 2.0 * pc.dot(&ray.direction()); diff --git a/ria-weekend/src/hitable.rs b/ria-weekend/src/hitable.rs deleted file mode 100644 index 59fd7e5..0000000 --- a/ria-weekend/src/hitable.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::types::{Ray, Vec3}; - -#[derive(Debug, Copy, Clone)] -pub struct HitRecord { - pub t: f32, - pub point: Vec3, - pub normal: Vec3, -} - -pub trait Hitable { - fn hit(&self, ray: &Ray, time_min: f32, time_max: f32, hit_rec: &mut HitRecord) -> bool; -} - -pub struct HitableList { - inner: Vec>, -} - -impl HitableList { - pub fn new(items: Vec>) -> HitableList { - HitableList { inner: items } - } -} - -impl Hitable for HitableList { - fn hit(&self, ray: &Ray, t_min: f32, t_max: f32, hit_rec: &mut HitRecord) -> bool { - let mut temp_hit_rec: HitRecord = HitRecord { - t: 0.0, - point: Vec3::new(0.0, 0.0, 0.0), - normal: Vec3::new(0.0, 0.0, 0.0), - }; - let mut hit_anything = false; - let mut closest_to_far = t_max; - - for obj in &self.inner { - if obj.hit(&ray, t_min, closest_to_far, &mut temp_hit_rec) { - hit_anything = true; - closest_to_far = hit_rec.t; - hit_rec.point = temp_hit_rec.point; - hit_rec.t = temp_hit_rec.t; - hit_rec.normal = temp_hit_rec.normal; - } - } - hit_anything - } -} diff --git a/ria-weekend/src/main.rs b/ria-weekend/src/main.rs index 30348ca..95d1b98 100644 --- a/ria-weekend/src/main.rs +++ b/ria-weekend/src/main.rs @@ -1,18 +1,13 @@ -#![feature(impl_trait_in_bindings)] - -mod camera; -mod demo; mod demos; -mod hitable; -mod render; -mod shapes; mod types; -use demo::Demo; -use sdl2::{ - event::{Event, WindowEvent}, - keyboard::Keycode, - pixels::PixelFormatEnum, +use { + demos::Demo, + sdl2::{ + event::{Event, WindowEvent}, + keyboard::Keycode, + pixels::PixelFormatEnum, + }, }; const NUM_SAMPLES: u8 = 10; @@ -21,7 +16,7 @@ fn main() -> Result<(), String> { let sdl_ctx = sdl2::init()?; let video_subsys = sdl_ctx.video()?; - let (mut width, mut height): (usize, usize) = (1200, 600); + let (mut width, mut height) = (1280usize, 640usize); let window = video_subsys .window("Ray tracing in a weekend", width as u32, height as u32) @@ -47,7 +42,7 @@ fn main() -> Result<(), String> { //println!("{:?} {:?} {:?}", texture.query(), texture.color_mod(), texture.alpha_mod()); - let active_demo: impl Demo = demos::SurfaceNormalSphere; + let mut active_demo: Box = Box::new(demos::SimpleRectangle); // 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 @@ -61,10 +56,20 @@ fn main() -> Result<(), String> { keycode: Some(Keycode::Escape), .. } => return Ok(()), - Event::KeyUp { - keycode: Some(Keycode::S), - .. - } => active_demo.save_as_ppm(&buffer, width, height), + Event::KeyUp { keycode, .. } => { + match keycode { + Some(Keycode::S) => active_demo.save_as_ppm(&buffer, width, height), + Some(Keycode::Num1) => active_demo = Box::new(demos::SimpleRectangle), + Some(Keycode::Num2) => { + active_demo = Box::new(demos::LinearGradientRectangle) + } + Some(Keycode::Num3) => active_demo = Box::new(demos::SimpleSphere), + Some(Keycode::Num4) => active_demo = Box::new(demos::SurfaceNormalSphere), + None => unreachable!(), + _ => (), + }; + should_update = true; + } Event::Window { win_event: WindowEvent::Resized(w, h), .. diff --git a/ria-weekend/src/render.rs b/ria-weekend/src/render.rs deleted file mode 100644 index ed86306..0000000 --- a/ria-weekend/src/render.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::hitable::{HitRecord, Hitable, HitableList}; -use crate::shapes; -use crate::types::{Ray, Vec3}; -use rand::Rng; -use std::error::Error; -use std::fs::File; -use std::io::Write; - -pub struct Renderer { - width: usize, - height: usize, -} - -impl Renderer { - fn new(width: usize, height: usize) -> Renderer { - Renderer { width, height } - } - - fn update_dimensions(&mut self, width: usize, height: usize) {} - - fn render(&mut self, buf: &mut Vec, world: &HitableList, calc_color: F) - where - F: Fn(Ray, &HitableList) -> Vec3, - { - let w = self.width; - let h = self.height; - // in my case, The resolution is 1200x800 - // These numbers are calculated by first calculating the aspect ratio - // and then just figuring out lower left corner, Width(2 x aspect ratio width) - // Height(2 x aspect ratio height) - let lower_left_corner = Vec3::new(-2.0, -1.0, -1.0); - let horizontal = Vec3::new(4.0, 0.0, 0.0); - let vertical = Vec3::new(0.0, 2.0, 0.0); - let origin = Vec3::new(0.0, 0.0, 0.0); - - let mut offset = 0; - for j in (0..h).rev() { - for i in 0..w { - let u = i as f32 / w as f32; - let v = j as f32 / h as f32; - - let ray = Ray::new(origin, lower_left_corner + horizontal * u + vertical * v); - - let color = calc_color(ray, &world); - let ir = (255.99 * color.r()) as u8; - let ig = (255.99 * color.g()) as u8; - let ib = (255.99 * color.b()) as u8; - - buf[offset] = ir; - buf[offset + 1] = ig; - buf[offset + 2] = ib; - offset += 4; - } - } - } - - fn save_as_ppm(&self, buf: &[u8], name: &str) { - let (width, height) = (self.width, self.height); - let header = format!("P3\n{} {}\n255\n", width, height); - - let mut file = match File::create(&format!("{}-{}x{}.ppm", name, width, height)) { - Ok(file) => file, - Err(e) => panic!("couldn't create {}: {}", name, e), - }; - file.write(header.as_bytes()) - .expect("error in writing file header"); - - for i in buf.chunks(4) { - match file.write(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) { - Ok(_) => (), - Err(e) => panic!("couldn't write to {}: {}", name, e), - } - } - } -} diff --git a/ria-weekend/src/shapes/mod.rs b/ria-weekend/src/shapes/mod.rs deleted file mode 100644 index 7ad4cd6..0000000 --- a/ria-weekend/src/shapes/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod sphere; - -pub use sphere::Sphere; diff --git a/ria-weekend/src/shapes/sphere.rs b/ria-weekend/src/shapes/sphere.rs deleted file mode 100644 index b0995d5..0000000 --- a/ria-weekend/src/shapes/sphere.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::{ - hitable::{HitRecord, Hitable}, - types::{Ray, Vec3}, -}; - -pub struct Sphere { - center: Vec3, - radius: f32, -} - -impl Sphere { - pub fn new(center: Vec3, radius: f32) -> Sphere { - Sphere { center, radius } - } -} - -impl Hitable for Sphere { - fn hit(&self, ray: &Ray, t_min: f32, t_max: f32, hit_rec: &mut HitRecord) -> bool { - let oc = ray.origin() - self.center; - let a = ray.direction().dot(&ray.direction()); - let b = oc.dot(&ray.direction()); - let c = oc.dot(&oc) - self.radius * self.radius; - // TODO: I don't yet understand how 4 was canceled from this equation here - let discriminant = b * b - 4.0 * a * c; - - if discriminant > 0.0 { - let mut root = (-b - discriminant.sqrt()) / a; - if root < t_max && root > t_min { - hit_rec.t = root; - hit_rec.point = ray.point_at_parameter(hit_rec.t); - hit_rec.normal = (hit_rec.point - self.center) / self.radius; - return true; - } - root = (-b + discriminant.sqrt()) / a; - if root < t_max && root > t_min { - hit_rec.t = root; - hit_rec.point = ray.point_at_parameter(hit_rec.t); - hit_rec.normal = (hit_rec.point - self.center) / self.radius; - return true; - } - } - return false; - } -} diff --git a/ria-weekend/src/types/ray.rs b/ria-weekend/src/types/ray.rs index d26221d..8c89470 100644 --- a/ria-weekend/src/types/ray.rs +++ b/ria-weekend/src/types/ray.rs @@ -1,6 +1,5 @@ use crate::types::Vec3; -#[derive(Debug)] pub struct Ray { a: Vec3, b: Vec3, diff --git a/ria-weekend/src/types/vec3.rs b/ria-weekend/src/types/vec3.rs index 99d926d..065e633 100644 --- a/ria-weekend/src/types/vec3.rs +++ b/ria-weekend/src/types/vec3.rs @@ -1,14 +1,15 @@ -use std::ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Sub}; +use std::{ + fmt::{Display, Formatter, Result as FmtResult}, + ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Sub, SubAssign}, +}; -#[derive(Debug, Copy, Clone)] -pub struct Vec3 { - inner: [f32; 3], -} +#[derive(Copy, Clone)] +pub struct Vec3([f32; 3]); impl Vec3 { #[inline] - pub fn new(a: f32, b: f32, c: f32) -> Vec3 { - Vec3 { inner: [a, b, c] } + pub const fn new(a: f32, b: f32, c: f32) -> Vec3 { + Vec3([a, b, c]) } #[inline] pub fn x(&self) -> f32 { @@ -52,20 +53,25 @@ impl Vec3 { #[inline] pub fn cross(&self, v: &Vec3) -> Vec3 { - Vec3 { - inner: [ - self[1] * v[2] - self[2] * v[1], - self[2] * v[0] - self[0] * v[2], - self[0] * v[1] - self[1] * v[0], - ], - } + Vec3([ + self[1] * v[2] - self[2] * v[1], + self[2] * v[0] - self[0] * v[2], + self[0] * v[1] - self[1] * v[0], + ]) } + + #[inline] + pub fn make_unit_vector(&mut self) { + let k = 1.0f32 / (self[0] * self[0] + self[1] * self[1] + self[2] * self[2]); + self[0] *= k; + self[1] *= k; + self[2] *= k; + } + #[inline] pub fn unit_vector(&self) -> Vec3 { let length = self.length(); - Vec3 { - inner: [self[0] / length, self[1] / length, self[2] / length], - } + Vec3([self[0] / length, self[1] / length, self[2] / length]) } } @@ -73,17 +79,15 @@ impl Add for Vec3 { type Output = Vec3; fn add(self, o: Vec3) -> Vec3 { - Vec3 { - inner: [self[0] + o[0], self[1] + o[1], self[2] + o[2]], - } + Vec3([self[0] + o[0], self[1] + o[1], self[2] + o[2]]) } } impl AddAssign for Vec3 { fn add_assign(&mut self, o: Vec3) { - self.inner[0] += o.inner[0]; - self.inner[1] += o.inner[1]; - self.inner[2] += o.inner[2]; + self.0[0] += o.0[0]; + self.0[1] += o.0[1]; + self.0[2] += o.0[2]; } } @@ -91,9 +95,15 @@ impl Sub for Vec3 { type Output = Vec3; fn sub(self, o: Vec3) -> Vec3 { - Vec3 { - inner: [self[0] - o[0], self[1] - o[1], self[2] - o[2]], - } + Vec3([self[0] - o[0], self[1] - o[1], self[2] - o[2]]) + } +} + +impl SubAssign for Vec3 { + fn sub_assign(&mut self, o: Vec3) { + self[0] -= o[0]; + self[1] -= o[1]; + self[2] -= o[2]; } } @@ -116,9 +126,7 @@ impl MulAssign for Vec3 { impl Mul for Vec3 { type Output = Vec3; fn mul(self, o: f32) -> Vec3 { - Vec3 { - inner: [self[0] * o, self[1] * o, self[2] * o], - } + Vec3([self[0] * o, self[1] * o, self[2] * o]) } } @@ -126,9 +134,7 @@ impl Div for Vec3 { type Output = Vec3; fn div(self, o: Vec3) -> Vec3 { - Vec3 { - inner: [self[0] / o[0], self[1] / o[1], self[2] / o[2]], - } + Vec3([self[0] / o[0], self[1] / o[1], self[2] / o[2]]) } } @@ -137,18 +143,16 @@ impl Div for Vec3 { fn div(self, o: f32) -> Vec3 { let o = 1.0 / o; - Vec3 { - inner: [self[0] * o, self[1] * o, self[2] * o], - } + Vec3([self[0] * o, self[1] * o, self[2] * o]) } } impl DivAssign for Vec3 { fn div_assign(&mut self, o: f32) { let o = 1.0 / o; - self.inner[0] /= o; - self.inner[1] /= o; - self.inner[2] /= o; + self.0[0] /= o; + self.0[1] /= o; + self.0[2] /= o; } } @@ -156,12 +160,18 @@ impl Index for Vec3 { type Output = f32; fn index(&self, q: usize) -> &f32 { - &self.inner[q] + &self.0[q] } } impl IndexMut for Vec3 { fn index_mut(&mut self, q: usize) -> &mut f32 { - &mut self.inner[q] + &mut self.0[q] + } +} + +impl Display for Vec3 { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + f.write_fmt(format_args!("{} {} {}", self[0], self[1], self[2])) } }