From a725cc2e396514e8131a634a319bd6f147b79e31 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Fri, 24 Feb 2023 01:50:33 -0600 Subject: [PATCH] Solved 1c sample 1 --- assignment-1c/examples/Hw1cSample1.txt | 2 +- assignment-1c/examples/soft-shadow-demo.txt | 45 +++ assignment-1c/src/lib.rs | 2 - assignment-1c/src/main.rs | 2 +- assignment-1c/src/scene/cylinder.rs | 9 +- assignment-1c/src/scene/data.rs | 10 +- assignment-1c/src/scene/illumination.rs | 32 +- assignment-1c/src/scene/input_file.rs | 357 ++++++++++++-------- assignment-1c/src/scene/mod.rs | 7 +- assignment-1c/src/scene/sphere.rs | 2 + assignment-1c/src/scene/triangle.rs | 95 ++++++ 11 files changed, 388 insertions(+), 175 deletions(-) create mode 100644 assignment-1c/examples/soft-shadow-demo.txt create mode 100644 assignment-1c/src/scene/triangle.rs diff --git a/assignment-1c/examples/Hw1cSample1.txt b/assignment-1c/examples/Hw1cSample1.txt index 3411fc7..c286de0 100755 --- a/assignment-1c/examples/Hw1cSample1.txt +++ b/assignment-1c/examples/Hw1cSample1.txt @@ -14,4 +14,4 @@ v 1 -1 -4 v 2 1 -6 f 1 2 3 -f 1 3 4 \ No newline at end of file +f 1 3 4 diff --git a/assignment-1c/examples/soft-shadow-demo.txt b/assignment-1c/examples/soft-shadow-demo.txt new file mode 100644 index 000000000..279e3f1 --- /dev/null +++ b/assignment-1c/examples/soft-shadow-demo.txt @@ -0,0 +1,45 @@ +imsize 640 480 +eye 0 0 15 +viewdir 0 0 -1 +hfov 60 +updir 0 1 0 +bkgcolor 0.5 0.5 0.5 + +depthcueing 0.5 0.5 0.5 1 0.4 60 0 + +light 10 10 -10 1 1 1 1 + +mtlcolor 0.5 1 0.5 1 1 1 0.2 1 0.1 5 +sphere 4.5 4.5 -20 4.5 +sphere -4.5 -4.5 -20 4.5 + +mtlcolor 1 0.5 0.5 1 1 1 0.2 0.8 0 5 +sphere -10 0 -30 4 +sphere -20 0 -30 4 +sphere -30 0 -30 4 +sphere -40 0 -30 4 +sphere 0 0 -30 4 +sphere 10 0 -30 4 +sphere 20 0 -30 4 +sphere 30 0 -30 4 +sphere 40 0 -30 4 + +sphere -10 -10 -30 4 +sphere -20 -10 -30 4 +sphere -30 -10 -30 4 +sphere -40 -10 -30 4 +sphere 0 -10 -30 4 +sphere 10 -10 -30 4 +sphere 20 -10 -30 4 +sphere 30 -10 -30 4 +sphere 40 -10 -30 4 + +sphere -10 10 -30 4 +sphere -20 10 -30 4 +sphere -30 10 -30 4 +sphere -40 10 -30 4 +sphere 0 10 -30 4 +sphere 10 10 -30 4 +sphere 20 10 -30 4 +sphere 30 10 -30 4 +sphere 40 10 -30 4 diff --git a/assignment-1c/src/lib.rs b/assignment-1c/src/lib.rs index db5634b..e2b448e 100644 --- a/assignment-1c/src/lib.rs +++ b/assignment-1c/src/lib.rs @@ -1,5 +1,3 @@ -#![doc = include_str!("../README.md")] - use nalgebra::Vector3; #[macro_use] diff --git a/assignment-1c/src/main.rs b/assignment-1c/src/main.rs index be5cf87..a2bebc2 100644 --- a/assignment-1c/src/main.rs +++ b/assignment-1c/src/main.rs @@ -91,7 +91,7 @@ fn main() -> Result<()> { .iter() .enumerate() .filter_map(|(i, object)| { - match object.kind.intersects_ray_at(&ray) { + match object.kind.intersects_ray_at(&scene, &ray) { Ok(Some(t)) => { // Return both the t and the sphere, because we want to sort on // the t but later retrieve attributes from the sphere diff --git a/assignment-1c/src/scene/cylinder.rs b/assignment-1c/src/scene/cylinder.rs index dc3541e..960c6ba 100644 --- a/assignment-1c/src/scene/cylinder.rs +++ b/assignment-1c/src/scene/cylinder.rs @@ -7,6 +7,7 @@ use crate::Vector; use crate::{ray::Ray, Point}; use super::illumination::IntersectionContext; +use super::Scene; #[derive(Debug)] pub struct Cylinder { @@ -23,6 +24,7 @@ impl Cylinder { /// If there is no intersection point, returns None. pub fn intersects_ray_at( &self, + _: &Scene, ray: &Ray, ) -> Result> { // Determine rotation matrix for turning the cylinder upright along the @@ -180,7 +182,7 @@ impl Cylinder { #[cfg(test)] mod tests { - use crate::{ray::Ray, Point, Vector}; + use crate::{ray::Ray, scene::Scene, Point, Vector}; use super::Cylinder; @@ -197,7 +199,8 @@ mod tests { let end = Point::new(0.0, 2.0, 2.0); let ray = Ray::from_endpoints(eye, end); - let res = cylinder.intersects_ray_at(&ray); - panic!("Result: {res:?}"); + let scene = Scene::default(); + let res = cylinder.intersects_ray_at(&scene, &ray); + // panic!("Result: {res:?}"); } } diff --git a/assignment-1c/src/scene/data.rs b/assignment-1c/src/scene/data.rs index 02aef1f..c1cf907 100644 --- a/assignment-1c/src/scene/data.rs +++ b/assignment-1c/src/scene/data.rs @@ -10,12 +10,14 @@ use crate::Point; use super::cylinder::Cylinder; use super::illumination::IntersectionContext; use super::sphere::Sphere; +use super::triangle::FlatTriangle; use super::Scene; #[derive(Debug)] pub enum ObjectKind { Sphere(Sphere), Cylinder(Cylinder), + FlatTriangle(FlatTriangle), } impl ObjectKind { @@ -26,11 +28,15 @@ impl ObjectKind { /// Shade_Ray. pub fn intersects_ray_at( &self, + scene: &Scene, ray: &Ray, ) -> Result> { match self { - ObjectKind::Sphere(sphere) => sphere.intersects_ray_at(ray), - ObjectKind::Cylinder(cylinder) => cylinder.intersects_ray_at(ray), + ObjectKind::Sphere(sphere) => sphere.intersects_ray_at(scene, ray), + ObjectKind::Cylinder(cylinder) => cylinder.intersects_ray_at(scene, ray), + ObjectKind::FlatTriangle(triangle) => { + triangle.intersects_ray_at(scene, ray) + } } } } diff --git a/assignment-1c/src/scene/illumination.rs b/assignment-1c/src/scene/illumination.rs index 60a46e8..07717f0 100644 --- a/assignment-1c/src/scene/illumination.rs +++ b/assignment-1c/src/scene/illumination.rs @@ -151,13 +151,14 @@ impl Scene { // This list will be a set of opacities let intersections = other_objects .filter_map(|(_, object)| { - let intersection_context = match object.kind.intersects_ray_at(&ray) { - Ok(v) => v, - Err(err) => { - error!("Error while performing shadow casting: {err}"); - None - } - }?; + let intersection_context = + match object.kind.intersects_ray_at(&self, &ray) { + Ok(v) => v, + Err(err) => { + error!("Error while performing shadow casting: {err}"); + None + } + }?; let intersection_time = *intersection_context.time; match light.kind { @@ -228,14 +229,15 @@ impl Scene { direction, }; - let intersection_context = match object.kind.intersects_ray_at(&ray) { - Ok(Some(v)) => v, - Ok(None) => return false, - Err(err) => { - error!("Error while performing shadow casting: {err}"); - return false; - } - }; + let intersection_context = + match object.kind.intersects_ray_at(&self, &ray) { + Ok(Some(v)) => v, + Ok(None) => return false, + Err(err) => { + error!("Error while performing shadow casting: {err}"); + return false; + } + }; let light_time = (location - ray.origin).norm(); let intersection_time = *intersection_context.time; diff --git a/assignment-1c/src/scene/input_file.rs b/assignment-1c/src/scene/input_file.rs index d28277d..a75bca7 100644 --- a/assignment-1c/src/scene/input_file.rs +++ b/assignment-1c/src/scene/input_file.rs @@ -1,8 +1,8 @@ -use std::{fs::File, io::Read, path::Path, str::FromStr}; +use std::{fs::File, io::Read, path::Path}; use anyhow::Result; use itertools::Itertools; -use nalgebra::{Vector2, Vector3}; +use nalgebra::Vector3; use crate::{ image::Color, @@ -10,6 +10,7 @@ use crate::{ cylinder::Cylinder, data::{Attenuation, Light, LightKind, Material, Object}, sphere::Sphere, + triangle::FlatTriangle, Scene, }, Point, Vector, @@ -33,30 +34,16 @@ impl Scene { let mut material_color = None; for line in contents.lines() { + // Split lines into words, and identify the keyword let mut parts = line.split_whitespace(); let keyword = match parts.next() { Some(v) => v, None => continue, }; - if keyword == "imsize" { - let parts = parts - .map(|s| s.parse::().map_err(|e| e.into())) - .collect::>>()?; - if let [width, height] = parts[..] { - scene.image_width = width; - scene.image_height = height; - } - } else if keyword == "projection" { - if let Some("parallel") = parts.next() { - scene.parallel_projection = true; - } - } - // Do float parsing instead - else { - /// Shortcut for reading something from the iterator and converting it - /// into the appropriate format - macro_rules! r { + /// Shortcut for reading something from the iterator and converting it + /// into the appropriate format + macro_rules! r { ($ty:ty) => { <$ty>::construct(&mut parts, ())? }; @@ -66,138 +53,214 @@ impl Scene { }; } - /// Shortcut for getting material color - macro_rules! mat { - () => { - match material_color { - Some(v) => v, - None => { - bail!("Each sphere must be preceded by a `mtlcolor` line") - } + /// Shortcut for getting material color + macro_rules! mat { + () => { + match material_color { + Some(v) => v, + None => { + bail!("Each sphere must be preceded by a `mtlcolor` line") } + } + }; + } + + match keyword { + "imsize" => { + scene.image_width = r!(usize); + scene.image_height = r!(usize); + } + "projection" => { + if let Some("parallel") = parts.next() { + scene.parallel_projection = true; + } + } + + "eye" => scene.eye_pos = r!(Vector3), + "viewdir" => scene.view_dir = r!(Vector3), + "updir" => scene.up_dir = r!(Vector3), + + "hfov" => scene.hfov = r!(f64), + "bkgcolor" => scene.bkg_color = r!(Color), + + // light x y z w r g b + "light" => { + let vec3 = r!(Vector3); + let w = r!(usize); + let color = r!(Color); + + let kind = match w as usize { + 0 => LightKind::Directional { direction: vec3 }, + 1 => LightKind::Point { + location: vec3, + attenuation: None, + }, + _ => bail!("Invalid w; must be either 0 or 1"), + }; + + let light = Light { kind, color }; + scene.lights.push(light); + } + + // attlight x y z w r g b c1 c2 c3 + "attlight" => { + let vec3 = r!(Vector3); + let w = r!(usize); + let color = r!(Color); + let c = r!(Vector3); + + let kind = match w as usize { + 0 => LightKind::Directional { direction: vec3 }, + 1 => LightKind::Point { + location: vec3, + attenuation: Some(Attenuation { + c1: c.x, + c2: c.y, + c3: c.z, + }), + }, + _ => bail!("Invalid w; must be either 0 or 1"), + }; + + let light = Light { kind, color }; + scene.lights.push(light); + } + + // depthcueing dcr dcg dcb amax amin distmax distmin + "depthcueing" => { + let color = r!(Color); + let a_max = r!(f64); + let a_min = r!(f64); + let dist_max = r!(f64); + let dist_min = r!(f64); + + scene.depth_cueing = DepthCueing { + color, + a_max, + a_min, + dist_max, + dist_min, }; } - match keyword { - "eye" => scene.eye_pos = r!(Vector3), - "viewdir" => scene.view_dir = r!(Vector3), - "updir" => scene.up_dir = r!(Vector3), + // mtlcolor Odr Odg Odb Osr Osg Osb ka kd ks n + "mtlcolor" => { + let diffuse_color = r!(Color); + let specular_color = r!(Color); + let k_a = r!(f64); + let k_d = r!(f64); + let k_s = r!(f64); + let exponent = r!(f64); - "hfov" => scene.hfov = r!(f64), - "bkgcolor" => scene.bkg_color = r!(Color), + let material = Material { + diffuse_color, + specular_color, + k_a, + k_d, + k_s, + exponent, + }; - // light x y z w r g b - "light" => { - let vec3 = r!(Vector3); - let w = r!(usize); - let color = r!(Color); - - let kind = match w as usize { - 0 => LightKind::Directional { direction: vec3 }, - 1 => LightKind::Point { - location: vec3, - attenuation: None, - }, - _ => bail!("Invalid w; must be either 0 or 1"), - }; - - let light = Light { kind, color }; - scene.lights.push(light); - } - - // attlight x y z w r g b c1 c2 c3 - "attlight" => { - let vec3 = r!(Vector3); - let w = r!(usize); - let color = r!(Color); - let c = r!(Vector3); - - let kind = match w as usize { - 0 => LightKind::Directional { direction: vec3 }, - 1 => LightKind::Point { - location: vec3, - attenuation: Some(Attenuation { - c1: c.x, - c2: c.y, - c3: c.z, - }), - }, - _ => bail!("Invalid w; must be either 0 or 1"), - }; - - let light = Light { kind, color }; - scene.lights.push(light); - } - - // depthcueing dcr dcg dcb amax amin distmax distmin - "depthcueing" => { - let color = r!(Color); - let a_max = r!(f64); - let a_min = r!(f64); - let dist_max = r!(f64); - let dist_min = r!(f64); - - scene.depth_cueing = DepthCueing { - color, - a_max, - a_min, - dist_max, - dist_min, - }; - } - - // mtlcolor Odr Odg Odb Osr Osg Osb ka kd ks n - "mtlcolor" => { - let diffuse_color = r!(Color); - let specular_color = r!(Color); - let k_a = r!(f64); - let k_d = r!(f64); - let k_s = r!(f64); - let exponent = r!(f64); - - let material = Material { - diffuse_color, - specular_color, - k_a, - k_d, - k_s, - exponent, - }; - - let idx = scene.materials.len(); - material_color = Some(idx); - scene.materials.push(material); - } - - "sphere" => { - let center = r!(Point); - let radius = r!(f64); - - scene.objects.push(Object { - kind: ObjectKind::Sphere(Sphere { center, radius }), - material: mat!(), - }); - } - - "cylinder" => { - let center = r!(Point); - let direction = r!(Vector); - let radius = r!(f64); - let length = r!(f64); - - scene.objects.push(Object { - kind: ObjectKind::Cylinder(Cylinder { - center, - direction, - radius, - length, - }), - material: mat!(), - }); - } - - _ => bail!("Unknown keyword {keyword}"), + let idx = scene.materials.len(); + material_color = Some(idx); + scene.materials.push(material); } + + "sphere" => { + let center = r!(Point); + let radius = r!(f64); + + scene.objects.push(Object { + kind: ObjectKind::Sphere(Sphere { center, radius }), + material: mat!(), + }); + } + + "cylinder" => { + let center = r!(Point); + let direction = r!(Vector); + let radius = r!(f64); + let length = r!(f64); + + scene.objects.push(Object { + kind: ObjectKind::Cylinder(Cylinder { + center, + direction, + radius, + length, + }), + material: mat!(), + }); + } + + // Assignment 1C: Triangles and textures + + // v x y z + "v" => scene.vertices.push(r!(Vector)), + + // vn nx ny nz + "vn" => scene.normals.push(r!(Vector)), + + // f v1 v2 v3 + // f v1//n1 v2//n2 v3//n3 + "f" => { + use TriangleVertex::*; + + let v1 = r!(TriangleVertex); + let v2 = r!(TriangleVertex); + let v3 = r!(TriangleVertex); + + match (v1, v2, v3) { + (Flat(v1), Flat(v2), Flat(v3)) => { + let vertices = Vector3::new(v1, v2, v3); + scene.objects.push(Object { + kind: ObjectKind::FlatTriangle(FlatTriangle { vertices }), + material: mat!(), + }); + } + (Smooth(v1, n1), Smooth(v2, n2), Smooth(v3, n3)) => { + scene.smooth_triangles.push(((v1, n1), (v2, n2), (v3, n3))) + } + _ => bail!("Must all be either v_idx or v_idx//n_idx"), + } + + enum TriangleVertex { + Flat(usize), + Smooth(usize, usize), + } + + impl Construct for TriangleVertex { + type Args = (); + + fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result + where + I: Iterator, + { + let s = match it.next() { + Some(v) => v, + None => bail!("Waiting on another"), + }; + + // Note: indexed by 1 not 0, so we will just do the subtraction + // here to avoid having to deal with it later + let parts = s.split("//").collect_vec(); + ensure!(parts.len() >= 1 && parts.len() <= 2); + let v_idx: usize = parts[0].parse()?; + Ok(match parts.len() { + 1 => TriangleVertex::Flat(v_idx - 1), + 2 => { + let n_idx: usize = parts[1].parse()?; + TriangleVertex::Smooth(v_idx - 1, n_idx - 1) + } + _ => bail!("Invalid"), + }) + } + } + } + + "texture" => {} + + _ => bail!("Unknown keyword {keyword}"), } } @@ -219,7 +282,7 @@ macro_rules! impl_construct { impl Construct for $ty { type Args = (); - fn construct<'a, I>(it: &mut I, args: Self::Args) -> Result + fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result where I: Iterator, { @@ -240,7 +303,7 @@ impl_construct!(usize); impl Construct for Vector3 { type Args = (); - fn construct<'a, I>(it: &mut I, args: Self::Args) -> Result + fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result where I: Iterator, { diff --git a/assignment-1c/src/scene/mod.rs b/assignment-1c/src/scene/mod.rs index f35c0fe..4746889 100644 --- a/assignment-1c/src/scene/mod.rs +++ b/assignment-1c/src/scene/mod.rs @@ -3,8 +3,7 @@ pub mod data; pub mod illumination; pub mod input_file; pub mod sphere; - -use nalgebra::{Matrix2x3, Vector3}; +pub mod triangle; use crate::image::{Color, Image}; use crate::{Point, Vector}; @@ -35,8 +34,8 @@ pub struct Scene { pub textures: Vec, pub vertices: Vec, - pub flat_triangles: Vec>, + pub flat_triangles: Vec<(usize, usize, usize)>, pub normals: Vec, - pub smooth_triangles: Vec>, + pub smooth_triangles: Vec<((usize, usize), (usize, usize), (usize, usize))>, } diff --git a/assignment-1c/src/scene/sphere.rs b/assignment-1c/src/scene/sphere.rs index aa24e49..283ff73 100644 --- a/assignment-1c/src/scene/sphere.rs +++ b/assignment-1c/src/scene/sphere.rs @@ -4,6 +4,7 @@ use ordered_float::NotNan; use crate::{ray::Ray, utils::min_f64, Point}; use super::illumination::IntersectionContext; +use super::Scene; #[derive(Debug)] pub struct Sphere { @@ -18,6 +19,7 @@ impl Sphere { /// If there is no intersection point, returns None. pub fn intersects_ray_at( &self, + _: &Scene, ray: &Ray, ) -> Result> { let a = ray.direction.norm(); diff --git a/assignment-1c/src/scene/triangle.rs b/assignment-1c/src/scene/triangle.rs new file mode 100644 index 000000000..27e64ce --- /dev/null +++ b/assignment-1c/src/scene/triangle.rs @@ -0,0 +1,95 @@ +use anyhow::Result; +use nalgebra::{Matrix2, Vector2, Vector3}; +use ordered_float::NotNan; + +use crate::ray::Ray; +use crate::utils::{cross, dot}; +use crate::Point; + +use super::illumination::IntersectionContext; +use super::Scene; + +#[derive(Debug)] +pub struct FlatTriangle { + pub vertices: Vector3, +} + +impl FlatTriangle { + pub fn intersects_ray_at( + &self, + scene: &Scene, + ray: &Ray, + ) -> Result> { + let p0 = scene.vertices[self.vertices.x]; + let p1 = scene.vertices[self.vertices.y]; + let p2 = scene.vertices[self.vertices.z]; + + // Solve for the plane equation coefficients A, B, C, D such that: + // + // $$ + // Ax + By + Cz + D = 0 + // $$ + let e1 = p1 - p0; + let e2 = p2 - p0; + let n = cross(e1, e2); + let a = n.x; + let b = n.y; + let c = n.z; + + // Sub in p0 to solve for D + let d = -(a * p0.x + b * p0.y + c * p0.z); + + // Find the intersection point + let time = { + let (x0, y0, z0, xd, yd, zd) = + match (ray.origin.as_slice(), ray.direction.as_slice()) { + ([x0, y0, z0], [xd, yd, zd]) => (x0, y0, z0, xd, yd, zd), + _ => unreachable!("lol rip no tuple interface"), + }; + let denom = a * xd + b * yd + c * zd; + if denom == 0.0 { + // The ray is parallel to the plane, so there is no intersection point. + return Ok(None); + }; + -(a * x0 + b * y0 + c * z0 + d) / denom + }; + + let time = NotNan::new(time)?; + let point = ray.eval(*time); + + // Use barycentric coordinates to determine if the point is inside of the + // triangle + { + // p = p0 + beta * e1 + gamma * e2 + // Using the whack linear algebra approach derived on slide 57 + let ep = point - p0; + let d = Matrix2::new(dot(e1, e1), dot(e1, e2), dot(e2, e1), dot(e2, e2)); + let p = Vector2::new(dot(e1, ep), dot(e2, ep)); + + let d_inv = match d.try_inverse() { + Some(v) => v, + // TODO: Whack + None => return Ok(None), + }; + + let sol = d_inv * p; + let beta = sol.x; + let gamma = sol.y; + + // Slide 46 + let alpha = 1.0 - beta - gamma; + + // Each of alpha, beta, and gamma must be between 0 and 1 + if ![alpha, beta, gamma].into_iter().all(|v| 0.0 < v && v < 1.0) { + return Ok(None); + } + } + let normal = n.normalize(); + + Ok(Some(IntersectionContext { + time, + point, + normal, + })) + } +}