Solved 1c sample 1

This commit is contained in:
Michael Zhang 2023-02-24 01:50:33 -06:00
parent 973e03df4d
commit a725cc2e39
11 changed files with 388 additions and 175 deletions

View file

@ -14,4 +14,4 @@ v 1 -1 -4
v 2 1 -6
f 1 2 3
f 1 3 4
f 1 3 4

View file

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

View file

@ -1,5 +1,3 @@
#![doc = include_str!("../README.md")]
use nalgebra::Vector3;
#[macro_use]

View file

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

View file

@ -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<Option<IntersectionContext>> {
// 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:?}");
}
}

View file

@ -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<Option<IntersectionContext>> {
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)
}
}
}
}

View file

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

View file

@ -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::<usize>().map_err(|e| e.into()))
.collect::<Result<Vec<_>>>()?;
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<f64>),
"viewdir" => scene.view_dir = r!(Vector3<f64>),
"updir" => scene.up_dir = r!(Vector3<f64>),
"hfov" => scene.hfov = r!(f64),
"bkgcolor" => scene.bkg_color = r!(Color),
// light x y z w r g b
"light" => {
let vec3 = r!(Vector3<f64>);
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<f64>);
let w = r!(usize);
let color = r!(Color);
let c = r!(Vector3<f64>);
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<f64>),
"viewdir" => scene.view_dir = r!(Vector3<f64>),
"updir" => scene.up_dir = r!(Vector3<f64>),
// 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<f64>);
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<f64>);
let w = r!(usize);
let color = r!(Color);
let c = r!(Vector3<f64>);
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<Self>
where
I: Iterator<Item = &'a str>,
{
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<Self>
fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result<Self>
where
I: Iterator<Item = &'a str>,
{
@ -240,7 +303,7 @@ impl_construct!(usize);
impl Construct for Vector3<f64> {
type Args = ();
fn construct<'a, I>(it: &mut I, args: Self::Args) -> Result<Self>
fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result<Self>
where
I: Iterator<Item = &'a str>,
{

View file

@ -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<Image>,
pub vertices: Vec<Point>,
pub flat_triangles: Vec<Vector3<usize>>,
pub flat_triangles: Vec<(usize, usize, usize)>,
pub normals: Vec<Vector>,
pub smooth_triangles: Vec<Matrix2x3<usize>>,
pub smooth_triangles: Vec<((usize, usize), (usize, usize), (usize, usize))>,
}

View file

@ -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<Option<IntersectionContext>> {
let a = ray.direction.norm();

View file

@ -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<usize>,
}
impl FlatTriangle {
pub fn intersects_ray_at(
&self,
scene: &Scene,
ray: &Ray,
) -> Result<Option<IntersectionContext>> {
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,
}))
}
}