From 7cba8998c57467b0be0ac8b7ad10dab509f3a469 Mon Sep 17 00:00:00 2001 From: ishanjain28 Date: Thu, 7 Mar 2019 06:03:46 +0530 Subject: [PATCH] Refactored codebase, Added sdl2 along with some shortcuts to switch between different renders and save them on disk in ppm format --- ria-weekend/examples/ppm.rs | 13 --- ria-weekend/examples/ray_demo.rs | 38 ------- ria-weekend/examples/surface_normal_sphere.rs | 54 +++++++++ ria-weekend/src/demo.rs | 89 ++------------- ria-weekend/src/lib.rs | 5 - ria-weekend/src/linear_interpolation_y.rs | 48 ++++++++ ria-weekend/src/main.rs | 105 ++++++++++++++++++ ria-weekend/src/ppm.rs | 15 --- ria-weekend/src/ppm_example.rs | 28 +++++ ria-weekend/src/ray.rs | 32 ------ ria-weekend/src/simple_sphere.rs | 72 ++++++++++++ 11 files changed, 319 insertions(+), 180 deletions(-) delete mode 100644 ria-weekend/examples/ppm.rs delete mode 100644 ria-weekend/examples/ray_demo.rs create mode 100644 ria-weekend/examples/surface_normal_sphere.rs delete mode 100644 ria-weekend/src/lib.rs create mode 100644 ria-weekend/src/linear_interpolation_y.rs create mode 100644 ria-weekend/src/main.rs delete mode 100644 ria-weekend/src/ppm.rs create mode 100644 ria-weekend/src/ppm_example.rs create mode 100644 ria-weekend/src/simple_sphere.rs diff --git a/ria-weekend/examples/ppm.rs b/ria-weekend/examples/ppm.rs deleted file mode 100644 index 21fd10a..0000000 --- a/ria-weekend/examples/ppm.rs +++ /dev/null @@ -1,13 +0,0 @@ -extern crate ria_weekend; - -use ria_weekend::{ppm, demo::Demo}; - -fn main() { - let demo = Demo::new("ppm_sample"); - let dimensions = demo.dimensions(); - let mut buf = String::new(); - - ppm::create_sample(&mut buf, dimensions); - - demo.save_as_ppm(buf); -} diff --git a/ria-weekend/examples/ray_demo.rs b/ria-weekend/examples/ray_demo.rs deleted file mode 100644 index b0b7ba9..0000000 --- a/ria-weekend/examples/ray_demo.rs +++ /dev/null @@ -1,38 +0,0 @@ -extern crate ria_weekend; -extern crate sdl2; - -use ria_weekend::{demo::Demo, ray, ray::Ray, vec3::Vec3}; -use sdl2::{pixels, rect::Rect}; - -fn main() { - let (width, height): (usize, usize) = (500, 500); - let mut demo = Demo::new("ray_demo", width, height).expect("error occurred"); - - let texture_creator = demo.canvas.texture_creator(); - - // linear interpolation based on y coordinate - // top to down - let linear_interpolate_y = |ray: Ray| -> Vec3 { - let unit_direction = ray.direction().unit_vector(); - let t = 0.5 * (unit_direction.y() + 1.0); - // (1.0 - t) * start blend_color + t * end color - Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.0, 0.0, 0.0) * t - }; - let mut texture = texture_creator - .create_texture_streaming(pixels::PixelFormatEnum::RGB888, width as u32, height as u32) - .map_err(|e| e.to_string()) - .expect("error in creating texture"); - let mut buf = Vec::new(); - ray::create_ray_demo( - &mut buf, - (width as u32, height as u32), - linear_interpolate_y, - ); - - texture.update(Rect::new(0, 0, width as u32, height as u32), &buf, 20); - - demo.canvas.copy(&texture, None, None).unwrap(); - demo.canvas.present(); - demo.save_as_ppm(&buf); - demo.start().unwrap(); -} diff --git a/ria-weekend/examples/surface_normal_sphere.rs b/ria-weekend/examples/surface_normal_sphere.rs new file mode 100644 index 0000000..71901a6 --- /dev/null +++ b/ria-weekend/examples/surface_normal_sphere.rs @@ -0,0 +1,54 @@ +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/demo.rs b/ria-weekend/src/demo.rs index 0832ccb..7fed171 100644 --- a/ria-weekend/src/demo.rs +++ b/ria-weekend/src/demo.rs @@ -1,91 +1,26 @@ -extern crate sdl2; - -use sdl2::{ - event::Event, - keyboard::Keycode, - pixels::PixelFormatEnum, - rect::Rect, - render::{Canvas, Texture, TextureValueError}, - video::Window, - EventPump, Sdl, -}; use std::error::Error; use std::fs::File; -use std::{io, io::Write}; +use std::io::Write; -pub struct Demo<'a> { - pub width: usize, - pub height: usize, - pub project_name: &'a str, - pub canvas: Canvas, - pub sdl_ctx: Sdl, -} +pub trait Demo { + fn render(&self, buf: &mut Vec, width: usize, height: usize); + fn name(&self) -> String; -impl<'a> Demo<'a> { - pub fn new(project_name: &str, width: usize, height: usize) -> Result { - let sdl_ctx = sdl2::init()?; - let video_subsys = sdl_ctx.video()?; + fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize) { + let header = format!("P3\n{} {}\n255\n", width, height); - let window = video_subsys - .window(project_name, width as u32, height as u32) - .position_centered() - .build() - .map_err(|e| e.to_string())?; - let mut canvas = window - .into_canvas() - .target_texture() - .build() - .map_err(|e| e.to_string())?; - - Ok(Demo { - width, - height, - canvas, - project_name, - sdl_ctx, - }) - } - - pub fn save_as_ppm(&self, buf: &[u8]) { - let mut header = format!("P3\n{} {}\n255\n", self.width, self.height); - - let mut file = match File::create(&format!( - "{}-{}x{}.ppm", - self.project_name, self.width, self.height - )) { + let mut file = match File::create(&format!("{}-{}x{}.ppm", self.name(), width, height)) { Ok(file) => file, - Err(e) => panic!("couldn't create {}: {}", self.project_name, e.description()), + Err(e) => panic!("couldn't create {}: {}", self.name(), e.description()), }; file.write(header.as_bytes()) .expect("error in writing file header"); - match file.write_all(&buf) { - Ok(_) => println!("Succesfully wrote to {}", self.project_name), - Err(e) => panic!( - "couldn't write to {}: {}", - self.project_name, - e.description() - ), - } - } - pub fn start(&mut self) -> Result<(), String> { - let mut event_pump = self.sdl_ctx.event_pump()?; - - loop { - for event in event_pump.poll_iter() { - match event { - Event::Quit { .. } - | Event::KeyDown { - keycode: Some(Keycode::Escape), - .. - } => return Ok(()), - _ => {} - }; + 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()), } } } - - pub fn dimensions(&self) -> (usize, usize) { - (self.width, self.height) - } } diff --git a/ria-weekend/src/lib.rs b/ria-weekend/src/lib.rs deleted file mode 100644 index ba1572e..0000000 --- a/ria-weekend/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod ppm; -pub mod vec3; -pub mod ray; -pub mod demo; -pub mod stage; diff --git a/ria-weekend/src/linear_interpolation_y.rs b/ria-weekend/src/linear_interpolation_y.rs new file mode 100644 index 0000000..fb20378 --- /dev/null +++ b/ria-weekend/src/linear_interpolation_y.rs @@ -0,0 +1,48 @@ +use crate::ray::Ray; +use crate::vec3::Vec3; + +pub struct LinearInterpolationY; + +impl crate::Demo for LinearInterpolationY { + fn name(&self) -> String { + "linear_interpolation_y".to_owned() + } + + 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); + // 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 { + // relative offsets + // current position to total width/length + 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); + let ir = (255.99 * color[0]) as u8; + let ig = (255.99 * color[1]) as u8; + let ib = (255.99 * color[2]) as u8; + + buf[offset] = ir; + buf[offset + 1] = ig; + buf[offset + 2] = ib; + buf[offset + 3] = 0; + offset += 4; + } + } + } +} + +#[inline] +fn calc_color(ray: Ray) -> Vec3 { + let unit_direction = ray.direction().unit_vector(); + let t = 0.5 * (unit_direction.y() + 1.0); + // (1.0 - t) * start blend_color + t * 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/main.rs b/ria-weekend/src/main.rs new file mode 100644 index 0000000..431e6b6 --- /dev/null +++ b/ria-weekend/src/main.rs @@ -0,0 +1,105 @@ +mod demo; +mod linear_interpolation_y; +mod ppm_example; +mod ray; +mod simple_sphere; +mod vec3; + +use demo::Demo; +use linear_interpolation_y::LinearInterpolationY; +use ppm_example::PpmExample; +use sdl2::{ + event::{Event, WindowEvent}, + keyboard::Keycode, + pixels::PixelFormatEnum, + rect::Rect, + render::{Canvas, Texture, TextureValueError}, + video::Window, + EventPump, Sdl, +}; +use simple_sphere::SimpleSphere; +use vec3::Vec3; + +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 window = video_subsys + .window("Ray tracing in a weekend", width as u32, height as u32) + .position_centered() + .build() + .map_err(|e| e.to_string())?; + + let mut event_pump = sdl_ctx.event_pump()?; + + let mut canvas = window + .into_canvas() + .target_texture() + .build() + .map_err(|e| e.to_string())?; + + // Buffer to store a RGBA framebuffer + let mut buffer = vec![0; height * width * 4]; + + let texture_creator = canvas.texture_creator(); + let mut texture = texture_creator + .create_texture_static(PixelFormatEnum::RGB888, width as u32, height as u32) + .map_err(|e| e.to_string())?; + + let mut active_demo: Box = Box::new(LinearInterpolationY); + + loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => return Ok(()), + Event::KeyUp { + keycode: Some(Keycode::Num1), + .. + } => { + active_demo = Box::new(PpmExample); + } + Event::KeyUp { + keycode: Some(Keycode::Num2), + .. + } => { + active_demo = Box::new(LinearInterpolationY); + } + Event::KeyUp { + keycode: Some(Keycode::Num3), + .. + } => { + active_demo = Box::new(SimpleSphere); + } + Event::KeyUp { + keycode: Some(Keycode::S), + .. + } => { + active_demo.save_as_ppm(&buffer, width, height); + } + Event::Window { + win_event: WindowEvent::Resized(w, h), + .. + } => { + width = w as usize; + height = h as usize; + buffer.resize(width * height * 4, 0); + texture = texture_creator + .create_texture_static(PixelFormatEnum::RGB888, width as u32, height as u32) + .expect("error in resizing texture"); + } + _ => {} + }; + } + + active_demo.render(&mut buffer, width, height); + texture.update(None, &buffer, width * 4); + canvas.copy(&texture, None, None); + canvas.present(); + } +} diff --git a/ria-weekend/src/ppm.rs b/ria-weekend/src/ppm.rs deleted file mode 100644 index 2fdc666..0000000 --- a/ria-weekend/src/ppm.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::vec3::Vec3; - -pub fn create_sample(buf: &mut String, dimensions: (u32, u32)) { - let (w, h ) = dimensions; - for j in (0..h).rev() { - for i in 0..w { - let color = Vec3::new((i as f32) / (w as f32), (j as f32) / (h as f32), 0.5_f32); - - let ir = (255.99 * color[0]) as u8; - let ig = (255.99 * color[1]) as u8; - let ib = (255.99 * color[2]) as u8; - buf.push_str(&format!("{} {} {}\n", ir, ig, ib)); - } - } -} diff --git a/ria-weekend/src/ppm_example.rs b/ria-weekend/src/ppm_example.rs new file mode 100644 index 0000000..c1206f3 --- /dev/null +++ b/ria-weekend/src/ppm_example.rs @@ -0,0 +1,28 @@ +use crate::vec3::Vec3; +pub struct PpmExample; + +impl crate::Demo for PpmExample { + fn name(&self) -> String { + "ppm_example".to_owned() + } + + fn render(&self, buf: &mut Vec, w: usize, h: usize) { + let mut offset = 0; + for j in (0..h) { + 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; + + buf[offset] = ir; + buf[offset + 1] = ig; + buf[offset + 2] = ib; + buf[offset + 3] = 255; + + offset += 4; + } + } + } +} diff --git a/ria-weekend/src/ray.rs b/ria-weekend/src/ray.rs index 8fb1495..a1d87e9 100644 --- a/ria-weekend/src/ray.rs +++ b/ria-weekend/src/ray.rs @@ -19,35 +19,3 @@ impl Ray { return self.a + self.b * t; } } - -pub fn create_ray_demo(buf: &mut Vec, dimensions: (u32, u32), op: F) -where - F: Fn(Ray) -> Vec3, -{ - let (w, h) = dimensions; - - // uses standard cg RHS notation - // y up, z pointing outwards and x to right - let lower_left_corner = Vec3::new(-1.0, -1.0, -1.0); - let horizontal = Vec3::new(2.0, 0.0, 0.0); - let vertical = Vec3::new(0.0, 2.0, 0.0); - // observer - let origin = Vec3::new(0.0, 0.0, 0.0); - - for j in (0..h).rev() { - for i in 0..w { - // relative offsets - // current position to total width/length - 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 = op(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; - buf.extend([ir, ig, ib].iter()); - } - } -} diff --git a/ria-weekend/src/simple_sphere.rs b/ria-weekend/src/simple_sphere.rs new file mode 100644 index 0000000..b1a9f01 --- /dev/null +++ b/ria-weekend/src/simple_sphere.rs @@ -0,0 +1,72 @@ +use crate::{ray::Ray, vec3::Vec3}; + +pub struct SimpleSphere; + +impl crate::Demo for SimpleSphere { + fn name(&self) -> String { + "simple_sphere".to_owned() + } + + 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); + // 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 { + // relative offsets + // current position to total width/length + 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); + let ir = (255.99 * color[0]) as u8; + let ig = (255.99 * color[1]) as u8; + let ib = (255.99 * color[2]) as u8; + + buf[offset] = ir; + buf[offset + 1] = ig; + buf[offset + 2] = ib; + buf[offset + 3] = 0; + offset += 4; + } + } + } +} + +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 + + // 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; + + discriminant > 0.0 +} + +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) { + // 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); + } + 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 +}