use crate::tokenizer::Tokenizer; use error::SourceFile; use miette::{Diagnostic, IntoDiagnostic, Result, SourceSpan, bail, miette}; use ray_tracing_core::{ affine_transform::AffineTransform, math::{Dir3, Pos3}, prelude::Float, }; use std::{ collections::HashMap, path::{Path, PathBuf}, sync::Arc, }; use texture::PbrtTexture; use thiserror::Error; #[macro_use] mod tokenizer; mod error; mod texture; struct Lexer { input: Tokenizer, } impl Lexer { fn new(path: impl AsRef) -> Result { Ok(Self { input: Tokenizer::new(path)?, }) } } #[derive(Debug)] enum CameraType { Orthographic { frame_aspect_ratio: Option, screen_window: Option, lens_radius: Float, focal_distance: Float, }, Perspective { frame_aspect_ratio: Option, screen_window: Option, lens_radius: Float, focal_distance: Float, fov: Float, }, } #[derive(Debug)] struct PbrtCamera { camera_type: CameraType, shutter_open: Float, shutter_close: Float, } #[derive(Debug)] enum Statement { AttributeBegin, AttributeEnd, WorldBegin, Camera(PbrtCamera), Include(String), ConcatTransform(AffineTransform), CoordinateSystem(String), CoordSysTransform(String), Shape(ShapeType, ShapeAlpha), Unknown(String, Vec), Transform(AffineTransform), Texture(String, Arc), } fn parse_look_at(iter: &mut Tokenizer) -> Result { 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(iter: &mut Tokenizer) -> Result { let shape_type = iter.next().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, Vec::new(); n, Vec, Vec::new(); s, Vec, Vec::new(); uv, Vec<[Float; 2]>, Vec::new(); indices, Vec, 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()?; 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.indices.len() % 3 != 0 { bail!( "number of indices must be divisible by 3. 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.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 { indices: t.indices, p: t.p, n: t.n, s: t.s, uv: t.uv, }, t.alpha, )) } "\"bilinearmesh\"" => { let t = parse_dict!(iter => p, Vec, Vec::new(); n, Vec, Vec::new(); uv, Vec<[Float; 2]>, Vec::new(); indices, Vec, 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, Vec::new(); p, Vec, 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, 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(tokenizer: &mut Tokenizer) -> Result { let camera_type = tokenizer .next() .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, None; screen_window, Option, 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, None; screen_window, Option, 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( &mut self, textures: &HashMap>, ) -> Option> { match self.input.next() { Some(Ok(s)) => match s.as_str() { "AttributeBegin" => Some(Ok(Statement::AttributeBegin)), "AttributeEnd" => Some(Ok(Statement::AttributeEnd)), "Include" => { let s = self .input .next() .unwrap() .unwrap() .trim_matches('"') .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) .map(|(name, texture)| Statement::Texture(name, texture)), ), "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), }), "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| !s.starts_with(|c: char| c.is_ascii_alphabetic())) { match p { Ok(c) => v.push(c), Err(e) => return Some(Err(e)), } } Some(Ok(Statement::Unknown(s, v))) } } }, Some(Err(e)) => Some(Err(e)), None => None, } } } fn parse_transform(input: &mut Tokenizer) -> Result { if !input .next() .is_none_or(|p| p.is_ok_and(|p| p.as_str() == "[")) { bail!("expected list.") } let mut v = [0.0; 16]; for i in 0..16 { v[i] = input .next() .ok_or(miette!("value expected"))?? .parse::() .into_diagnostic()?; } if !input .next() .is_none_or(|p| p.is_ok_and(|p| p.as_str() == "]")) { bail!("expected list end.") } 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]], ]) .ok_or(miette!("Unable to invert transformation")) } fn parse_translate(iter: &mut Tokenizer) -> Result { let pos = Pos3::new(iter.parse_next()?, iter.parse_next()?, iter.parse_next()?); Ok(Statement::ConcatTransform(AffineTransform::translation( pos, ))) } fn parse_scale(iter: &mut Tokenizer) -> Result { Ok(Statement::ConcatTransform(AffineTransform::scale( iter.parse_next()?, iter.parse_next()?, iter.parse_next()?, ))) } fn parse_rotate(iter: &mut Tokenizer) -> Result { let angle = iter .next() .ok_or(miette!("missing argument"))?? .parse() .into_diagnostic()?; let dir = Dir3::new( iter.next() .ok_or(miette!("missing argument"))?? .parse() .into_diagnostic()?, iter.next() .ok_or(miette!("missing argument"))?? .parse() .into_diagnostic()?, iter.next() .ok_or(miette!("missing argument"))?? .parse() .into_diagnostic()?, ); Ok(Statement::ConcatTransform(AffineTransform::rotation( angle, dir, ))) } struct BytesToChar { count: usize, iter: I, } impl BytesToChar { fn new(iter: I) -> Self { Self { count: 0, iter } } } impl>> Iterator for BytesToChar { type Item = Result<(usize, char)>; fn next(&mut self) -> Option { 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: P, inner: Option>>, iter: Lexer, } impl + std::fmt::Debug> Parser

{ fn new(path: P) -> Result { Ok(Self { iter: Lexer::new(path.as_ref())?, path, inner: None, }) } } impl> Parser

{ fn next( &mut self, textures: &HashMap>, ) -> Option> { if let Some(iter) = &mut self.inner { if let Some(statement) = iter.next(textures) { return Some(statement); } self.inner = None; } match self.iter.next(textures) { Some(Ok(Statement::Include(s))) => { let path = self.path.as_ref().parent().unwrap().join(s); self.inner = Some(Box::new(Parser::new(path).unwrap())); self.next(textures) } Some(s) => Some(s), None => None, } } } #[derive(Debug)] enum ShapeType { Sphere { radius: Float, zmin: Float, zmax: Float, phimax: Float, }, TriangleMesh { indices: Vec, p: Vec, n: Vec, s: Vec, uv: Vec<[Float; 2]>, }, BilinearMesh { indices: Vec, p: Vec, n: Vec, uv: Vec<[Float; 2]>, }, LoopSubDiv { levels: u32, indices: Vec, p: Vec, }, Disk { height: Float, radius: Float, innerradius: Float, phimax: Float, }, PlyMesh { filename: String, displacement: Option, edgelength: Float, }, } #[derive(Debug)] enum ShapeAlpha { None, Value(Float), Texture(String), } #[derive(Debug)] struct Shape { ctm: AffineTransform, material: usize, obj: ShapeType, alpha: ShapeAlpha, } #[derive(Debug)] pub struct Pbrt { settings: PbrtWorldSettings, scene: PbrtScene, } impl Pbrt { fn new(settings: PbrtWorldSettings) -> Self { Self { settings, scene: PbrtScene { shapes: Vec::new() }, } } } #[derive(Debug)] struct PbrtWorldSettings { camera: PbrtCamera, camera_ctm: AffineTransform, } #[derive(Debug)] struct PbrtScene { shapes: Vec, } fn inner_parse_pbrt(path: impl AsRef + std::fmt::Debug) -> Result { // unwrap on context.last() ok because context is never empty let mut context_ctm = vec![AffineTransform::identity()]; let mut parser = Parser::new(path)?; // parse global settings let mut camera = None; let mut named_transforms = HashMap::new(); let mut textures = HashMap::new(); loop { let p = parser.next(&textures).ok_or_else(|| miette!(""))??; // dbg!(&p); match p { Statement::AttributeBegin => context_ctm.push(*context_ctm.last().unwrap()), Statement::AttributeEnd => { context_ctm.pop(); if context_ctm.is_empty() { return Err(miette!("Attribute end does not match.")); } } 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_ctm.last().unwrap())); named_transforms.insert(String::from("\"camera\""), *context_ctm.last().unwrap()); } Statement::CoordinateSystem(s) => { named_transforms.insert(s, *context_ctm.last().unwrap()); } 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 }); let mut context_ctm = vec![AffineTransform::identity()]; // let mut context_material = vec![]; while let Some(p) = parser.next(&textures).transpose()? { match p { Statement::AttributeBegin => context_ctm.push(*context_ctm.last().unwrap()), Statement::AttributeEnd => { context_ctm.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) => { pbrt.scene.shapes.push(Shape { ctm: *context_ctm.last().unwrap(), material: 0, obj: shape_type, alpha: shape_alpha, }); } Statement::CoordinateSystem(s) => { named_transforms.insert(s, *context_ctm.last().unwrap()); } Statement::CoordSysTransform(s) => { *context_ctm.last_mut().unwrap() = *named_transforms .get(&s) .ok_or_else(|| miette!("unknown transform"))?; } Statement::Unknown(s, _items) => { eprintln!("Unknown statement: {s}") } s => bail!("unexpected statemnet in world settings: {s:?}"), } } Ok(pbrt) } pub fn parse_pbrt_v4(path: impl AsRef + std::fmt::Debug) -> Result { inner_parse_pbrt(path) }