870 lines
29 KiB
Rust
870 lines
29 KiB
Rust
use crate::{
|
|
bvh::Bvh,
|
|
either::Either,
|
|
scene::PbrtScene,
|
|
shape::{Shape, ShapeAlpha, ShapeType, TriangleMesh},
|
|
tokenizer::{Token, Tokenizer},
|
|
};
|
|
use material::PbrtMaterial;
|
|
use miette::{IntoDiagnostic, Result, bail, miette};
|
|
use ray_tracing_core::{
|
|
affine_transform::AffineTransform,
|
|
color::Color,
|
|
light::Light,
|
|
math::{Dir3, Pos3},
|
|
prelude::{Float, Rng},
|
|
};
|
|
use std::{
|
|
collections::HashMap,
|
|
path::{Path, PathBuf},
|
|
sync::Arc,
|
|
};
|
|
use texture::PbrtTexture;
|
|
|
|
#[macro_use]
|
|
mod tokenizer;
|
|
|
|
mod bvh;
|
|
mod either;
|
|
mod error;
|
|
mod material;
|
|
pub mod scene;
|
|
mod shape;
|
|
mod texture;
|
|
|
|
struct Lexer {
|
|
input: Tokenizer,
|
|
}
|
|
|
|
impl Lexer {
|
|
fn new(path: PathBuf, base_path: PathBuf) -> Result<Self> {
|
|
Ok(Self {
|
|
input: Tokenizer::new(path, base_path)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
pub enum CameraType {
|
|
Orthographic {
|
|
frame_aspect_ratio: Option<Float>,
|
|
screen_window: Option<Float>,
|
|
lens_radius: Float,
|
|
focal_distance: Float,
|
|
},
|
|
Perspective {
|
|
frame_aspect_ratio: Option<Float>,
|
|
screen_window: Option<Float>,
|
|
lens_radius: Float,
|
|
focal_distance: Float,
|
|
fov: Float,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
pub struct PbrtCamera {
|
|
pub camera_type: CameraType,
|
|
shutter_open: Float,
|
|
shutter_close: Float,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum Statement<R> {
|
|
AttributeBegin,
|
|
AttributeEnd,
|
|
WorldBegin,
|
|
Camera(PbrtCamera),
|
|
Include(String),
|
|
ConcatTransform(AffineTransform),
|
|
CoordinateSystem(String),
|
|
CoordSysTransform(String),
|
|
Shape(ShapeType, ShapeAlpha),
|
|
Unknown(String, Vec<Token>),
|
|
Transform(AffineTransform),
|
|
Texture(String, Arc<dyn PbrtTexture>),
|
|
Material(Arc<dyn PbrtMaterial<R>>),
|
|
MakeNamedMaterial(String, Arc<dyn PbrtMaterial<R>>),
|
|
NamedMaterial(String),
|
|
AreaLight(AreaLight),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AreaLight {
|
|
pub color: Color,
|
|
}
|
|
|
|
impl<R: Rng> Light<R> for AreaLight {
|
|
fn emit(&self, w_in: Dir3, _rng: &mut R) -> Color {
|
|
if w_in.y() > 0.0 {
|
|
self.color
|
|
} else {
|
|
Color::black()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_look_at<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
|
|
let eye = Pos3::new(iter.parse_next()?, iter.parse_next()?, iter.parse_next()?);
|
|
let look_at = Pos3::new(iter.parse_next()?, iter.parse_next()?, iter.parse_next()?);
|
|
let up = Dir3::new(iter.parse_next()?, iter.parse_next()?, iter.parse_next()?);
|
|
|
|
Ok(Statement::ConcatTransform(
|
|
AffineTransform::look_at(eye, look_at, up)
|
|
.ok_or(miette!("Unable to calculate inverse of matrix"))?,
|
|
))
|
|
}
|
|
|
|
fn parse_shape<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
|
|
let shape_type = iter
|
|
.next_if_string_value()
|
|
.ok_or(miette!("unable to get shape type"))??;
|
|
|
|
match shape_type.as_str() {
|
|
"sphere" => {
|
|
let t = parse_dict!(iter =>
|
|
radius, Float, 1.0;
|
|
zmin, Float, {-radius};
|
|
zmax, Float, {radius};
|
|
phimax, Float, 360.0;
|
|
alpha, ShapeAlpha, ShapeAlpha::None
|
|
=>
|
|
radius, "float radius", iter.parse_parameter()?;
|
|
zmin, "float zmin", iter.parse_parameter()?;
|
|
zmax, "float zmax", iter.parse_parameter()?;
|
|
phimax, "float phimax", iter.parse_parameter()?;
|
|
alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
|
|
alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
|
|
);
|
|
Ok(Statement::Shape(
|
|
ShapeType::Sphere {
|
|
radius: t.radius,
|
|
zmin: t.zmin,
|
|
zmax: t.zmax,
|
|
phimax: t.phimax,
|
|
},
|
|
t.alpha,
|
|
))
|
|
}
|
|
"trianglemesh" => {
|
|
let t = parse_dict!(iter =>
|
|
p, Vec<Pos3>, Vec::new();
|
|
n, Vec<Dir3>, Vec::new();
|
|
s, Vec<Dir3>, Vec::new();
|
|
uv, Vec<[Float; 2]>, Vec::new();
|
|
indices, Vec<[usize;3]>, Vec::new();
|
|
alpha, ShapeAlpha, ShapeAlpha::None
|
|
=>
|
|
p, "point3 P", iter.parse_list_3(Pos3::new)?;
|
|
n, "normal N", iter.parse_list_3(Dir3::new)?;
|
|
s, "normal S", iter.parse_list_3(Dir3::new)?;
|
|
uv, "point2 uv", iter.parse_list_2(|u, v| [u, v])?;
|
|
indices, "integer indices", iter.parse_list_3(|a, b, c|[a, b, c])?;
|
|
alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
|
|
alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
|
|
);
|
|
|
|
if t.p.len() < 3 {
|
|
bail!("At least 3 points required.")
|
|
}
|
|
if t.indices.is_empty() && t.p.len() != 3 {
|
|
bail!("Indices required for trianglemesh with more than 3 points.")
|
|
}
|
|
|
|
if !t.n.is_empty() && t.n.len() != t.p.len() {
|
|
bail!("Number of normals not equal to number of positions.")
|
|
}
|
|
|
|
if !t.s.is_empty() && t.s.len() != t.p.len() {
|
|
bail!("Number of tangents not equal to number of positions.")
|
|
}
|
|
if !t.uv.is_empty() && t.uv.len() != t.p.len() {
|
|
bail!("Number of uvs not equal to number of positions.")
|
|
}
|
|
|
|
Ok(Statement::Shape(
|
|
ShapeType::TriangleMesh(Bvh::new(
|
|
TriangleMesh {
|
|
indices: t.indices,
|
|
p: t.p,
|
|
n: t.n,
|
|
s: t.s,
|
|
uv: t.uv,
|
|
},
|
|
8,
|
|
)),
|
|
t.alpha,
|
|
))
|
|
}
|
|
"bilinearmesh" => {
|
|
let t = parse_dict!(iter =>
|
|
p, Vec<Pos3>, Vec::new();
|
|
n, Vec<Dir3>, Vec::new();
|
|
uv, Vec<[Float; 2]>, Vec::new();
|
|
indices, Vec<usize>, Vec::new();
|
|
alpha, ShapeAlpha, ShapeAlpha::None
|
|
=>
|
|
p, "point3 P", iter.parse_list_3(Pos3::new)?;
|
|
n, "normal N", iter.parse_list_3(Dir3::new)?;
|
|
uv, "point2 uv", iter.parse_list_2(|u, v| [u, v])?;
|
|
indices, "integer indices", iter.parse_list()?;
|
|
alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
|
|
alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
|
|
);
|
|
|
|
if t.p.len() < 4 {
|
|
bail!("At least 4 points required.")
|
|
}
|
|
if t.indices.is_empty() && t.p.len() != 4 {
|
|
bail!("Indices required for trianglemesh with more than 4 points.")
|
|
}
|
|
|
|
if t.indices.len() % 4 != 0 {
|
|
bail!(
|
|
"number of indices must be divisible by 4. num indices: {}",
|
|
t.indices.len()
|
|
)
|
|
}
|
|
|
|
if !t.n.is_empty() && t.n.len() != t.p.len() {
|
|
bail!("Number of normals not equal to number of positions.")
|
|
}
|
|
|
|
if !t.uv.is_empty() && t.uv.len() != t.p.len() {
|
|
bail!("Number of uvs not equal to number of positions.")
|
|
}
|
|
|
|
Ok(Statement::Shape(
|
|
ShapeType::BilinearMesh {
|
|
indices: t.indices,
|
|
p: t.p,
|
|
n: t.n,
|
|
uv: t.uv,
|
|
},
|
|
t.alpha,
|
|
))
|
|
}
|
|
"loopsubdiv" => {
|
|
let t = parse_dict!(iter =>
|
|
levels, u32, 3;
|
|
indices, Vec<usize>, Vec::new();
|
|
p, Vec<Pos3>, Vec::new();
|
|
alpha, ShapeAlpha, ShapeAlpha::None
|
|
=>
|
|
levels, "integer levels", iter.parse_parameter()?;
|
|
indices, "integer indices", iter.parse_list()?;
|
|
p, "point3 P", iter.parse_list_3(Pos3::new)?;
|
|
alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
|
|
alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
|
|
);
|
|
|
|
if t.indices.is_empty() {
|
|
bail!("indices are a required field")
|
|
}
|
|
|
|
if t.p.is_empty() {
|
|
bail!("p is a required field")
|
|
}
|
|
|
|
Ok(Statement::Shape(
|
|
ShapeType::LoopSubDiv {
|
|
levels: t.levels,
|
|
indices: t.indices,
|
|
p: t.p,
|
|
},
|
|
t.alpha,
|
|
))
|
|
}
|
|
"disk" => {
|
|
let t = parse_dict!(iter =>
|
|
height, Float, 0.0;
|
|
radius, Float, 1.0;
|
|
innerradius, Float, 0.0;
|
|
phimax, Float, 360.0;
|
|
alpha, ShapeAlpha, ShapeAlpha::None
|
|
=>
|
|
height, "float height", iter.parse_parameter()?;
|
|
radius, "float radius", iter.parse_parameter()?;
|
|
innerradius, "float innerradius", iter.parse_parameter()?;
|
|
phimax, "float phimax", iter.parse_parameter()?;
|
|
alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
|
|
alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
|
|
);
|
|
|
|
Ok(Statement::Shape(
|
|
ShapeType::Disk {
|
|
height: t.height,
|
|
radius: t.radius,
|
|
innerradius: t.innerradius,
|
|
phimax: t.phimax,
|
|
},
|
|
t.alpha,
|
|
))
|
|
}
|
|
"plymesh" => {
|
|
let t = parse_dict!(iter =>
|
|
filename, String, String::new();
|
|
displacement, Option<String>, None;
|
|
edgelength, Float, 1.0;
|
|
alpha, ShapeAlpha, ShapeAlpha::None
|
|
=>
|
|
filename, "string filename", iter.parse_parameter()?;
|
|
displacement, "string displacement", Some(iter.parse_parameter()?);
|
|
edgelength, "float edgelength", iter.parse_parameter()?;
|
|
alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
|
|
alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
|
|
);
|
|
|
|
Ok(Statement::Shape(
|
|
ShapeType::PlyMesh {
|
|
filename: t.filename,
|
|
displacement: t.displacement,
|
|
edgelength: t.edgelength,
|
|
},
|
|
t.alpha,
|
|
))
|
|
}
|
|
_ => Err(miette!("Unknown shape {}", shape_type)),
|
|
}
|
|
}
|
|
|
|
fn parse_camera<R>(tokenizer: &mut Tokenizer) -> Result<Statement<R>> {
|
|
let camera_type = tokenizer
|
|
.next_if_string_value()
|
|
.ok_or(miette!("unable to get shape type"))??;
|
|
|
|
match camera_type.as_str() {
|
|
"orthographic" => {
|
|
let t = parse_dict!(tokenizer =>
|
|
shutteropen, Float, 0.0;
|
|
shutterclose, Float, 1.0;
|
|
frame_aspect_ratio, Option<Float>, None;
|
|
screen_window, Option<Float>, None;
|
|
lens_radius, Float, 0.0;
|
|
focal_distance, Float, Float::powi(10.0, 30)
|
|
=>
|
|
shutteropen, "float shutteropen", tokenizer.parse_parameter()?;
|
|
shutterclose, "float shutterclose", tokenizer.parse_parameter()?;
|
|
frame_aspect_ratio, "float frameaspectratio", Some(tokenizer.parse_parameter()?);
|
|
screen_window, "float screenwindow", Some(tokenizer.parse_parameter()?);
|
|
lens_radius, "float lensradius", tokenizer.parse_parameter()?;
|
|
focal_distance, "float focaldistance", tokenizer.parse_parameter()?
|
|
);
|
|
|
|
Ok(Statement::Camera(PbrtCamera {
|
|
camera_type: CameraType::Orthographic {
|
|
frame_aspect_ratio: t.frame_aspect_ratio,
|
|
screen_window: t.screen_window,
|
|
lens_radius: t.lens_radius,
|
|
focal_distance: t.focal_distance,
|
|
},
|
|
shutter_open: t.shutteropen,
|
|
shutter_close: t.shutterclose,
|
|
}))
|
|
}
|
|
"perspective" => {
|
|
let t = parse_dict!(tokenizer =>
|
|
shutteropen, Float, 0.0;
|
|
shutterclose, Float, 1.0;
|
|
frame_aspect_ratio, Option<Float>, None;
|
|
screen_window, Option<Float>, None;
|
|
lens_radius, Float, 0.0;
|
|
focal_distance, Float, Float::powi(10.0, 30);
|
|
fov, Float, 90.0
|
|
=>
|
|
shutteropen, "float shutteropen", tokenizer.parse_parameter()?;
|
|
shutterclose, "float shutterclose", tokenizer.parse_parameter()?;
|
|
frame_aspect_ratio, "float frameaspectratio", Some(tokenizer.parse_parameter()?);
|
|
screen_window, "float screenwindow", Some(tokenizer.parse_parameter()?);
|
|
lens_radius, "float lensradius", tokenizer.parse_parameter()?;
|
|
focal_distance, "float focaldistance", tokenizer.parse_parameter()?;
|
|
fov, "float fov", tokenizer.parse_parameter()?
|
|
);
|
|
|
|
Ok(Statement::Camera(PbrtCamera {
|
|
camera_type: CameraType::Perspective {
|
|
frame_aspect_ratio: t.frame_aspect_ratio,
|
|
screen_window: t.screen_window,
|
|
lens_radius: t.lens_radius,
|
|
focal_distance: t.focal_distance,
|
|
fov: t.fov,
|
|
},
|
|
shutter_open: t.shutteropen,
|
|
shutter_close: t.shutterclose,
|
|
}))
|
|
}
|
|
_ => Err(miette!("Unknown camera_type {}", camera_type)),
|
|
}
|
|
}
|
|
|
|
impl Lexer {
|
|
fn next<R: Rng>(&mut self, context: &PbrtContext<R>) -> Option<Result<Statement<R>>> {
|
|
match self.input.next() {
|
|
Some(Ok(Token::Identifier(s))) => match s.as_str() {
|
|
"AttributeBegin" => Some(Ok(Statement::AttributeBegin)),
|
|
"AttributeEnd" => Some(Ok(Statement::AttributeEnd)),
|
|
"Include" => {
|
|
let s = self
|
|
.input
|
|
.next_if_string_value()
|
|
.unwrap()
|
|
.unwrap()
|
|
.to_string();
|
|
|
|
Some(Ok(Statement::Include(s)))
|
|
}
|
|
"Camera" => Some(parse_camera(&mut self.input)),
|
|
"LookAt" => Some(parse_look_at(&mut self.input)),
|
|
"Identity" => Some(Ok(Statement::ConcatTransform(AffineTransform::identity()))),
|
|
"Translate" => Some(parse_translate(&mut self.input)),
|
|
"Scale" => Some(parse_scale(&mut self.input)),
|
|
"Shape" => Some(parse_shape(&mut self.input)),
|
|
"Rotate" => Some(parse_rotate(&mut self.input)),
|
|
"Transform" => Some(parse_transform(&mut self.input).map(Statement::Transform)),
|
|
"Texture" => Some(
|
|
texture::parse_texture(&mut self.input, context)
|
|
.map(|(name, texture)| Statement::Texture(name, texture)),
|
|
),
|
|
"Material" => Some(
|
|
material::parse_material(&mut self.input, context).map(Statement::Material),
|
|
),
|
|
"MakeNamedMaterial" => Some(
|
|
material::parse_make_named_material(&mut self.input, context)
|
|
.map(|(name, material)| Statement::MakeNamedMaterial(name, material)),
|
|
),
|
|
"NamedMaterial" => {
|
|
Some(self.input.next_string_value().map(Statement::NamedMaterial))
|
|
}
|
|
"ConcatTransform" => {
|
|
Some(parse_transform(&mut self.input).map(Statement::ConcatTransform))
|
|
}
|
|
"CoordinateSystem" => Some(match self.input.parse_parameter() {
|
|
Ok(s) => Ok(Statement::CoordinateSystem(s)),
|
|
Err(e) => Err(e),
|
|
}),
|
|
"CoordSysTransform" => Some(match self.input.parse_parameter() {
|
|
Ok(s) => Ok(Statement::CoordSysTransform(s)),
|
|
Err(e) => Err(e),
|
|
}),
|
|
|
|
"AreaLightSource" => Some(parse_area_light(&mut self.input)),
|
|
"WorldBegin" => Some(Ok(Statement::WorldBegin)),
|
|
_ => {
|
|
if s.chars().any(|c| !c.is_ascii_alphabetic()) {
|
|
Some(Err(miette!("malformed identifier")))
|
|
} else {
|
|
let mut v = Vec::new();
|
|
|
|
while let Some(p) =
|
|
self.input.next_if(|s| !matches!(s, Token::Identifier(_)))
|
|
{
|
|
match p {
|
|
Ok(c) => v.push(c),
|
|
Err(e) => return Some(Err(e)),
|
|
}
|
|
}
|
|
|
|
Some(Ok(Statement::Unknown(s, v)))
|
|
}
|
|
}
|
|
},
|
|
Some(Ok(s)) => Some(Err(miette!(
|
|
labels = vec![self.input.last_span_labeled(Some("here"))],
|
|
"expected identifier got {s:?}"
|
|
))),
|
|
Some(Err(e)) => Some(Err(e)),
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_area_light<R>(input: &mut Tokenizer) -> Result<Statement<R>> {
|
|
let s = input.next_string_value()?;
|
|
|
|
if s.as_str() != "diffuse" {
|
|
return Err(miette!(
|
|
labels = vec![input.last_span_labeled(Some("here"))],
|
|
"Only diffuse area light supported."
|
|
)
|
|
.with_source_code(input.get_src()));
|
|
}
|
|
|
|
Ok(Statement::AreaLight(parse_dict2!(input, AreaLight;
|
|
color, Color::white(), ["rgb L", texture::parse_rgb(input)?]
|
|
)))
|
|
}
|
|
|
|
fn parse_transform(input: &mut Tokenizer) -> Result<AffineTransform> {
|
|
input.next_expect_bracket_open()?;
|
|
let mut v = [0.0; 16];
|
|
|
|
for i in &mut v {
|
|
*i = input.parse_next()?;
|
|
}
|
|
|
|
input.next_expect_bracket_close()?;
|
|
|
|
if v[3] != 0.0 || v[7] != 0.0 || v[11] != 0.0 || v[15] != 1.0 {
|
|
bail!("invalid transform entry")
|
|
}
|
|
|
|
AffineTransform::new([
|
|
[v[0], v[4], v[8], v[12]],
|
|
[v[1], v[5], v[9], v[13]],
|
|
[v[2], v[6], v[10], v[14]],
|
|
[v[3], v[7], v[11], v[15]],
|
|
])
|
|
.ok_or(miette!("Unable to invert transformation"))
|
|
}
|
|
|
|
fn parse_translate<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
|
|
let pos = Pos3::new(
|
|
-iter.parse_next()?,
|
|
-iter.parse_next()?,
|
|
-iter.parse_next()?,
|
|
);
|
|
|
|
Ok(Statement::ConcatTransform(AffineTransform::translation(
|
|
pos,
|
|
)))
|
|
}
|
|
|
|
fn parse_scale<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
|
|
Ok(Statement::ConcatTransform(AffineTransform::scale(
|
|
iter.parse_next()?,
|
|
iter.parse_next()?,
|
|
iter.parse_next()?,
|
|
)))
|
|
}
|
|
|
|
fn parse_rotate<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
|
|
let angle = iter.parse_parameter()?;
|
|
let dir = Dir3::new(
|
|
iter.parse_parameter()?,
|
|
iter.parse_parameter()?,
|
|
iter.parse_parameter()?,
|
|
);
|
|
|
|
Ok(Statement::ConcatTransform(AffineTransform::rotation(
|
|
angle, dir,
|
|
)))
|
|
}
|
|
struct BytesToChar<I> {
|
|
count: usize,
|
|
iter: I,
|
|
}
|
|
|
|
impl<I> BytesToChar<I> {
|
|
fn new(iter: I) -> Self {
|
|
Self { count: 0, iter }
|
|
}
|
|
}
|
|
|
|
impl<I: Iterator<Item = Result<u8, std::io::Error>>> Iterator for BytesToChar<I> {
|
|
type Item = Result<(usize, char)>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
match self.iter.next()? {
|
|
Ok(a) => {
|
|
self.count += 1;
|
|
if a & 0x80 == 0 {
|
|
Some(Ok((self.count - 1, char::from(a))))
|
|
} else {
|
|
todo!()
|
|
}
|
|
}
|
|
Err(e) => Some(Err(e).into_diagnostic()),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Parser {
|
|
path: PathBuf,
|
|
base_path: PathBuf,
|
|
inner: Option<Box<Parser>>,
|
|
iter: Lexer,
|
|
}
|
|
|
|
impl Parser {
|
|
fn new(path: PathBuf, base_path: PathBuf) -> Result<Self> {
|
|
Ok(Self {
|
|
iter: Lexer::new(path.clone(), base_path.clone())?,
|
|
base_path,
|
|
path,
|
|
inner: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Parser {
|
|
fn next<R: Rng>(&mut self, context: &PbrtContext<R>) -> Option<Result<Statement<R>>> {
|
|
if let Some(iter) = &mut self.inner {
|
|
if let Some(statement) = iter.next(context) {
|
|
return Some(statement);
|
|
}
|
|
self.inner = None;
|
|
}
|
|
|
|
match self.iter.next(context) {
|
|
Some(Ok(Statement::Include(s))) => {
|
|
let path = self.path.parent().unwrap().join(s);
|
|
self.inner = Some(Box::new(Parser::new(path, self.base_path.clone()).unwrap()));
|
|
|
|
self.next(context)
|
|
}
|
|
Some(s) => Some(s),
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Pbrt<R: Rng> {
|
|
pub settings: PbrtWorldSettings,
|
|
pub scene: PbrtScene<R>,
|
|
}
|
|
|
|
impl<R: Rng> Pbrt<R> {
|
|
fn new(settings: PbrtWorldSettings) -> Self {
|
|
Self {
|
|
settings,
|
|
scene: PbrtScene {
|
|
shapes: Vec::new(),
|
|
infinite_light: Some(scene::PbrtInfiniteLight {
|
|
color: Color::new(0.4, 0.45, 0.5),
|
|
}),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PbrtWorldSettings {
|
|
pub camera: PbrtCamera,
|
|
pub camera_ctm: AffineTransform,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PbrtContext<R> {
|
|
ctm: Vec<AffineTransform>,
|
|
textures: HashMap<String, Arc<dyn PbrtTexture>>,
|
|
material: Vec<Arc<dyn PbrtMaterial<R>>>,
|
|
area_light: Vec<AreaLight>,
|
|
materials: HashMap<String, Arc<dyn PbrtMaterial<R>>>,
|
|
}
|
|
|
|
impl<R> PbrtContext<R> {
|
|
fn new() -> Self {
|
|
Self {
|
|
ctm: vec![AffineTransform::identity()],
|
|
textures: HashMap::new(),
|
|
area_light: Vec::new(),
|
|
material: Vec::new(),
|
|
materials: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn get_ctm(&self) -> AffineTransform {
|
|
*self.ctm.last().unwrap()
|
|
}
|
|
|
|
pub fn get_texture(&self, name: &String) -> Option<&Arc<dyn PbrtTexture>> {
|
|
self.textures.get(name)
|
|
}
|
|
|
|
pub fn get_named_material(&self, name: &String) -> Option<&Arc<dyn PbrtMaterial<R>>> {
|
|
self.materials.get(name)
|
|
}
|
|
|
|
pub fn get_material(&self) -> Option<&Arc<dyn PbrtMaterial<R>>> {
|
|
self.material.last()
|
|
}
|
|
|
|
fn push(&mut self) {
|
|
self.ctm.push(*self.ctm.last().unwrap());
|
|
|
|
if !self.material.is_empty() {
|
|
self.material
|
|
.push(Arc::clone(self.material.last().unwrap()));
|
|
}
|
|
|
|
if !self.area_light.is_empty() {
|
|
self.area_light
|
|
.push(self.area_light.last().unwrap().clone());
|
|
}
|
|
}
|
|
|
|
fn pop(&mut self) -> Result<()> {
|
|
self.ctm.pop();
|
|
|
|
if self.ctm.is_empty() {
|
|
return Err(miette!("Attributes do not matcch"));
|
|
}
|
|
|
|
self.material.pop();
|
|
self.area_light.pop();
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn inner_parse_pbrt<R: Rng + std::fmt::Debug>(
|
|
path: impl AsRef<Path> + std::fmt::Debug,
|
|
) -> Result<Pbrt<R>> {
|
|
// unwrap on context.last() ok because context is never empty
|
|
let mut context = PbrtContext::new();
|
|
|
|
let mut parser = Parser::new(
|
|
path.as_ref().to_path_buf(),
|
|
path.as_ref()
|
|
.parent()
|
|
.ok_or_else(|| miette!("parent from file not found"))?
|
|
.to_path_buf(),
|
|
)?;
|
|
|
|
// parse global settings
|
|
|
|
let mut camera = None;
|
|
|
|
let mut named_transforms = HashMap::new();
|
|
|
|
loop {
|
|
let p = parser.next(&context).ok_or_else(|| miette!(""))??;
|
|
// dbg!(&p);
|
|
match p {
|
|
Statement::AttributeBegin => context.push(),
|
|
Statement::AttributeEnd => {
|
|
context.pop()?;
|
|
}
|
|
Statement::Include(_) => unreachable!(),
|
|
Statement::ConcatTransform(affine_transform) => {
|
|
*context.ctm.last_mut().unwrap() *= affine_transform
|
|
}
|
|
Statement::Transform(affine_transform) => {
|
|
*context.ctm.last_mut().unwrap() = affine_transform
|
|
}
|
|
Statement::Unknown(s, _items) => {
|
|
eprintln!("Unknown statement: {s}")
|
|
}
|
|
Statement::Camera(c) => {
|
|
if camera.is_some() {
|
|
return Err(miette!("The camera can only be set once."));
|
|
}
|
|
camera = Some((c, context.get_ctm()));
|
|
named_transforms.insert(String::from("camera"), context.get_ctm());
|
|
}
|
|
Statement::CoordinateSystem(s) => {
|
|
named_transforms.insert(s, context.get_ctm());
|
|
}
|
|
Statement::CoordSysTransform(s) => {
|
|
*context.ctm.last_mut().unwrap() = *named_transforms
|
|
.get(&s)
|
|
.ok_or_else(|| miette!("unknown transform"))?;
|
|
}
|
|
Statement::WorldBegin => break,
|
|
s => bail!("unexpected statemnet in global settings: {s:?}"),
|
|
}
|
|
}
|
|
|
|
let (camera, camera_ctm) = camera.ok_or(miette!("A camera has to be specified"))?;
|
|
|
|
let mut pbrt = Pbrt::new(PbrtWorldSettings { camera, camera_ctm });
|
|
|
|
context.ctm = vec![AffineTransform::identity()];
|
|
|
|
// let mut context_material = vec![];
|
|
|
|
while let Some(p) = parser.next(&context).transpose()? {
|
|
match p {
|
|
Statement::AttributeBegin => context.push(),
|
|
Statement::AttributeEnd => {
|
|
context.pop()?;
|
|
}
|
|
Statement::Include(_) => unreachable!(),
|
|
Statement::ConcatTransform(affine_transform) => {
|
|
*context.ctm.last_mut().unwrap() *= affine_transform
|
|
}
|
|
Statement::Transform(affine_transform) => {
|
|
*context.ctm.last_mut().unwrap() = affine_transform
|
|
}
|
|
Statement::Shape(shape_type, shape_alpha) => {
|
|
// dbg!(&context);
|
|
if context.area_light.is_empty() {
|
|
pbrt.scene.shapes.push(Shape {
|
|
ctm: context.get_ctm(),
|
|
material: Either::A(Arc::clone(
|
|
context
|
|
.material
|
|
.last()
|
|
.ok_or_else(|| miette!("No material specified"))?,
|
|
)),
|
|
obj: shape_type,
|
|
alpha: shape_alpha,
|
|
});
|
|
} else {
|
|
pbrt.scene.shapes.push(Shape {
|
|
ctm: context.get_ctm(),
|
|
material: Either::B(context.area_light.last().unwrap().clone()),
|
|
obj: shape_type,
|
|
alpha: shape_alpha,
|
|
});
|
|
}
|
|
}
|
|
Statement::CoordinateSystem(s) => {
|
|
named_transforms.insert(s, context.get_ctm());
|
|
}
|
|
Statement::CoordSysTransform(s) => {
|
|
*context.ctm.last_mut().unwrap() = *named_transforms
|
|
.get(&s)
|
|
.ok_or_else(|| miette!("unknown transform"))?;
|
|
}
|
|
Statement::Material(m) => {
|
|
if context.material.is_empty() {
|
|
context.material.push(m);
|
|
} else {
|
|
*context.material.last_mut().unwrap() = m;
|
|
}
|
|
}
|
|
Statement::AreaLight(l) => {
|
|
if context.area_light.is_empty() {
|
|
context.area_light.push(l);
|
|
} else {
|
|
*context.area_light.last_mut().unwrap() = l;
|
|
}
|
|
}
|
|
Statement::MakeNamedMaterial(n, m) => {
|
|
context.materials.insert(n, m);
|
|
}
|
|
Statement::NamedMaterial(n) => {
|
|
let m = Arc::clone(
|
|
context
|
|
.get_named_material(&n)
|
|
.ok_or_else(|| miette!("Unknown named material {n}"))?,
|
|
);
|
|
if context.material.is_empty() {
|
|
context.material.push(m);
|
|
} else {
|
|
*context.material.last_mut().unwrap() = m;
|
|
}
|
|
}
|
|
Statement::Unknown(s, _items) => {
|
|
eprintln!("Unknown statement: {s}")
|
|
}
|
|
Statement::Texture(name, texture) => {
|
|
context.textures.insert(name, texture);
|
|
}
|
|
s => bail!("unexpected statemnet in world settings: {s:?}"),
|
|
}
|
|
}
|
|
|
|
// dbg!(context);
|
|
|
|
Ok(pbrt)
|
|
}
|
|
|
|
pub fn parse_pbrt_v4<R: Rng + std::fmt::Debug>(
|
|
path: impl AsRef<Path> + std::fmt::Debug,
|
|
) -> Result<Pbrt<R>> {
|
|
inner_parse_pbrt(path)
|
|
}
|