diff --git a/examples/quads.rs b/examples/quads.rs new file mode 100644 index 0000000..1641a9f --- /dev/null +++ b/examples/quads.rs @@ -0,0 +1,83 @@ +use glam::DVec3; +use raytracer::{ + camera::Camera, + material::Material, + shapes::{quad::Quad, sphere::Sphere}, + textures::Texture, +}; +use std::{io, path::Path}; + +fn main() -> io::Result<()> { + let mut world = vec![]; + + // Materials + let left_red = Material::Lambertian { + albedo: DVec3::new(1.0, 0.2, 0.2).into(), + }; + let back_green = Material::Lambertian { + albedo: DVec3::new(0.2, 1.0, 0.2).into(), + }; + let right_blue = Material::Lambertian { + albedo: DVec3::new(0.2, 0.2, 1.0).into(), + }; + let upper_orange = Material::Lambertian { + albedo: DVec3::new(1.0, 0.5, 0.0).into(), + }; + let lower_teal = Material::Lambertian { + albedo: DVec3::new(0.2, 0.8, 0.8).into(), + }; + + // Quads + world.push(Quad::new( + DVec3::new(-3., -2., 5.), + DVec3::new(0., 0., -4.), + DVec3::new(0., 4., 0.), + left_red, + )); + world.push(Quad::new( + DVec3::new(-2., -2., 0.), + DVec3::new(4., 0., 0.), + DVec3::new(0., 4., 0.), + back_green, + )); + world.push(Quad::new( + DVec3::new(3., -2., 1.), + DVec3::new(0., 0., 4.), + DVec3::new(0., 4., 0.), + right_blue, + )); + world.push(Quad::new( + DVec3::new(-2., 3., 1.), + DVec3::new(4., 0., 0.), + DVec3::new(0., 0., 4.), + upper_orange, + )); + world.push(Quad::new( + DVec3::new(-2., -3., 5.), + DVec3::new(4., 0., 0.), + DVec3::new(0., 0., -4.), + lower_teal, + )); + + let camera = Camera::init() + .image_width(800) + .aspect_ratio(1.) + .look_from(DVec3::new(0., 0., 9.)) + .look_at(DVec3::ZERO) + .vup(DVec3::Y) + .focus_dist(10.0) + .defocus_angle(0.0) + .samples_per_pixel(500) + .max_depth(50) + .vfov(80.) + .build(); + + let filename = Path::new(file!()) + .file_name() + .and_then(|s| s.to_str()) + .unwrap() + .trim_end_matches(".rs"); + camera.render_to_disk(filename, world)?; + + Ok(()) +} diff --git a/src/shapes.rs b/src/shapes.rs index 44409a4..53fa13c 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -1,5 +1,6 @@ use crate::hittable::Hittable; +pub mod quad; pub mod sphere; // are other shapes useful? // possible SDF definitions? @@ -9,6 +10,7 @@ pub mod sphere; pub enum Shapes { Sphere(sphere::Sphere), + Quad(quad::Quad), // RoundedBox(rounded_box::RoundedBox), // Box(a_box::Box), // Cylinder(cylinder::Cylinder), @@ -23,6 +25,9 @@ impl Hittable for Shapes { match self { Shapes::Sphere(object) => { object.hit(ray, interval) + } + Shapes::Quad(object) => { + object.hit(ray, interval) } // Shapes::RoundedBox(object) => { // object.hit(ray, interval) // } diff --git a/src/shapes/quad.rs b/src/shapes/quad.rs new file mode 100644 index 0000000..d2a1e88 --- /dev/null +++ b/src/shapes/quad.rs @@ -0,0 +1,108 @@ +use std::{f64::consts::PI, ops::Range}; + +use glam::DVec3; + +use crate::{ + hittable::{HitRecord, Hittable}, + material::Material, + ray::Ray, + textures::Texture, +}; + +pub struct Quad { + Q: DVec3, + u: DVec3, + v: DVec3, + material: Material, + normal: DVec3, + D: f64, + w: DVec3, +} + +impl Quad { + pub fn new( + Q: DVec3, + u: DVec3, + v: DVec3, + material: Material, + ) -> Self { + let n = u.cross(v); + let normal = n.normalize(); + let D = normal.dot(Q); + let w = n / n.dot(n); + Self { + Q, + u, + v, + material, + normal, + D, + w, + } + } + fn is_interior(a: f64, b: f64) -> Option<(f64, f64)> { + // Given the hit point in plane coordinates, return false if it is outside the + // primitive, otherwise set the hit record UV coordinates and return true. + + if (a < 0.) || (1. < a) || (b < 0.) || (1. < b) { + return None; + } + + // a,b == u,v + Some((a, b)) + } +} + +impl Hittable for Quad { + fn hit( + &self, + ray: &Ray, + interval: Range, + ) -> Option { + let denom = self.normal.dot(ray.direction); + + // No hit if the ray is parallel to the plane. + if denom.abs() < 1e-8 { + return None; + } + // Return false if the hit point parameter t is outside the ray interval. + let t = + (self.D - self.normal.dot(ray.origin)) / denom; + if !interval.contains(&t) { + return None; + } + + // Determine the hit point lies within the planar shape using its plane coordinates. + let intersection = ray.at(t); + let planar_hitpt_vector: DVec3 = + intersection - self.Q; + let alpha = + self.w.dot(planar_hitpt_vector.cross(self.v)); + let beta = + self.w.dot(self.u.cross(planar_hitpt_vector)); + + let Some((u, v)) = Quad::is_interior(alpha, beta) + else { + return None; + }; + // Ray hits the 2D shape; set the rest of the hit record and return true. + // rec.t = t; + // rec.p = intersection; + // rec.mat = mat; + // rec.set_face_normal(r, normal); + + // return true; + + let rec = HitRecord::with_face_normal( + self.material.clone(), + intersection, + self.normal, + t, + ray, + u, + v, + ); + + Some(rec) + } +}