Initial commit for pbrt file format parsing.

This commit is contained in:
hal8174 2025-07-29 21:57:29 +02:00
parent 9fa518572e
commit 900fe532b5
Signed by: hal8174
SSH key fingerprint: SHA256:JwuqS+eVfISfKr+DkDQ6NWAbGd1jFAHkPpCM1yCnlTs
6 changed files with 409 additions and 34 deletions

View file

@ -0,0 +1,10 @@
[package]
name = "ray-tracing-pbrt-scene"
version = "0.1.0"
edition = "2024"
[dependencies]
ray-tracing-core = { path = "../ray-tracing-core" }
clap = { version = "4.5.41", features = ["derive"] }
miette = { version = "7.6.0", features = ["fancy"] }
nom = "8.0.0"

View file

@ -0,0 +1,35 @@
LookAt 3 4 1.5 # eye
.5 .5 0 # look at point
0 0 1 # up vector
Camera "perspective" "float fov" 45
Sampler "halton" "integer pixelsamples" 128
Integrator "volpath"
Film "rgb" "string filename" "simple.png"
"integer xresolution" [400] "integer yresolution" [400]
WorldBegin
# uniform blue-ish illumination from all directions
LightSource "infinite" "rgb L" [ .4 .45 .5 ]
# approximate the sun
LightSource "distant" "point3 from" [ -30 40 100 ]
"blackbody L" 3000 "float scale" 1.5
AttributeBegin
Material "dielectric"
Shape "sphere" "float radius" 1
AttributeEnd
AttributeBegin
Texture "checks" "spectrum" "checkerboard"
"float uscale" [16] "float vscale" [16]
"rgb tex1" [.1 .1 .1] "rgb tex2" [.8 .8 .8]
Material "diffuse" "texture reflectance" "checks"
Translate 0 0 -1
Shape "bilinearmesh"
"point3 P" [ -20 -20 0 20 -20 0 -20 20 0 20 20 0 ]
"point2 uv" [ 0 0 1 0 1 1 0 1 ]
AttributeEnd

View file

@ -0,0 +1,14 @@
use clap::Parser;
use ray_tracing_pbrt_scene::parse_pbrt_v4;
use std::path::PathBuf;
#[derive(Parser)]
struct Args {
filename: PathBuf,
}
fn main() -> Result<(), miette::Error> {
let args = Args::parse();
parse_pbrt_v4(args.filename)
}

View file

@ -0,0 +1,286 @@
use std::{
fs::File,
io::{BufRead, BufReader, Bytes, Read},
iter::Peekable,
path::{Path, PathBuf},
};
use miette::{Diagnostic, Error, IntoDiagnostic, Result, bail, miette};
use nom::IResult;
use ray_tracing_core::math::{Dir3, Pos3};
struct Tokenizer<I: Iterator<Item = Result<char>>> {
input_iterator: Peekable<I>,
}
impl<I: Iterator<Item = Result<char>>> Tokenizer<I> {
fn new(input_iterator: I) -> Self {
Self {
input_iterator: input_iterator.peekable(),
}
}
}
impl<I> Iterator for Tokenizer<I>
where
I: Iterator<Item = Result<char>>,
{
type Item = Result<String>;
fn next(&mut self) -> Option<Self::Item> {
while self
.input_iterator
.peek()
.is_some_and(|c| c.as_ref().is_ok_and(|&c| c.is_whitespace() || c == '#'))
{
if self
.input_iterator
.peek()
.is_some_and(|c| c.as_ref().is_ok_and(|&c| c == '#'))
{
while self
.input_iterator
.next()
.is_some_and(|c| c.is_ok_and(|c| c != '\n'))
{}
} else {
match self.input_iterator.next()? {
Ok(_) => (),
Err(e) => return Some(Err(e)),
}
}
}
match self.input_iterator.next() {
Some(Ok('[')) => Some(Ok(String::from('['))),
Some(Ok(']')) => Some(Ok(String::from(']'))),
Some(Ok('"')) => {
let mut r = String::from('"');
while let Some(p) = self
.input_iterator
.next_if(|c| c.as_ref().is_ok_and(|&c| c != '"'))
{
match p {
Ok(c) => r.push(c),
Err(e) => return Some(Err(e)),
}
}
if self
.input_iterator
.next()
.is_none_or(|c| !c.is_ok_and(|c| c == '"'))
{
return Some(Err(miette::miette!("unfinished string")));
};
r.push('"');
Some(Ok(r))
}
Some(Ok(c)) => {
let mut r = String::new();
r.push(c);
while let Some(p) = self.input_iterator.next_if(|c| {
c.as_ref()
.is_ok_and(|&c| c != '#' && c != '[' && c != ']' && !c.is_whitespace())
}) {
match p {
Ok(c) => r.push(c),
Err(e) => return Some(Err(e)),
}
}
Some(Ok(r))
}
Some(Err(e)) => Some(Err(e)),
None => None,
}
}
}
struct Lexer<I: Iterator<Item = Result<char>>> {
input: Peekable<Tokenizer<I>>,
}
impl<I: Iterator<Item = Result<char>>> Lexer<I> {
fn new(iter: I) -> Self {
Self {
input: Tokenizer::new(iter).peekable(),
}
}
}
#[derive(Debug)]
enum Statement {
AttributeStart,
AttributeEnd,
Include(String),
LookAt { eye: Pos3, look_at: Pos3, up: Dir3 },
Unknown(String, Vec<String>),
}
fn parse_look_at(iter: &mut impl Iterator<Item = Result<String>>) -> Result<Statement> {
let eye = Pos3::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()?,
);
let look_at = Pos3::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()?,
);
let up = 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::LookAt { eye, look_at, up })
}
impl<I: Iterator<Item = Result<char>>> Iterator for Lexer<I> {
type Item = Result<Statement>;
fn next(&mut self) -> Option<Self::Item> {
match self.input.next() {
Some(Ok(s)) => match s.as_str() {
"AttributeStart" => Some(Ok(Statement::AttributeStart)),
"AttributeEnd" => Some(Ok(Statement::AttributeEnd)),
"Include" => {
let s = self
.input
.next()
.unwrap()
.unwrap()
.trim_matches('"')
.to_string();
Some(Ok(Statement::Include(s)))
}
"LookAt" => Some(parse_look_at(&mut self.input)),
_ => {
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.as_ref()
.is_ok_and(|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,
}
}
}
struct BytesToChar<I> {
iter: I,
}
impl<I: Iterator<Item = Result<u8, std::io::Error>>> Iterator for BytesToChar<I> {
type Item = Result<char>;
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next()? {
Ok(a) => {
if a & 0x80 == 0 {
Some(Ok(char::from(a)))
} else {
todo!()
}
}
Err(e) => Some(Err(e).into_diagnostic()),
}
}
}
struct Parser<P> {
path: P,
inner: Option<Box<Parser<PathBuf>>>,
iter: Lexer<BytesToChar<Bytes<BufReader<File>>>>,
}
impl<P: AsRef<Path> + std::fmt::Debug> Parser<P> {
fn new(path: P) -> Result<Self> {
dbg!(&path);
let bufreader = BufReader::new(std::fs::File::open(&path).into_diagnostic()?);
Ok(Self {
path,
inner: None,
iter: Lexer::new(BytesToChar {
iter: bufreader.bytes(),
}),
})
}
}
impl<P: AsRef<Path>> Iterator for Parser<P> {
type Item = Result<Statement>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(iter) = &mut self.inner {
if let Some(statement) = iter.next() {
return Some(statement);
}
self.inner = None;
}
match self.iter.next() {
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()
}
Some(s) => Some(s),
None => None,
}
}
}
pub fn parse_pbrt_v4(path: impl AsRef<Path> + std::fmt::Debug) -> Result<()> {
let mut tokens = 0;
for token in Parser::new(path).unwrap() {
dbg!(token);
tokens += 1;
}
dbg!(tokens);
Ok(())
}