Re-added demos upto surface normal sphere

This commit is contained in:
Ishan Jain 2020-02-12 22:10:23 +05:30
parent 693965b452
commit c4fd7ab209
17 changed files with 225 additions and 450 deletions

View File

@ -5,5 +5,6 @@ authors = ["ishanjain28 <ishanjain28@gmail.com>"]
edition = "2018"
[dependencies]
sdl2 = "*"
rand = "*"
sdl2 = "0.33.0"
rand = "0.7.3"

View File

@ -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),
}
}
}

View File

@ -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<u8>, 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()),
}
}
}
}

View File

@ -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<u8>, w: usize, h: usize, ns: u8) {
let list: Vec<Box<dyn Hitable>> = 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
}
}

View File

@ -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<u8>, 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<Box<dyn Hitable>> = 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
}
}

View File

@ -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
}

View File

@ -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),
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<u8>, 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

View File

@ -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<u8>, 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());

View File

@ -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<Box<dyn Hitable>>,
}
impl HitableList {
pub fn new(items: Vec<Box<dyn Hitable>>) -> 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
}
}

View File

@ -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<dyn Demo> = 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),
..

View File

@ -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<F>(&mut self, buf: &mut Vec<u8>, 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),
}
}
}
}

View File

@ -1,3 +0,0 @@
pub mod sphere;
pub use sphere::Sphere;

View File

@ -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;
}
}

View File

@ -1,6 +1,5 @@
use crate::types::Vec3;
#[derive(Debug)]
pub struct Ray {
a: Vec3,
b: Vec3,

View File

@ -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<f32> for Vec3 {
impl Mul<f32> 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<Vec3> 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<f32> 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<f32> 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<usize> for Vec3 {
type Output = f32;
fn index(&self, q: usize) -> &f32 {
&self.inner[q]
&self.0[q]
}
}
impl IndexMut<usize> 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]))
}
}