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 { Ok(Self { input: Tokenizer::new(path, base_path)?, }) } } #[derive(Debug)] #[allow(dead_code)] pub 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)] #[allow(dead_code)] pub struct PbrtCamera { pub 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), Material(Arc>), MakeNamedMaterial(String, Arc>), NamedMaterial(String), AreaLight(AreaLight), } #[derive(Debug, Clone)] pub struct AreaLight { pub color: Color, } impl Light 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(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_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, Vec::new(); n, Vec, Vec::new(); s, Vec, 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, 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_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, 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, context: &PbrtContext) -> Option>> { 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(input: &mut Tokenizer) -> Result> { 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 { 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(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.parse_parameter()?; let dir = Dir3::new( iter.parse_parameter()?, iter.parse_parameter()?, iter.parse_parameter()?, ); 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: PathBuf, base_path: PathBuf, inner: Option>, iter: Lexer, } impl Parser { fn new(path: PathBuf, base_path: PathBuf) -> Result { Ok(Self { iter: Lexer::new(path.clone(), base_path.clone())?, base_path, path, inner: None, }) } } impl Parser { fn next(&mut self, context: &PbrtContext) -> Option>> { 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 { pub settings: PbrtWorldSettings, pub scene: PbrtScene, } impl Pbrt { 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 { ctm: Vec, textures: HashMap>, material: Vec>>, area_light: Vec, materials: HashMap>>, } impl PbrtContext { 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> { self.textures.get(name) } pub fn get_named_material(&self, name: &String) -> Option<&Arc>> { self.materials.get(name) } pub fn get_material(&self) -> Option<&Arc>> { 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( path: impl AsRef + std::fmt::Debug, ) -> Result> { // 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( path: impl AsRef + std::fmt::Debug, ) -> Result> { inner_parse_pbrt(path) }