diff --git a/ria-weekend/Cargo.lock b/ria-weekend/Cargo.lock index 175f897..854f3fb 100644 --- a/ria-weekend/Cargo.lock +++ b/ria-weekend/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "autocfg" version = "0.1.2" diff --git a/ria-weekend/examples/surface_normal_sphere.rs b/ria-weekend/examples/surface_normal_sphere.rs deleted file mode 100644 index 71901a6..0000000 --- a/ria-weekend/examples/surface_normal_sphere.rs +++ /dev/null @@ -1,54 +0,0 @@ -extern crate ria_weekend; - -use ria_weekend::{demo::Demo, ray, ray::Ray, vec3::Vec3}; - -fn main() { - let demo = Demo::new("surface_normal_sphere"); - let dimensions = demo.dimensions(); - - let mut buf = String::new(); - // linear interpolation based on y coordinate - // top to down - let 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), 0.5, &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); - 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); - - // start color + end color - Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.0, 0.0, 0.0) * t - }; - - ray::create_ray_demo(&mut buf, dimensions, color); - - demo.save_as_ppm(buf); -} - -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 - - // A-C - let ac = ray.origin() - center; - let a = ray.direction().dot(&ray.direction()); - let b = 2.0 * ac.dot(&ray.direction()); - let c = ac.dot(&ac) - radius * radius; - let discriminant = b * b - 4.0 * a * c; - - if discriminant >= 0.0 { - // return quadratic root - (-b + discriminant.sqrt()) / (2.0 * a) - } else { - -1.0 - } -} diff --git a/ria-weekend/src/demos/linear_interpolation_y.rs b/ria-weekend/src/demos/linear_interpolation_y.rs index 6b1ea62..079d004 100644 --- a/ria-weekend/src/demos/linear_interpolation_y.rs +++ b/ria-weekend/src/demos/linear_interpolation_y.rs @@ -9,9 +9,13 @@ impl crate::Demo for LinearInterpolationY { } fn render(&self, buf: &mut Vec, w: usize, h: usize) { - 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); + // 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(-3.0, -2.0, -1.0); + let horizontal = Vec3::new(6.0, 0.0, 0.0); + let vertical = Vec3::new(0.0, 4.0, 0.0); // Observer position let origin = Vec3::new(0.0, 0.0, 0.0); @@ -25,9 +29,9 @@ impl crate::Demo for LinearInterpolationY { let ray = Ray::new(origin, lower_left_corner + horizontal * u + vertical * v); let color = calc_color(ray); - let ir = (255.99 * color[0]) as u8; - let ig = (255.99 * color[1]) as u8; - let ib = (255.99 * color[2]) as u8; + 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; diff --git a/ria-weekend/src/demos/mod.rs b/ria-weekend/src/demos/mod.rs index 587d108..f3228c9 100644 --- a/ria-weekend/src/demos/mod.rs +++ b/ria-weekend/src/demos/mod.rs @@ -1,7 +1,9 @@ mod linear_interpolation_y; -mod simple_sphere; mod ppm_example; +mod simple_sphere; +mod surface_normal_sphere; pub use linear_interpolation_y::LinearInterpolationY; -pub use simple_sphere::SimpleSphere; pub use ppm_example::PpmExample; +pub use simple_sphere::SimpleSphere; +pub use surface_normal_sphere::SurfaceNormalSphere; diff --git a/ria-weekend/src/demos/ppm_example.rs b/ria-weekend/src/demos/ppm_example.rs index dc750c5..96cfac5 100644 --- a/ria-weekend/src/demos/ppm_example.rs +++ b/ria-weekend/src/demos/ppm_example.rs @@ -12,9 +12,9 @@ impl crate::Demo for PpmExample { for i in 0..w { let color = Vec3::new((i as f32) / (w as f32), (j as f32) / (h as f32), 0.2); - let ir = (255.99 * color[0]) as u8; - let ig = (255.99 * color[1]) as u8; - let ib = (255.99 * color[2]) as u8; + 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; diff --git a/ria-weekend/src/demos/simple_sphere.rs b/ria-weekend/src/demos/simple_sphere.rs index 7a7e6d2..77db1fa 100644 --- a/ria-weekend/src/demos/simple_sphere.rs +++ b/ria-weekend/src/demos/simple_sphere.rs @@ -1,5 +1,7 @@ use crate::{ray::Ray, vec3::Vec3}; +const RADIUS: f32 = 0.8; + pub struct SimpleSphere; impl crate::Demo for SimpleSphere { @@ -8,9 +10,13 @@ impl crate::Demo for SimpleSphere { } fn render(&self, buf: &mut Vec, w: usize, h: usize) { - 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); + // 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(-3.0, -2.0, -1.0); + let horizontal = Vec3::new(6.0, 0.0, 0.0); + let vertical = Vec3::new(0.0, 4.0, 0.0); // Observer position let origin = Vec3::new(0.0, 0.0, 0.0); @@ -24,9 +30,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[0]) as u8; - let ig = (255.99 * color[1]) as u8; - let ib = (255.99 * color[2]) as u8; + 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; @@ -38,15 +44,20 @@ impl crate::Demo for SimpleSphere { } fn ray_hit_sphere(center: Vec3, radius: f32, ray: &Ray) -> bool { - // 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 + // 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 + // 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) + + // Vector from circle center to point + let pc = ray.origin() - center; - // A-C - let ac = ray.origin() - center; let a = ray.direction().dot(&ray.direction()); - let b = 2.0 * ac.dot(&ray.direction()); - let c = ac.dot(&ac) - radius * radius; + let b = 2.0 * pc.dot(&ray.direction()); + let c = pc.dot(&pc) - radius * radius; let discriminant = b * b - 4.0 * a * c; discriminant > 0.0 @@ -55,8 +66,8 @@ 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 half - if ray_hit_sphere(Vec3::new(0.0, 0.0, 1.0), 0.5, &ray) { + // center at z=-1. xy axis cuts sphere in 4 parts + 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 return Vec3::new(1.0, 0.0, 0.0); diff --git a/ria-weekend/src/demos/surface_normal_sphere.rs b/ria-weekend/src/demos/surface_normal_sphere.rs new file mode 100644 index 0000000..e1f114d --- /dev/null +++ b/ria-weekend/src/demos/surface_normal_sphere.rs @@ -0,0 +1,79 @@ +use crate::{demo::Demo, ray, ray::Ray, vec3::Vec3}; + +const RADIUS: f32 = 0.8; +pub struct SurfaceNormalSphere; + +impl crate::Demo for SurfaceNormalSphere { + fn name(&self) -> String { + "surface_normal_sphere".to_owned() + } + + fn render(&self, buf: &mut Vec, w: usize, h: usize) { + // 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(-3.0, -2.0, -1.0); + let horizontal = Vec3::new(6.0, 0.0, 0.0); + let vertical = Vec3::new(0.0, 4.0, 0.0); + // Observer position + let origin = Vec3::new(0.0, 0.0, 0.0); + + let mut offset = 0; + for j in 0..h { + 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); + 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) -> 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); + + // 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 oc = ray.origin() - center; + let a = ray.direction().dot(&ray.direction()); + let b = 2.0 * oc.dot(&ray.direction()); + let c = oc.dot(&oc) - radius * radius; + let discriminant = b * b - 4.0 * a * c; + + if discriminant >= 0.0 { + // return quadratic root + (-b - discriminant.sqrt()) / (2.0 * a) + } else { + -1.0 + } +} diff --git a/ria-weekend/src/main.rs b/ria-weekend/src/main.rs index e378d1d..34f872d 100644 --- a/ria-weekend/src/main.rs +++ b/ria-weekend/src/main.rs @@ -4,7 +4,7 @@ mod ray; mod vec3; use demo::Demo; -use demos::{LinearInterpolationY, PpmExample, SimpleSphere}; +use demos::{LinearInterpolationY, PpmExample, SimpleSphere, SurfaceNormalSphere}; use sdl2::{ event::{Event, WindowEvent}, keyboard::Keycode, @@ -19,7 +19,7 @@ fn main() -> Result<(), String> { let sdl_ctx = sdl2::init()?; let video_subsys = sdl_ctx.video()?; - let (mut width, mut height): (usize, usize) = (500, 500); + let (mut width, mut height): (usize, usize) = (1200, 800); let mut window = video_subsys .window("Ray tracing in a weekend", width as u32, height as u32) @@ -35,7 +35,7 @@ fn main() -> Result<(), String> { .build() .map_err(|e| e.to_string())?; - // Buffer to store a RGBA framebuffer + // RGBA framebuffer let mut buffer = vec![0; height * width * 4]; let texture_creator = canvas.texture_creator(); @@ -43,10 +43,10 @@ fn main() -> Result<(), String> { .create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32) .map_err(|e| e.to_string())?; - let mut active_demo: Box = Box::new(LinearInterpolationY); + let mut active_demo: Box = Box::new(PpmExample); //println!("{:?} {:?} {:?}", texture.query(), texture.color_mod(), texture.alpha_mod()); - + let mut should_update = true; loop { for event in event_pump.poll_iter() { match event { @@ -58,15 +58,31 @@ fn main() -> Result<(), String> { Event::KeyUp { keycode: Some(Keycode::Num1), .. - } => active_demo = Box::new(PpmExample), + } => { + should_update = true; + active_demo = Box::new(PpmExample); + } Event::KeyUp { keycode: Some(Keycode::Num2), .. - } => active_demo = Box::new(LinearInterpolationY), + } => { + should_update = true; + active_demo = Box::new(LinearInterpolationY); + } Event::KeyUp { keycode: Some(Keycode::Num3), .. - } => active_demo = Box::new(SimpleSphere), + } => { + should_update = true; + active_demo = Box::new(SimpleSphere); + } + Event::KeyUp { + keycode: Some(Keycode::Num4), + .. + } => { + should_update = true; + active_demo = Box::new(SurfaceNormalSphere); + } Event::KeyUp { keycode: Some(Keycode::S), .. @@ -81,14 +97,17 @@ fn main() -> Result<(), String> { texture = texture_creator .create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32) .expect("error in resizing texture"); + should_update = true; } _ => {} }; } - - active_demo.render(&mut buffer, width, height); - texture.update(None, &buffer, width * 4); - canvas.copy(&texture, None, None); - canvas.present(); + if should_update { + active_demo.render(&mut buffer, width, height); + texture.update(None, &buffer, width * 4); + canvas.copy(&texture, None, None); + canvas.present(); + should_update = false; + } } } diff --git a/ria-weekend/src/ray.rs b/ria-weekend/src/ray.rs index a1d87e9..6b03b3f 100644 --- a/ria-weekend/src/ray.rs +++ b/ria-weekend/src/ray.rs @@ -1,5 +1,6 @@ use crate::vec3::Vec3; +#[derive(Debug)] pub struct Ray { a: Vec3, b: Vec3, @@ -9,13 +10,16 @@ impl Ray { pub fn new(a: Vec3, b: Vec3) -> Ray { Ray { a, b } } - pub fn origin(&self) -> Vec3 { - return self.a; + #[inline] + pub const fn origin(&self) -> Vec3 { + self.a } - pub fn direction(&self) -> Vec3 { - return self.b; + #[inline] + pub const fn direction(&self) -> Vec3 { + self.b } + #[inline] pub fn point_at_parameter(&self, t: f32) -> Vec3 { - return self.a + self.b * t; + self.a + self.b * t } } diff --git a/ria-weekend/src/vec3.rs b/ria-weekend/src/vec3.rs index 99fcffc..84bd731 100644 --- a/ria-weekend/src/vec3.rs +++ b/ria-weekend/src/vec3.rs @@ -6,43 +6,51 @@ pub struct Vec3 { } impl Vec3 { + #[inline] pub fn new(a: f32, b: f32, c: f32) -> Vec3 { Vec3 { inner: [a, b, c] } } - + #[inline] pub fn x(&self) -> f32 { self[0] } + #[inline] pub fn y(&self) -> f32 { self[1] } + #[inline] pub fn z(&self) -> f32 { self[2] } + #[inline] pub fn r(&self) -> f32 { self[0] } + #[inline] pub fn g(&self) -> f32 { self[1] } + #[inline] pub fn b(&self) -> f32 { self[2] } + #[inline] pub fn length(&self) -> f32 { self.sq_len().sqrt() } + #[inline] pub fn sq_len(&self) -> f32 { - self[0] * self[0] - + self[1] * self[1] - + self[2] * self[2] + self[0] * self[0] + self[1] * self[1] + self[2] * self[2] } + #[inline] pub fn dot(&self, v: &Vec3) -> f32 { self[0] * v[0] + self[1] * v[1] + self[2] * v[2] } + #[inline] pub fn cross(&self, v: &Vec3) -> Vec3 { Vec3 { inner: [ @@ -52,7 +60,7 @@ impl Vec3 { ], } } - + #[inline] pub fn unit_vector(&self) -> Vec3 { let length = self.length(); Vec3 { @@ -66,11 +74,7 @@ impl Add for Vec3 { fn add(self, o: Vec3) -> Vec3 { Vec3 { - inner: [ - self[0] + o[0], - self[1] + o[1], - self[2] + o[2], - ], + inner: [self[0] + o[0], self[1] + o[1], self[2] + o[2]], } } } @@ -80,11 +84,7 @@ impl Sub for Vec3 { fn sub(self, o: Vec3) -> Vec3 { Vec3 { - inner: [ - self[0] - o[0], - self[1] - o[1], - self[2] - o[2], - ], + inner: [self[0] - o[0], self[1] - o[1], self[2] - o[2]], } } } @@ -119,11 +119,7 @@ impl Div for Vec3 { fn div(self, o: Vec3) -> Vec3 { Vec3 { - inner: [ - self[0] / o[0], - self[1] / o[1], - self[2] / o[2], - ], + inner: [self[0] / o[0], self[1] / o[1], self[2] / o[2]], } } }