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