The PBRT Scene Format is a scene description format associated with the book “Physically Based Rendering” by Pharr et al.
This vignette outlines the beginning of the parsing process, and starts to define parser functions for the main elements in the format.
pbrt_text <- '
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 "path"
Film "image" "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" "point from" [ -30 40 100 ]
"blackbody L" [3000 1.5]
AttributeBegin
Material "glass"
Shape "sphere" "float radius" 1
AttributeEnd
AttributeBegin
Texture "checks" "spectrum" "checkerboard"
"float uscale" [8] "float vscale" [8]
"rgb tex1" [.1 .1 .1] "rgb tex2" [.8 .8 .8]
Material "matte" "texture Kd" "checks"
Translate 0 0 -1
Shape "trianglemesh"
"integer indices" [0 1 2 0 2 3]
"point P" [ -20 -20 0 20 -20 0 20 20 0 -20 20 0 ]
"float st" [ 0 0 1 0 1 1 0 1 ]
AttributeEnd
WorldEnd
'
When using a renderer which supports the PBRT scene format, the above example should render to the following image:
pbrt_regexes <- c(
comment = '(#.*?)\n', # Assume # only appears to denote comment to end of line
number = '[+\\-]?\\.?(?:0|[1-9]\\d*)(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?',
string = '"(.*?)"',
list_start = "\\[",
list_end = "\\]",
identifier = "\\w+",
newline = '\n',
whitespace = '\\s+'
)
tokens <- flexo::lex(pbrt_text, pbrt_regexes)
tokens <- tokens[!(names(tokens) %in% c('whitespace', 'newline', 'comment'))]
tokens[1:20]
#> identifier number number
#> "LookAt" "3" "4"
#> number number number
#> "1.5" ".5" ".5"
#> number number number
#> "0" "0" "0"
#> number identifier string
#> "1" "Camera" "perspective"
#> string number identifier
#> "float fov" "45" "Sampler"
#> string string number
#> "halton" "integer pixelsamples" "128"
#> identifier string
#> "Integrator" "path"
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Parse an identifier
#
# Parse the stream for the current location until just before the next
# identifier
#
# @param stream stream positioned such the the first token is an 'identifier'
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
parse_identifier <- function(stream) {
stream$assert_name('identifier')
identifier <- stream$consume(1)
body <- stream$consume_until(name = 'identifier', inclusive = FALSE)
setNames(list(list(body)), identifier)
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Parse an Attribute
#
# Parse the stream for the current location until just after the next
# 'AttributeEnd'
#
# @param stream stream positioned such the the first token has the value
# 'AttributeBein'
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
parse_attribute <- function(stream) {
stream$assert_value('AttributeBegin')
stream$consume(1)
attr <- list()
while (!stream$end_of_stream() && stream$read(1) != 'AttributeEnd') {
identifier <- parse_identifier(stream)
attr <- append(attr, identifier)
}
stream$consume(1) # AttributeEnd
attr
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create a stream from the tokens in order to manipulate them.
# Allocate a place for the setup and world informatino
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stream <- flexo::TokenStream$new(tokens)
setup <- list()
world <- list()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# The first token should always be an identifier
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stream$assert_name('identifier')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Parse all the setup identifiers up to (but not including) WorldBegin
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
while (!stream$end_of_stream() && stream$read(1) != 'WorldBegin') {
identifier <- parse_identifier(stream)
setup <- append(setup, identifier)
}
setup
#> $LookAt
#> $LookAt[[1]]
#> number number number number number number number number number
#> "3" "4" "1.5" ".5" ".5" "0" "0" "0" "1"
#>
#>
#> $Camera
#> $Camera[[1]]
#> string string number
#> "perspective" "float fov" "45"
#>
#>
#> $Sampler
#> $Sampler[[1]]
#> string string number
#> "halton" "integer pixelsamples" "128"
#>
#>
#> $Integrator
#> $Integrator[[1]]
#> string
#> "path"
#>
#>
#> $Film
#> $Film[[1]]
#> string string string
#> "image" "string filename" "simple.png"
#> string list_start number
#> "integer xresolution" "[" "400"
#> list_end string list_start
#> "]" "integer yresolution" "["
#> number list_end
#> "400" "]"
jnk <- stream$consume(1) # WorldBegin
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Parse all the world entries
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
while (!stream$end_of_stream() && stream$read(1) != 'WorldEnd') {
if (stream$read(1) == 'AttributeBegin') {
attribute <- parse_attribute(stream)
world <- append(world, attribute)
} else {
identifier <- parse_identifier(stream)
world <- append(world, identifier)
}
}
world
#> $LightSource
#> $LightSource[[1]]
#> string string list_start number number number list_end
#> "infinite" "rgb L" "[" ".4" ".45" ".5" "]"
#>
#>
#> $LightSource
#> $LightSource[[1]]
#> string string list_start number number
#> "distant" "point from" "[" "-30" "40"
#> number list_end string list_start number
#> "100" "]" "blackbody L" "[" "3000"
#> number list_end
#> "1.5" "]"
#>
#>
#> $Material
#> $Material[[1]]
#> string
#> "glass"
#>
#>
#> $Shape
#> $Shape[[1]]
#> string string number
#> "sphere" "float radius" "1"
#>
#>
#> $Texture
#> $Texture[[1]]
#> string string string string list_start
#> "checks" "spectrum" "checkerboard" "float uscale" "["
#> number list_end string list_start number
#> "8" "]" "float vscale" "[" "8"
#> list_end string list_start number number
#> "]" "rgb tex1" "[" ".1" ".1"
#> number list_end string list_start number
#> ".1" "]" "rgb tex2" "[" ".8"
#> number number list_end
#> ".8" ".8" "]"
#>
#>
#> $Material
#> $Material[[1]]
#> string string string
#> "matte" "texture Kd" "checks"
#>
#>
#> $Translate
#> $Translate[[1]]
#> number number number
#> "0" "0" "-1"
#>
#>
#> $Shape
#> $Shape[[1]]
#> string string list_start number
#> "trianglemesh" "integer indices" "[" "0"
#> number number number number
#> "1" "2" "0" "2"
#> number list_end string list_start
#> "3" "]" "point P" "["
#> number number number number
#> "-20" "-20" "0" "20"
#> number number number number
#> "-20" "0" "20" "20"
#> number number number number
#> "0" "-20" "20" "0"
#> list_end string list_start number
#> "]" "float st" "[" "0"
#> number number number number
#> "0" "1" "0" "1"
#> number number number list_end
#> "1" "0" "1" "]"
Next step would be to call specialist functions for parsing particular block types.
parse_identifier()
should recognise the identifer and then call a specialist parsing function for it. i.e.
parse_shape()
parse_translate()
parse_material()
There could also be some low-level parse functions which parse some common generic structures:
parse_number()
converts the token (a character) into a numeric valueparse_list()
converts a list defined in PBRT format as [e1 e2 e3 ... en]
into a standard R list object.