ray-tracing2/ray-tracing-pbrt-scene/src/lib.rs

773 lines
25 KiB
Rust

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<Path>) -> Result<Self> {
Ok(Self {
input: Tokenizer::new(path)?,
})
}
}
#[derive(Debug)]
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)]
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<String>),
Transform(AffineTransform),
Texture(String, Arc<dyn PbrtTexture>),
}
fn parse_look_at(iter: &mut Tokenizer) -> Result<Statement> {
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<Statement> {
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<Pos3>, Vec::new();
n, Vec<Dir3>, Vec::new();
s, 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)?;
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<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(tokenizer: &mut Tokenizer) -> Result<Statement> {
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<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(
&mut self,
textures: &HashMap<String, Arc<dyn PbrtTexture>>,
) -> Option<Result<Statement>> {
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<AffineTransform> {
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::<Float>()
.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<Statement> {
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<Statement> {
Ok(Statement::ConcatTransform(AffineTransform::scale(
iter.parse_next()?,
iter.parse_next()?,
iter.parse_next()?,
)))
}
fn parse_rotate(iter: &mut Tokenizer) -> Result<Statement> {
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<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<P> {
path: P,
inner: Option<Box<Parser<PathBuf>>>,
iter: Lexer,
}
impl<P: AsRef<Path> + std::fmt::Debug> Parser<P> {
fn new(path: P) -> Result<Self> {
Ok(Self {
iter: Lexer::new(path.as_ref())?,
path,
inner: None,
})
}
}
impl<P: AsRef<Path>> Parser<P> {
fn next(
&mut self,
textures: &HashMap<String, Arc<dyn PbrtTexture>>,
) -> Option<Result<Statement>> {
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<usize>,
p: Vec<Pos3>,
n: Vec<Dir3>,
s: Vec<Dir3>,
uv: Vec<[Float; 2]>,
},
BilinearMesh {
indices: Vec<usize>,
p: Vec<Pos3>,
n: Vec<Dir3>,
uv: Vec<[Float; 2]>,
},
LoopSubDiv {
levels: u32,
indices: Vec<usize>,
p: Vec<Pos3>,
},
Disk {
height: Float,
radius: Float,
innerradius: Float,
phimax: Float,
},
PlyMesh {
filename: String,
displacement: Option<String>,
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<Shape>,
}
fn inner_parse_pbrt(path: impl AsRef<Path> + std::fmt::Debug) -> Result<Pbrt> {
// 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<Path> + std::fmt::Debug) -> Result<Pbrt> {
inner_parse_pbrt(path)
}