Refactor with macros

This commit is contained in:
Michael Zhang 2023-02-24 00:46:22 -06:00
parent 00000540b4
commit 973e03df4d

View file

@ -1,15 +1,18 @@
use std::{fs::File, io::Read, path::Path}; use std::{fs::File, io::Read, path::Path, str::FromStr};
use anyhow::Result; use anyhow::Result;
use itertools::Itertools;
use nalgebra::{Vector2, Vector3};
use crate::{ use crate::{
image::Color,
scene::{ scene::{
cylinder::Cylinder, cylinder::Cylinder,
data::{Attenuation, Light, LightKind, Material, Object}, data::{Attenuation, Light, LightKind, Material, Object},
sphere::Sphere, sphere::Sphere,
Scene, Scene,
}, },
Point, Point, Vector,
}; };
use super::data::{DepthCueing, ObjectKind}; use super::data::{DepthCueing, ObjectKind};
@ -51,99 +54,114 @@ impl Scene {
} }
// Do float parsing instead // Do float parsing instead
else { else {
let parts = parts /// Shortcut for reading something from the iterator and converting it
.map(|s| s.parse::<f64>().map_err(|e| e.into())) /// into the appropriate format
.collect::<Result<Vec<_>>>()?; macro_rules! r {
($ty:ty) => {
let read_vec3 = |start: usize| { <$ty>::construct(&mut parts, ())?
ensure!(parts.len() >= start + 3, "Vec3 requires 3 components.");
Ok(Point::new(parts[start], parts[start + 1], parts[start + 2]))
}; };
match keyword { ($ty:ty, $($ex:expr),* $(,)?) => {
"eye" => scene.eye_pos = read_vec3(0)?, <$ty>::construct(&mut parts, $($ex,)*)?
"viewdir" => scene.view_dir = read_vec3(0)?, };
"updir" => scene.up_dir = read_vec3(0)?, }
"hfov" => scene.hfov = parts[0], /// Shortcut for getting material color
"bkgcolor" => scene.bkg_color = read_vec3(0)?, macro_rules! mat {
() => {
match material_color {
Some(v) => v,
None => {
bail!("Each sphere must be preceded by a `mtlcolor` line")
}
}
};
}
match keyword {
"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 x y z w r g b
"light" => { "light" => {
ensure!(parts.len() == 7, "Light requires 7 params"); let vec3 = r!(Vector3<f64>);
let w = r!(usize);
let color = r!(Color);
let kind = match parts[3] as usize { let kind = match w as usize {
0 => LightKind::Directional { 0 => LightKind::Directional { direction: vec3 },
direction: read_vec3(0)?,
},
1 => LightKind::Point { 1 => LightKind::Point {
location: read_vec3(0)?, location: vec3,
attenuation: None, attenuation: None,
}, },
_ => bail!("Invalid w; must be either 0 or 1"), _ => bail!("Invalid w; must be either 0 or 1"),
}; };
let light = Light {
kind, let light = Light { kind, color };
color: read_vec3(4)?,
};
scene.lights.push(light); scene.lights.push(light);
} }
// attlight x y z w r g b c1 c2 c3 // attlight x y z w r g b c1 c2 c3
"attlight" => { "attlight" => {
ensure!(parts.len() == 10, "Attenuated light requires 10 params"); let vec3 = r!(Vector3<f64>);
let w = r!(usize);
let color = r!(Color);
let c = r!(Vector3<f64>);
let kind = match parts[3] as usize { let kind = match w as usize {
// TODO: Is this even defined? Pending TA answer 0 => LightKind::Directional { direction: vec3 },
0 => LightKind::Directional {
direction: read_vec3(0)?,
},
1 => LightKind::Point { 1 => LightKind::Point {
location: read_vec3(0)?, location: vec3,
attenuation: Some(Attenuation { attenuation: Some(Attenuation {
c1: parts[7], c1: c.x,
c2: parts[8], c2: c.y,
c3: parts[9], c3: c.z,
}), }),
}, },
_ => bail!("Invalid w; must be either 0 or 1"), _ => bail!("Invalid w; must be either 0 or 1"),
}; };
let light = Light {
kind, let light = Light { kind, color };
color: read_vec3(4)?,
};
scene.lights.push(light); scene.lights.push(light);
} }
// depthcueing dcr dcg dcb amax amin distmax distmin // depthcueing dcr dcg dcb amax amin distmax distmin
"depthcueing" => { "depthcueing" => {
ensure!(parts.len() == 7, "Depth cueing requires 7 params"); let color = r!(Color);
let a_max = r!(f64);
let a_min = r!(f64);
let dist_max = r!(f64);
let dist_min = r!(f64);
let color = read_vec3(0)?;
scene.depth_cueing = DepthCueing { scene.depth_cueing = DepthCueing {
color, color,
a_max: parts[3], a_max,
a_min: parts[4], a_min,
dist_max: parts[5], dist_max,
dist_min: parts[6], dist_min,
}; };
} }
// mtlcolor Odr Odg Odb Osr Osg Osb ka kd ks n // mtlcolor Odr Odg Odb Osr Osg Osb ka kd ks n
"mtlcolor" => { "mtlcolor" => {
ensure!(parts.len() == 10, "Material color requires 10 params"); let diffuse_color = r!(Color);
let specular_color = r!(Color);
let diffuse_color = read_vec3(0)?; let k_a = r!(f64);
let specular_color = read_vec3(3)?; let k_d = r!(f64);
let k_s = r!(f64);
let exponent = r!(f64);
let material = Material { let material = Material {
diffuse_color, diffuse_color,
specular_color, specular_color,
k_a: parts[6], k_a,
k_d: parts[7], k_d,
k_s: parts[8], k_s,
exponent: parts[9], exponent,
}; };
let idx = scene.materials.len(); let idx = scene.materials.len();
@ -151,33 +169,32 @@ impl Scene {
scene.materials.push(material); scene.materials.push(material);
} }
"sphere" => scene.objects.push(Object { "sphere" => {
kind: ObjectKind::Sphere(Sphere { let center = r!(Point);
center: read_vec3(0)?, let radius = r!(f64);
radius: parts[3],
}),
material: match material_color {
Some(v) => v,
None => {
bail!("Each sphere must be preceded by a `mtlcolor` line")
}
},
}),
"cylinder" => scene.objects.push(Object { scene.objects.push(Object {
kind: ObjectKind::Cylinder(Cylinder { kind: ObjectKind::Sphere(Sphere { center, radius }),
center: read_vec3(0)?, material: mat!(),
direction: read_vec3(3)?, });
radius: parts[6],
length: parts[7],
}),
material: match material_color {
Some(v) => v,
None => {
bail!("Each sphere must be preceded by a `mtlcolor` line")
} }
},
"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}"), _ => bail!("Unknown keyword {keyword}"),
} }
@ -187,3 +204,55 @@ impl Scene {
Ok(scene) Ok(scene)
} }
} }
pub trait Construct: Sized {
type Args;
/// Construct an element of this type from an iterator over strings.
fn construct<'a, I>(it: &mut I, args: Self::Args) -> Result<Self>
where
I: Iterator<Item = &'a str>;
}
macro_rules! impl_construct {
($ty:ty) => {
impl Construct for $ty {
type Args = ();
fn construct<'a, I>(it: &mut I, args: Self::Args) -> Result<Self>
where
I: Iterator<Item = &'a str>,
{
let item = match it.next() {
Some(v) => v,
None => bail!("Ran out of items"),
};
Ok(item.parse()?)
}
}
};
}
impl_construct!(f64);
impl_construct!(usize);
impl Construct for Vector3<f64> {
type Args = ();
fn construct<'a, I>(it: &mut I, args: Self::Args) -> Result<Self>
where
I: Iterator<Item = &'a str>,
{
let (x, y, z) = match it.next_tuple() {
Some(v) => v,
None => bail!("Expected 3 values"),
};
let x: f64 = x.parse()?;
let y: f64 = y.parse()?;
let z: f64 = z.parse()?;
Ok(Vector3::new(x, y, z))
}
}