Minor refactor

This commit is contained in:
Michael Zhang 2023-02-25 15:50:13 -06:00
parent 05462fde28
commit 8ab0a9d139
11 changed files with 130 additions and 81 deletions

View file

@ -1,12 +1,12 @@
use generator::{done, Gn};
use itertools::Itertools;
use std::{ use std::{
fs::File, fs::File,
io::{BufRead, BufReader, Read, Write}, io::{BufRead, BufReader, Read, Write},
path::Path, path::Path,
}; };
use anyhow::Result; use anyhow::{Context, Result};
use generator::{done, Gn};
use itertools::Itertools;
use nalgebra::Vector3; use nalgebra::Vector3;
/// A pixel color represented by a red, green, and blue value in the range 0-1. /// A pixel color represented by a red, green, and blue value in the range 0-1.
@ -38,20 +38,28 @@ impl Image {
let mut line_reader = BufReader::new(r); let mut line_reader = BufReader::new(r);
let mut header = String::new(); let mut header = String::new();
line_reader.read_line(&mut header)?; line_reader
let parts = header.split(" ").collect::<Vec<_>>(); .read_line(&mut header)
.context("Could not read line")?;
let parts = header.trim().split(" ").collect::<Vec<_>>();
let width = parts[1].parse::<usize>()?; let width = parts[1].parse::<usize>().context("Could not read width")?;
let height = parts[2].parse::<usize>()?; let height = parts[2].parse::<usize>().context("Could not read height")?;
let max_value = parts[3].parse::<usize>()?; let max_value = parts[3]
.parse::<usize>()
.context("Could not read max value")?;
// Generator for reading numbers
let numbers = Gn::<()>::new_scoped(move |mut s| { let numbers = Gn::<()>::new_scoped(move |mut s| {
macro_rules! gen_try { macro_rules! gen_try {
($expr:expr) => { ($expr:expr, $str:expr $(, $($arg:expr),* $(,)?)?) => {
match $expr { match $expr {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
s.yield_(Err(anyhow::Error::from(e))); s.yield_(
Err(anyhow::Error::from(e))
.with_context(|| format!($str $(, $($arg,)*)?)),
);
done!(); done!();
} }
} }
@ -59,11 +67,12 @@ impl Image {
} }
for line in line_reader.lines() { for line in line_reader.lines() {
let line = gen_try!(line); let line = gen_try!(line, "Could not read line");
let parts = line.split_whitespace(); let parts = line.trim().split_whitespace();
for part in parts { for part in parts {
let int = gen_try!(part.parse::<f64>()); let int =
gen_try!(part.parse::<u64>(), "Could not read int from: {}", part);
s.yield_(Ok(int)); s.yield_(Ok(int));
} }
} }
@ -78,9 +87,9 @@ impl Image {
None => bail!("Not enough elements"), None => bail!("Not enough elements"),
}; };
let r = r? / max_value as f64; let r = r? as f64 / max_value as f64;
let g = g? / max_value as f64; let g = g? as f64 / max_value as f64;
let b = b? / max_value as f64; let b = b? as f64 / max_value as f64;
let color = Color::new(r, g, b); let color = Color::new(r, g, b);
data.push(color); data.push(color);

View file

@ -112,8 +112,9 @@ fn main() -> Result<()> {
Ok(match earliest_intersection { Ok(match earliest_intersection {
// Take the object's material color // Take the object's material color
Some((obj_idx, intersection_context, object)) => scene Some((obj_idx, intersection_context, object)) => {
.compute_pixel_color(obj_idx, object.material, intersection_context), scene.compute_pixel_color(obj_idx, object, intersection_context)
}
// There was no intersection, so this should default to the scene's // There was no intersection, so this should default to the scene's
// background color // background color

View file

@ -200,7 +200,7 @@ mod tests {
let ray = Ray::from_endpoints(eye, end); let ray = Ray::from_endpoints(eye, end);
let scene = Scene::default(); let scene = Scene::default();
let res = cylinder.intersects_ray_at(&scene, &ray); let _res = cylinder.intersects_ray_at(&scene, &ray);
// panic!("Result: {res:?}"); // panic!("Result: {res:?}");
} }
} }

View file

@ -1,53 +1,11 @@
use std::fmt::Debug; use std::fmt::Debug;
use anyhow::Result;
use crate::image::Color; use crate::image::Color;
use crate::ray::Ray;
use crate::utils::cross; use crate::utils::cross;
use crate::Point; use crate::Point;
use super::cylinder::Cylinder;
use super::illumination::IntersectionContext;
use super::sphere::Sphere;
use super::triangle::Triangle;
use super::Scene; use super::Scene;
#[derive(Debug)]
pub enum ObjectKind {
Sphere(Sphere),
Cylinder(Cylinder),
Triangle(Triangle),
}
impl ObjectKind {
/// Determine where the ray intersects this object, returning the earliest
/// time this happens. Returns None if no intersection occurs.
///
/// Also known as Trace_Ray in the slides, except not the part where it calls
/// Shade_Ray.
pub fn intersects_ray_at(
&self,
scene: &Scene,
ray: &Ray,
) -> Result<Option<IntersectionContext>> {
match self {
ObjectKind::Sphere(sphere) => sphere.intersects_ray_at(scene, ray),
ObjectKind::Cylinder(cylinder) => cylinder.intersects_ray_at(scene, ray),
ObjectKind::Triangle(triangle) => triangle.intersects_ray_at(scene, ray),
}
}
}
/// An object in the scene
#[derive(Debug)]
pub struct Object {
pub kind: ObjectKind,
/// Index into the scene's material color list
pub material: usize,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Rect { pub struct Rect {
pub upper_left: Point, pub upper_left: Point,

View file

@ -25,17 +25,27 @@ impl Scene {
pub fn compute_pixel_color( pub fn compute_pixel_color(
&self, &self,
obj_idx: usize, obj_idx: usize,
material_idx: usize, object: &Object,
intersection_context: IntersectionContext, intersection_context: IntersectionContext,
) -> Color { ) -> Color {
// TODO: Does it make sense to make this function fallible from an API // TODO: Does it make sense to make this function fallible from an API
// design standpoint? // design standpoint?
let material = match self.materials.get(material_idx) { let material = match self.materials.get(object.material_idx) {
Some(v) => v, Some(v) => v,
None => return self.bkg_color, None => return self.bkg_color,
}; };
let ambient_component = material.k_a * material.diffuse_color; let diffuse_color = match object.texture_idx {
Some(texture_idx) => {
let texture = &self.textures[texture_idx];
// TODO: need to implement
material.diffuse_color
}
None => material.diffuse_color,
};
let ambient_component = material.k_a * diffuse_color;
// Diffuse and specular lighting for each separate light // Diffuse and specular lighting for each separate light
let diffuse_and_specular: Color = self let diffuse_and_specular: Color = self
@ -51,7 +61,7 @@ impl Scene {
((light_direction + viewer_direction) / 2.0).normalize(); ((light_direction + viewer_direction) / 2.0).normalize();
let diffuse_component = material.k_d let diffuse_component = material.k_d
* material.diffuse_color * diffuse_color
* dot(normal, light_direction).max(0.0); * dot(normal, light_direction).max(0.0);
let specular_component = material.k_s let specular_component = material.k_s

View file

@ -1,8 +1,6 @@
use std::{ use std::fs::File;
fs::File, use std::io::Read;
io::Read, use std::path::Path;
path::{Path, PathBuf},
};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use itertools::Itertools; use itertools::Itertools;
@ -15,7 +13,7 @@ use crate::{
data::{Attenuation, Light, LightKind, Material, Object}, data::{Attenuation, Light, LightKind, Material, Object},
sphere::Sphere, sphere::Sphere,
triangle::Triangle, triangle::Triangle,
Scene, Scene, texture::Texture,
}, },
Point, Vector, Point, Vector,
}; };
@ -37,7 +35,8 @@ impl Scene {
}; };
let mut scene = Scene::default(); let mut scene = Scene::default();
let mut material_color = None; let mut material_idx = None;
let mut texture_idx = None;
for line in contents.lines() { for line in contents.lines() {
// Split lines into words. `parts' is an iterator, which is consumed upon // Split lines into words. `parts' is an iterator, which is consumed upon
@ -167,7 +166,7 @@ impl Scene {
}; };
let idx = scene.materials.len(); let idx = scene.materials.len();
material_color = Some(idx); material_idx = Some(idx);
scene.materials.push(material); scene.materials.push(material);
} }
@ -177,7 +176,8 @@ impl Scene {
scene.objects.push(Object { scene.objects.push(Object {
kind: ObjectKind::Sphere(Sphere { center, radius }), kind: ObjectKind::Sphere(Sphere { center, radius }),
material: u!(material_color), material_idx: u!(material_idx),
texture_idx,
}); });
} }
@ -194,7 +194,8 @@ impl Scene {
radius, radius,
length, length,
}), }),
material: u!(material_color), material_idx: u!(material_idx),
texture_idx,
}); });
} }
@ -237,7 +238,8 @@ impl Scene {
}; };
scene.objects.push(Object { scene.objects.push(Object {
kind: ObjectKind::Triangle(triangle), kind: ObjectKind::Triangle(triangle),
material: u!(material_color), material_idx: u!(material_idx),
texture_idx,
}); });
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@ -293,8 +295,12 @@ impl Scene {
None => bail!("Did not provide path."), None => bail!("Did not provide path."),
}; };
let idx = scene.textures.len();
texture_idx = Some(idx);
let image = Image::from_file(path)?; let image = Image::from_file(path)?;
scene.textures.push(image); let texture = Texture::new(image);
scene.textures.push(texture);
} }
_ => bail!("Unknown keyword {keyword}"), _ => bail!("Unknown keyword {keyword}"),

View file

@ -3,14 +3,15 @@ pub mod data;
pub mod illumination; pub mod illumination;
pub mod input_file; pub mod input_file;
pub mod sphere; pub mod sphere;
pub mod texture;
pub mod triangle; pub mod triangle;
pub mod object;
use nalgebra::Vector2;
use crate::image::{Color, Image}; use crate::image::{Color, Image};
use crate::{Point, Point2, Vector}; use crate::{Point, Point2, Vector};
use self::data::{Attenuation, DepthCueing, Light, Material, Object}; use self::data::{Attenuation, DepthCueing, Light, Material, Object};
use self::texture::Texture;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Scene { pub struct Scene {
@ -35,7 +36,7 @@ pub struct Scene {
pub objects: Vec<Object>, pub objects: Vec<Object>,
/// List of textures /// List of textures
pub textures: Vec<Image>, pub textures: Vec<Texture>,
/// Coordinates into a texture image /// Coordinates into a texture image
pub texture_vertices: Vec<Point2>, pub texture_vertices: Vec<Point2>,

View file

@ -0,0 +1,47 @@
use anyhow::Result;
use crate::ray::Ray;
use super::cylinder::Cylinder;
use super::illumination::IntersectionContext;
use super::sphere::Sphere;
use super::triangle::Triangle;
use super::Scene;
/// An object in the scene
#[derive(Debug)]
pub struct Object {
pub kind: ObjectKind,
/// Index into the scene's material color list
pub material_idx: usize,
/// If this object has a texture, this is the index into the texture list
pub texture_idx: Option<usize>,
}
#[derive(Debug)]
pub enum ObjectKind {
Sphere(Sphere),
Cylinder(Cylinder),
Triangle(Triangle),
}
impl ObjectKind {
/// Determine where the ray intersects this object, returning the earliest
/// time this happens. Returns None if no intersection occurs.
///
/// Also known as Trace_Ray in the slides, except not the part where it calls
/// Shade_Ray.
pub fn intersects_ray_at(
&self,
scene: &Scene,
ray: &Ray,
) -> Result<Option<IntersectionContext>> {
match self {
ObjectKind::Sphere(sphere) => sphere.intersects_ray_at(scene, ray),
ObjectKind::Cylinder(cylinder) => cylinder.intersects_ray_at(scene, ray),
ObjectKind::Triangle(triangle) => triangle.intersects_ray_at(scene, ray),
}
}
}

View file

@ -0,0 +1,16 @@
use crate::image::{Color, Image};
#[derive(Debug)]
pub struct Texture(Image);
impl Texture {
pub fn new(image: Image) -> Self {
Self(image)
}
/// Returns a pixel at the given coordinate. For non-lattice coordinates,
/// interpolation of the image is done.
pub fn pixel_at(&self, x: f64, y: f64) -> Option<Color> {
todo!()
}
}

View file

@ -4,7 +4,7 @@ use ordered_float::NotNan;
use crate::ray::Ray; use crate::ray::Ray;
use crate::utils::{cross, dot}; use crate::utils::{cross, dot};
use crate::Point;
use super::illumination::IntersectionContext; use super::illumination::IntersectionContext;
use super::Scene; use super::Scene;

View file

@ -15,6 +15,7 @@
packages = (with pkgs; [ packages = (with pkgs; [
cargo-deny cargo-deny
cargo-edit cargo-edit
cargo-expand
cargo-flamegraph cargo-flamegraph
cargo-watch cargo-watch
imagemagick imagemagick