WIP refactor for new scene data layout.

All rendering code is commented out for now.  It will need to be
largely rewritten after this refactor is done, most likely.

This commit gets the basic new scene parsing working, with the basic
new data structures.
This commit is contained in:
Nathan Vegdahl 2019-12-01 10:41:48 +09:00
parent 7f9f576093
commit 2998bbe7ce
7 changed files with 273 additions and 804 deletions

View File

@ -31,13 +31,13 @@ mod math;
mod mis;
mod parse;
mod ray;
mod renderer;
// mod renderer;
mod sampling;
mod scene;
mod shading;
mod surface;
mod timer;
mod tracer;
// mod tracer;
mod transform_stack;
use std::{fs::File, io, io::Read, mem, path::Path, str::FromStr};
@ -51,7 +51,7 @@ use crate::{
accel::BVH4Node,
bbox::BBox,
parse::{parse_scene, DataTree},
renderer::LightPath,
// renderer::LightPath,
surface::SurfaceIntersection,
timer::Timer,
};
@ -163,7 +163,7 @@ fn main() {
"SurfaceIntersection size: {} bytes",
mem::size_of::<SurfaceIntersection>()
);
println!("LightPath size: {} bytes", mem::size_of::<LightPath>());
// println!("LightPath size: {} bytes", mem::size_of::<LightPath>());
println!("BBox size: {} bytes", mem::size_of::<BBox>());
// println!("BVHNode size: {} bytes", mem::size_of::<BVHNode>());
println!("BVH4Node size: {} bytes", mem::size_of::<BVH4Node>());
@ -247,7 +247,7 @@ fn main() {
}
let arena = Arena::new().with_block_size((1 << 20) * 4);
let mut r = parse_scene(&arena, child).unwrap_or_else(|e| {
let mut scene = parse_scene(&arena, child).unwrap_or_else(|e| {
e.print(&psy_contents);
panic!("Parse error.");
});
@ -256,7 +256,7 @@ fn main() {
if !args.is_present("serialized_output") {
println!("\tOverriding scene spp: {}", spp);
}
r.spp = usize::from_str(spp).unwrap();
// r.spp = usize::from_str(spp).unwrap();
}
let max_samples_per_bucket =
@ -279,78 +279,82 @@ fn main() {
if !args.is_present("serialized_output") {
println!("Rendering scene with {} threads...", thread_count);
}
let (mut image, rstats) = r.render(
max_samples_per_bucket,
crop,
thread_count,
args.is_present("serialized_output"),
);
// Print render stats
if !args.is_present("serialized_output") {
let rtime = t.tick();
let ntime = rtime as f64 / rstats.total_time;
println!("\tRendered scene in {:.3}s", rtime);
println!(
"\t\tTrace: {:.3}s",
ntime * rstats.trace_time
);
println!("\t\t\tRays traced: {}", rstats.ray_count);
println!(
"\t\t\tRays/sec: {}",
(rstats.ray_count as f64 / (ntime * rstats.trace_time) as f64) as u64
);
println!("\t\t\tRay/node tests: {}", rstats.accel_node_visits);
println!(
"\t\tInitial ray generation: {:.3}s",
ntime * rstats.initial_ray_generation_time
);
println!(
"\t\tRay generation: {:.3}s",
ntime * rstats.ray_generation_time
);
println!(
"\t\tSample writing: {:.3}s",
ntime * rstats.sample_writing_time
);
}
// Write to disk
if !args.is_present("serialized_output") {
println!("Writing image to disk into '{}'...", r.output_file);
if r.output_file.ends_with(".png") {
image
.write_png(Path::new(&r.output_file))
.expect("Failed to write png...");
} else if r.output_file.ends_with(".exr") {
image.write_exr(Path::new(&r.output_file));
} else {
panic!("Unknown output file extension.");
}
println!("\tWrote image in {:.3}s", t.tick());
}
println!("{:#?}", scene);
// Print memory stats if stats are wanted.
if args.is_present("stats") {
// let arena_stats = arena.stats();
// let mib_occupied = arena_stats.0 as f64 / 1_048_576.0;
// let mib_allocated = arena_stats.1 as f64 / 1_048_576.0;
println!("Didn't really render, because all that code is disabled! Done!");
// let (mut image, rstats) = r.render(
// max_samples_per_bucket,
// crop,
// thread_count,
// args.is_present("serialized_output"),
// );
// // Print render stats
// if !args.is_present("serialized_output") {
// let rtime = t.tick();
// let ntime = rtime as f64 / rstats.total_time;
// println!("\tRendered scene in {:.3}s", rtime);
// println!(
// "\t\tTrace: {:.3}s",
// ntime * rstats.trace_time
// );
// println!("\t\t\tRays traced: {}", rstats.ray_count);
// println!(
// "\t\t\tRays/sec: {}",
// (rstats.ray_count as f64 / (ntime * rstats.trace_time) as f64) as u64
// );
// println!("\t\t\tRay/node tests: {}", rstats.accel_node_visits);
// println!(
// "\t\tInitial ray generation: {:.3}s",
// ntime * rstats.initial_ray_generation_time
// );
// println!(
// "\t\tRay generation: {:.3}s",
// ntime * rstats.ray_generation_time
// );
// println!(
// "\t\tSample writing: {:.3}s",
// ntime * rstats.sample_writing_time
// );
// }
// println!("MemArena stats:");
// // Write to disk
// if !args.is_present("serialized_output") {
// println!("Writing image to disk into '{}'...", r.output_file);
// if r.output_file.ends_with(".png") {
// image
// .write_png(Path::new(&r.output_file))
// .expect("Failed to write png...");
// } else if r.output_file.ends_with(".exr") {
// image.write_exr(Path::new(&r.output_file));
// } else {
// panic!("Unknown output file extension.");
// }
// println!("\tWrote image in {:.3}s", t.tick());
// }
// if mib_occupied >= 1.0 {
// println!("\tOccupied: {:.1} MiB", mib_occupied);
// } else {
// println!("\tOccupied: {:.4} MiB", mib_occupied);
// }
// // Print memory stats if stats are wanted.
// if args.is_present("stats") {
// let arena_stats = arena.stats();
// let mib_occupied = arena_stats.0 as f64 / 1_048_576.0;
// let mib_allocated = arena_stats.1 as f64 / 1_048_576.0;
// if mib_allocated >= 1.0 {
// println!("\tUsed: {:.1} MiB", mib_allocated);
// } else {
// println!("\tUsed: {:.4} MiB", mib_allocated);
// }
// println!("MemArena stats:");
// println!("\tTotal blocks: {}", arena_stats.2);
}
// if mib_occupied >= 1.0 {
// println!("\tOccupied: {:.1} MiB", mib_occupied);
// } else {
// println!("\tOccupied: {:.4} MiB", mib_occupied);
// }
// if mib_allocated >= 1.0 {
// println!("\tUsed: {:.1} MiB", mib_allocated);
// } else {
// println!("\tUsed: {:.4} MiB", mib_allocated);
// }
// println!("\tTotal blocks: {}", arena_stats.2);
// }
}
}
}

View File

@ -49,6 +49,13 @@ impl<'a> DataTree<'a> {
}
}
pub fn ident(&'a self) -> Option<&'a str> {
match *self {
DataTree::Internal { ident, .. } => ident,
DataTree::Leaf { .. } => None,
}
}
pub fn byte_offset(&'a self) -> usize {
match *self {
DataTree::Internal { byte_offset, .. } | DataTree::Leaf { byte_offset, .. } => {

View File

@ -1,6 +1,6 @@
#![allow(dead_code)]
use std::{f32, result::Result};
use std::{collections::HashMap, f32, result::Result};
use nom::{combinator::all_consuming, sequence::tuple, IResult};
@ -11,15 +11,17 @@ use crate::{
color::{rec709_e_to_xyz, Color},
light::WorldLightSource,
math::Matrix4x4,
renderer::Renderer,
// renderer::Renderer,
scene::Scene,
scene::World,
shading::SurfaceShader,
};
use super::{
basics::{ws_f32, ws_u32},
psy_assembly::parse_assembly,
psy_light::parse_distant_disk_light,
psy_surface_shader::parse_surface_shader,
DataTree,
};
@ -31,6 +33,7 @@ pub enum PsyParseError {
UnknownVariant(usize, &'static str), // Error message
ExpectedInternalNode(usize, &'static str), // Error message
ExpectedLeafNode(usize, &'static str), // Error message
ExpectedIdent(usize, &'static str), // Error message
MissingNode(usize, &'static str), // Error message
IncorrectLeafData(usize, &'static str), // Error message
WrongNodeCount(usize, &'static str, usize), // Error message, sections found
@ -64,6 +67,11 @@ impl PsyParseError {
println!("Line {}: {}", line, error);
}
PsyParseError::ExpectedIdent(offset, error) => {
let line = line_count_to_byte_offset(psy_content, offset);
println!("Line {}: {}", line, error);
}
PsyParseError::MissingNode(offset, error) => {
let line = line_count_to_byte_offset(psy_content, offset);
println!("Line {}: {}", line, error);
@ -95,7 +103,7 @@ fn line_count_to_byte_offset(text: &str, offset: usize) -> usize {
pub fn parse_scene<'a>(
arena: &'a Arena,
tree: &'a DataTree,
) -> Result<Renderer<'a>, PsyParseError> {
) -> Result<Scene<'a>, PsyParseError> {
// Verify we have the right number of each section
if tree.iter_children_with_type("Output").count() != 1 {
let count = tree.iter_children_with_type("Output").count();
@ -132,6 +140,14 @@ pub fn parse_scene<'a>(
count,
));
}
if tree.iter_children_with_type("Shaders").count() != 1 {
let count = tree.iter_children_with_type("Shaders").count();
return Err(PsyParseError::WrongNodeCount(
tree.byte_offset(),
"Scene should have precisely one Shaders section.",
count,
));
}
if tree.iter_children_with_type("Assembly").count() != 1 {
let count = tree.iter_children_with_type("Assembly").count();
return Err(PsyParseError::WrongNodeCount(
@ -161,6 +177,9 @@ pub fn parse_scene<'a>(
// Parse world
let world = parse_world(arena, tree.iter_children_with_type("World").nth(0).unwrap())?;
// Parse shaders
let shaders = parse_shaders(tree.iter_children_with_type("Shaders").nth(0).unwrap())?;
// Parse root scene assembly
let assembly = parse_assembly(
arena,
@ -178,25 +197,25 @@ pub fn parse_scene<'a>(
None
};
let scene = Scene {
name: scene_name,
camera: camera,
world: world,
root: assembly,
shaders: shaders,
root_assembly: assembly,
};
// Put renderer together
let renderer = Renderer {
output_file: output_info.clone(),
resolution: (
(render_settings.0).0 as usize,
(render_settings.0).1 as usize,
),
spp: render_settings.1 as usize,
seed: render_settings.2,
scene: scene,
};
// // Put renderer together
// let renderer = Renderer {
// output_file: output_info.clone(),
// resolution: (
// (render_settings.0).0 as usize,
// (render_settings.0).1 as usize,
// ),
// spp: render_settings.1 as usize,
// seed: render_settings.2,
// scene: scene,
// };
return Ok(renderer);
return Ok(scene);
}
fn parse_output_info(tree: &DataTree) -> Result<String, PsyParseError> {
@ -553,6 +572,44 @@ fn parse_world<'a>(arena: &'a Arena, tree: &'a DataTree) -> Result<World<'a>, Ps
}
}
fn parse_shaders<'a>(
tree: &'a DataTree,
) -> Result<HashMap<String, Box<dyn SurfaceShader>>, PsyParseError> {
if tree.is_internal() {
let mut shaders = HashMap::new();
for shader_item in tree.iter_children() {
match shader_item {
DataTree::Internal {
type_name,
ident,
children,
byte_offset,
} if type_name == &"SurfaceShader" => {
if let Some(name) = ident {
shaders.insert(name.to_string(), parse_surface_shader(shader_item)?);
} else {
// TODO: error.
}
}
_ => {
// TODO: an error.
}
}
}
// Return the list of shaders.
return Ok(shaders);
} else {
return Err(PsyParseError::ExpectedInternalNode(
tree.byte_offset(),
"Shaders section should be an internal \
node.",
));
}
}
pub fn parse_matrix(contents: &str) -> Result<Matrix4x4, PsyParseError> {
if let IResult::Ok((leftover, ns)) = all_consuming(tuple((
ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32,

View File

@ -4,13 +4,12 @@ use std::result::Result;
use kioku::Arena;
use crate::scene::{Assembly, AssemblyBuilder, Object};
use crate::scene::{Assembly, Object, ObjectData};
use super::{
psy::{parse_matrix, PsyParseError},
psy_light::{parse_rectangle_light, parse_sphere_light},
psy_mesh_surface::parse_mesh_surface,
psy_surface_shader::parse_surface_shader,
DataTree,
};
@ -18,171 +17,93 @@ pub fn parse_assembly<'a>(
arena: &'a Arena,
tree: &'a DataTree,
) -> Result<Assembly<'a>, PsyParseError> {
let mut builder = AssemblyBuilder::new(arena);
if tree.is_internal() {
for child in tree.iter_children() {
match child.type_name() {
// Sub-Assembly
"Assembly" => {
if let DataTree::Internal {
ident: Some(ident), ..
} = *child
{
builder.add_assembly(ident, parse_assembly(arena, child)?);
} else {
return Err(PsyParseError::UnknownError(child.byte_offset()));
}
}
// Instance
"Instance" => {
// Pre-conditions
if !child.is_internal() {
return Err(PsyParseError::UnknownError(child.byte_offset()));
}
// Get data name
let name = {
if child.iter_leaf_children_with_type("Data").count() != 1 {
return Err(PsyParseError::UnknownError(child.byte_offset()));
}
child.iter_leaf_children_with_type("Data").nth(0).unwrap().1
};
// Get surface shader binding, if any.
let surface_shader_name = if child
.iter_leaf_children_with_type("SurfaceShaderBind")
.count()
> 0
{
Some(
child
.iter_leaf_children_with_type("SurfaceShaderBind")
.nth(0)
.unwrap()
.1,
)
} else {
None
};
// Get xforms
let mut xforms = Vec::new();
for (_, contents, _) in child.iter_leaf_children_with_type("Transform") {
xforms.push(parse_matrix(contents)?);
}
// Add instance
if builder.name_exists(name) {
builder.add_instance(name, surface_shader_name, Some(&xforms));
} else {
return Err(PsyParseError::InstancedMissingData(
child.iter_leaf_children_with_type("Data").nth(0).unwrap().2,
"Attempted to add \
instance for data with \
a name that doesn't \
exist.",
name.to_string(),
));
}
}
// SurfaceShader
"SurfaceShader" => {
if let DataTree::Internal {
ident: Some(ident), ..
} = *child
{
builder.add_surface_shader(ident, parse_surface_shader(arena, child)?);
} else {
// TODO: error condition of some kind, because no ident
panic!(
"SurfaceShader encountered that was a leaf, but SurfaceShaders cannot \
be a leaf: {}",
child.byte_offset()
);
}
}
// MeshSurface
"MeshSurface" => {
if let DataTree::Internal {
ident: Some(ident), ..
} = *child
{
builder.add_object(
ident,
Object::Surface(arena.alloc(parse_mesh_surface(arena, child)?)),
);
} else {
// TODO: error condition of some kind, because no ident
panic!(
"MeshSurface encountered that was a leaf, but MeshSurfaces cannot \
be a leaf: {}",
child.byte_offset()
);
}
}
// Sphere Light
"SphereLight" => {
if let DataTree::Internal {
ident: Some(ident), ..
} = *child
{
builder.add_object(
ident,
Object::SurfaceLight(arena.alloc(parse_sphere_light(arena, child)?)),
);
} else {
// No ident
return Err(PsyParseError::UnknownError(child.byte_offset()));
}
}
// Rectangle Light
"RectangleLight" => {
if let DataTree::Internal {
ident: Some(ident), ..
} = *child
{
builder.add_object(
ident,
Object::SurfaceLight(arena.alloc(parse_rectangle_light(arena, child)?)),
);
} else {
// No ident
return Err(PsyParseError::UnknownError(child.byte_offset()));
}
}
_ => {
// TODO: some kind of error, because not a known type name
} // // Bilinear Patch
// "BilinearPatch" => {
// assembly->add_object(child.name, parse_bilinear_patch(child));
// }
//
// // Bicubic Patch
// else if (child.type == "BicubicPatch") {
// assembly->add_object(child.name, parse_bicubic_patch(child));
// }
//
// // Subdivision surface
// else if (child.type == "SubdivisionSurface") {
// assembly->add_object(child.name, parse_subdivision_surface(child));
// }
//
// // Sphere
// else if (child.type == "Sphere") {
// assembly->add_object(child.name, parse_sphere(child));
// }
}
}
} else {
if !tree.is_internal() {
return Err(PsyParseError::UnknownError(tree.byte_offset()));
}
return Ok(builder.build());
let mut assembly = Assembly::new();
for object in tree.iter_children() {
if object.type_name() == "Object" {
// Get object identifier.
let object_ident = if let Some(ident) = object.ident() {
ident
} else {
return Err(PsyParseError::ExpectedIdent(
object.byte_offset(),
"\'Object\' types must have an identifier, but the identifier is missing.",
));
};
// Collect instances.
let mut instance_xform_idxs = Vec::new();
for instance in object.iter_children_with_type("Instance") {
if !instance.is_internal() {
// TODO: error.
}
let xforms_start_idx = assembly.xforms.len();
for (_, contents, _) in instance.iter_leaf_children_with_type("Transform") {
assembly.xforms.push(parse_matrix(contents)?);
}
instance_xform_idxs.push(xforms_start_idx..assembly.xforms.len());
}
// Get object data.
let object_data = {
let obj_data_tree = {
if object
.iter_children()
.filter(|d| d.type_name() != "Instance")
.count()
!= 1
{
// TODO: error.
}
object
.iter_children()
.filter(|d| d.type_name() != "Instance")
.nth(0)
.unwrap()
};
match obj_data_tree.type_name() {
// Sub-Assembly
"Assembly" => {
ObjectData::Assembly(Box::new(parse_assembly(arena, obj_data_tree)?))
}
"MeshSurface" => {
ObjectData::Surface(Box::new(parse_mesh_surface(arena, obj_data_tree)?))
}
"SphereLight" => {
ObjectData::Light(Box::new(parse_sphere_light(arena, obj_data_tree)?))
}
"RectangleLight" => {
ObjectData::Light(Box::new(parse_rectangle_light(arena, obj_data_tree)?))
}
_ => {
return Err(PsyParseError::UnknownVariant(
tree.byte_offset(),
"Unknown data type for Object.",
));
}
}
};
assembly.objects.insert(
object_ident.to_string(),
Object {
data: object_data,
instance_xform_idxs: instance_xform_idxs,
},
);
} else {
// TODO: error.
}
}
return Ok(assembly);
}

View File

@ -4,8 +4,6 @@ use std::result::Result;
use nom::{combinator::all_consuming, IResult};
use kioku::Arena;
use crate::shading::{SimpleSurfaceShader, SurfaceShader};
use super::{
@ -21,10 +19,7 @@ use super::{
// accel: BVH,
// }
pub fn parse_surface_shader<'a>(
arena: &'a Arena,
tree: &'a DataTree,
) -> Result<&'a dyn SurfaceShader, PsyParseError> {
pub fn parse_surface_shader(tree: &DataTree) -> Result<Box<dyn SurfaceShader>, PsyParseError> {
let type_name = if let Some((_, text, _)) = tree.iter_leaf_children_with_type("Type").nth(0) {
text.trim()
} else {
@ -52,7 +47,7 @@ pub fn parse_surface_shader<'a>(
));
};
arena.alloc(SimpleSurfaceShader::Lambert { color: color })
Box::new(SimpleSurfaceShader::Lambert { color: color })
}
"GGX" => {
@ -105,7 +100,7 @@ pub fn parse_surface_shader<'a>(
));
};
arena.alloc(SimpleSurfaceShader::GGX {
Box::new(SimpleSurfaceShader::GGX {
color: color,
roughness: roughness,
fresnel: fresnel,
@ -129,7 +124,7 @@ pub fn parse_surface_shader<'a>(
));
};
arena.alloc(SimpleSurfaceShader::Emit { color: color })
Box::new(SimpleSurfaceShader::Emit { color: color })
}
_ => unimplemented!(),

View File

@ -1,417 +1,34 @@
use std::collections::HashMap;
use std::{collections::HashMap, ops::Range};
use kioku::Arena;
use crate::{light::SurfaceLight, math::Matrix4x4, surface::Surface};
use crate::{
accel::BVH4,
accel::{LightAccel, LightTree},
bbox::{transform_bbox_slice_from, BBox},
boundable::Boundable,
color::SpectralSample,
lerp::lerp_slice,
light::SurfaceLight,
math::{Matrix4x4, Normal, Point},
shading::SurfaceShader,
surface::{Surface, SurfaceIntersection},
transform_stack::TransformStack,
};
#[derive(Copy, Clone, Debug)]
#[derive(Debug)]
pub struct Assembly<'a> {
// Instance list
pub instances: &'a [Instance],
pub light_instances: &'a [Instance],
pub xforms: &'a [Matrix4x4],
// Surface shader list
pub surface_shaders: &'a [&'a dyn SurfaceShader],
// Object list
pub objects: &'a [Object<'a>],
// Assembly list
pub assemblies: &'a [Assembly<'a>],
// Object accel
pub object_accel: BVH4<'a>,
// Light accel
pub light_accel: LightTree<'a>,
pub objects: HashMap<String, Object<'a>>, // Name, Object.
pub xforms: Vec<Matrix4x4>,
}
// TODO: actually fix this clippy warning, rather than `allow`ing it.
#[allow(clippy::type_complexity)]
impl<'a> Assembly<'a> {
// Returns (light_color, (sample_point, normal, point_err), pdf, selection_pdf)
pub fn sample_lights(
&self,
xform_stack: &mut TransformStack,
n: f32,
uvw: (f32, f32, f32),
wavelength: f32,
time: f32,
intr: &SurfaceIntersection,
) -> Option<(SpectralSample, (Point, Normal, f32), f32, f32)> {
if let SurfaceIntersection::Hit {
intersection_data: idata,
closure,
} = *intr
{
let sel_xform = if !xform_stack.top().is_empty() {
lerp_slice(xform_stack.top(), time)
} else {
Matrix4x4::new()
};
if let Some((light_i, sel_pdf, whittled_n)) = self.light_accel.select(
idata.incoming * sel_xform,
idata.pos * sel_xform,
idata.nor * sel_xform,
idata.nor_g * sel_xform,
&closure,
time,
n,
) {
let inst = self.light_instances[light_i];
match inst.instance_type {
InstanceType::Object => {
match self.objects[inst.data_index] {
Object::SurfaceLight(light) => {
// Get the world-to-object space transform of the light
let xform = if let Some((a, b)) = inst.transform_indices {
let pxforms = xform_stack.top();
let xform = lerp_slice(&self.xforms[a..b], time);
if !pxforms.is_empty() {
lerp_slice(pxforms, time) * xform
} else {
xform
}
} else {
let pxforms = xform_stack.top();
if !pxforms.is_empty() {
lerp_slice(pxforms, time)
} else {
Matrix4x4::new()
}
};
// Sample the light
let (color, sample_geo, pdf) = light.sample_from_point(
&xform, idata.pos, uvw.0, uvw.1, wavelength, time,
);
return Some((color, sample_geo, pdf, sel_pdf));
}
_ => unimplemented!(),
}
}
InstanceType::Assembly => {
// Push the world-to-object space transforms of the assembly onto
// the transform stack.
if let Some((a, b)) = inst.transform_indices {
xform_stack.push(&self.xforms[a..b]);
}
// Sample sub-assembly lights
let sample = self.assemblies[inst.data_index].sample_lights(
xform_stack,
whittled_n,
uvw,
wavelength,
time,
intr,
);
// Pop the assembly's transforms off the transform stack.
if inst.transform_indices.is_some() {
xform_stack.pop();
}
// Return sample
return sample.map(|(ss, v, pdf, spdf)| (ss, v, pdf, spdf * sel_pdf));
}
}
} else {
None
}
} else {
None
pub fn new() -> Assembly<'a> {
Assembly {
objects: HashMap::new(),
xforms: Vec::new(),
}
}
}
impl<'a> Boundable for Assembly<'a> {
fn bounds(&self) -> &[BBox] {
self.object_accel.bounds()
}
}
#[derive(Debug)]
pub struct AssemblyBuilder<'a> {
arena: &'a Arena,
pub struct Object<'a> {
pub data: ObjectData<'a>,
// Instance list
instances: Vec<Instance>,
xforms: Vec<Matrix4x4>,
// Shader list
surface_shaders: Vec<&'a dyn SurfaceShader>,
surface_shader_map: HashMap<String, usize>, // map Name -> Index
// Object list
objects: Vec<Object<'a>>,
object_map: HashMap<String, usize>, // map Name -> Index
// Assembly list
assemblies: Vec<Assembly<'a>>,
assembly_map: HashMap<String, usize>, // map Name -> Index
// One range per instance, indexing into the assembly's xforms array.
pub instance_xform_idxs: Vec<Range<usize>>,
}
impl<'a> AssemblyBuilder<'a> {
pub fn new(arena: &'a Arena) -> AssemblyBuilder<'a> {
AssemblyBuilder {
arena: arena,
instances: Vec::new(),
xforms: Vec::new(),
surface_shaders: Vec::new(),
surface_shader_map: HashMap::new(),
objects: Vec::new(),
object_map: HashMap::new(),
assemblies: Vec::new(),
assembly_map: HashMap::new(),
}
}
pub fn add_surface_shader(&mut self, name: &str, shader: &'a dyn SurfaceShader) {
// Make sure the name hasn't already been used.
if self.surface_shader_map.contains_key(name) {
panic!("Attempted to add surface shader to assembly with a name that already exists.");
}
// Add shader
self.surface_shader_map
.insert(name.to_string(), self.surface_shaders.len());
self.surface_shaders.push(shader);
}
pub fn add_object(&mut self, name: &str, obj: Object<'a>) {
// Make sure the name hasn't already been used.
if self.name_exists(name) {
panic!("Attempted to add object to assembly with a name that already exists.");
}
// Add object
self.object_map.insert(name.to_string(), self.objects.len());
self.objects.push(obj);
}
pub fn add_assembly(&mut self, name: &str, asmb: Assembly<'a>) {
// Make sure the name hasn't already been used.
if self.name_exists(name) {
panic!(
"Attempted to add assembly to another assembly with a name that already \
exists."
);
}
// Add assembly
self.assembly_map
.insert(name.to_string(), self.assemblies.len());
self.assemblies.push(asmb);
}
pub fn add_instance(
&mut self,
name: &str,
surface_shader_name: Option<&str>,
xforms: Option<&[Matrix4x4]>,
) {
// Make sure name exists
if !self.name_exists(name) {
panic!("Attempted to add instance with a name that doesn't exist.");
}
// Map zero-length transforms to None
let xforms = if let Some(xf) = xforms {
if !xf.is_empty() {
Some(xf)
} else {
None
}
} else {
None
};
// Create instance
let instance = if self.object_map.contains_key(name) {
Instance {
instance_type: InstanceType::Object,
data_index: self.object_map[name],
surface_shader_index: surface_shader_name.map(|name| {
*self
.surface_shader_map
.get(name)
.unwrap_or_else(|| panic!("Unknown surface shader '{}'.", name))
}),
id: self.instances.len(),
transform_indices: xforms
.map(|xf| (self.xforms.len(), self.xforms.len() + xf.len())),
}
} else {
Instance {
instance_type: InstanceType::Assembly,
data_index: self.assembly_map[name],
surface_shader_index: surface_shader_name.map(|name| {
*self
.surface_shader_map
.get(name)
.unwrap_or_else(|| panic!("Unknown surface shader '{}'.", name))
}),
id: self.instances.len(),
transform_indices: xforms
.map(|xf| (self.xforms.len(), self.xforms.len() + xf.len())),
}
};
self.instances.push(instance);
// Store transforms
if let Some(xf) = xforms {
self.xforms.extend(xf);
}
}
pub fn name_exists(&self, name: &str) -> bool {
self.object_map.contains_key(name) || self.assembly_map.contains_key(name)
}
pub fn build(mut self) -> Assembly<'a> {
// Calculate instance bounds, used for building object accel and light accel.
let (bis, bbs) = self.instance_bounds();
// Build object accel
let object_accel = BVH4::from_objects(self.arena, &mut self.instances[..], 1, |inst| {
&bbs[bis[inst.id]..bis[inst.id + 1]]
});
// Get list of instances that are for light sources or assemblies that contain light
// sources.
let mut light_instances: Vec<_> = self
.instances
.iter()
.filter(|inst| match inst.instance_type {
InstanceType::Object => {
if let Object::SurfaceLight(_) = self.objects[inst.data_index] {
true
} else {
false
}
}
InstanceType::Assembly => {
self.assemblies[inst.data_index]
.light_accel
.approximate_energy()
> 0.0
}
})
.cloned()
.collect();
// Build light accel
let light_accel = LightTree::from_objects(self.arena, &mut light_instances[..], |inst| {
let bounds = &bbs[bis[inst.id]..bis[inst.id + 1]];
let energy = match inst.instance_type {
InstanceType::Object => {
if let Object::SurfaceLight(light) = self.objects[inst.data_index] {
light.approximate_energy()
} else {
0.0
}
}
InstanceType::Assembly => self.assemblies[inst.data_index]
.light_accel
.approximate_energy(),
};
(bounds, energy)
});
Assembly {
instances: self.arena.copy_slice(&self.instances),
light_instances: self.arena.copy_slice(&light_instances),
xforms: self.arena.copy_slice(&self.xforms),
surface_shaders: self.arena.copy_slice(&self.surface_shaders),
objects: self.arena.copy_slice(&self.objects),
assemblies: self.arena.copy_slice(&self.assemblies),
object_accel: object_accel,
light_accel: light_accel,
}
}
/// Returns a pair of vectors with the bounds of all instances.
/// This is used for building the assembly's BVH4.
fn instance_bounds(&self) -> (Vec<usize>, Vec<BBox>) {
let mut indices = vec![0];
let mut bounds = Vec::new();
for inst in &self.instances {
let mut bbs = Vec::new();
let mut bbs2 = Vec::new();
// Get bounding boxes
match inst.instance_type {
InstanceType::Object => {
// Push bounds onto bbs
let obj = &self.objects[inst.data_index];
match *obj {
Object::Surface(s) => bbs.extend(s.bounds()),
Object::SurfaceLight(l) => bbs.extend(l.bounds()),
}
}
InstanceType::Assembly => {
// Push bounds onto bbs
let asmb = &self.assemblies[inst.data_index];
bbs.extend(asmb.bounds());
}
}
// Transform the bounding boxes, if necessary
if let Some((xstart, xend)) = inst.transform_indices {
let xf = &self.xforms[xstart..xend];
transform_bbox_slice_from(&bbs, xf, &mut bbs2);
} else {
bbs2.clear();
bbs2.extend(bbs);
}
// Push transformed bounds onto vec
bounds.extend(bbs2);
indices.push(bounds.len());
}
(indices, bounds)
}
}
#[derive(Copy, Clone, Debug)]
pub enum Object<'a> {
Surface(&'a dyn Surface),
SurfaceLight(&'a dyn SurfaceLight),
}
#[derive(Debug, Copy, Clone)]
pub struct Instance {
pub instance_type: InstanceType,
pub data_index: usize,
pub surface_shader_index: Option<usize>,
pub id: usize,
pub transform_indices: Option<(usize, usize)>,
}
#[derive(Debug, Copy, Clone)]
pub enum InstanceType {
Object,
Assembly,
#[derive(Debug)]
pub enum ObjectData<'a> {
Empty,
Surface(Box<dyn Surface + 'a>),
Light(Box<dyn SurfaceLight + 'a>),
Assembly(Box<Assembly<'a>>),
}

View File

@ -1,151 +1,19 @@
mod assembly;
mod world;
use crate::{
accel::LightAccel,
algorithm::weighted_choice,
camera::Camera,
color::SpectralSample,
math::{Normal, Point, Vector},
surface::SurfaceIntersection,
transform_stack::TransformStack,
};
use std::collections::HashMap;
use crate::{camera::Camera, shading::SurfaceShader};
pub use self::{
assembly::{Assembly, AssemblyBuilder, InstanceType, Object},
assembly::{Assembly, Object, ObjectData},
world::World,
};
#[derive(Debug)]
pub struct Scene<'a> {
pub name: Option<String>,
pub camera: Camera<'a>,
pub world: World<'a>,
pub root: Assembly<'a>,
}
impl<'a> Scene<'a> {
pub fn sample_lights(
&self,
xform_stack: &mut TransformStack,
n: f32,
uvw: (f32, f32, f32),
wavelength: f32,
time: f32,
intr: &SurfaceIntersection,
) -> SceneLightSample {
// TODO: this just selects between world lights and local lights
// with a 50/50 chance. We should do something more sophisticated
// than this, accounting for the estimated impact of the lights
// on the point being lit.
// Calculate relative probabilities of traversing into world lights
// or local lights.
let wl_energy = if self
.world
.lights
.iter()
.fold(0.0, |energy, light| energy + light.approximate_energy())
<= 0.0
{
0.0
} else {
1.0
};
let ll_energy = if self.root.light_accel.approximate_energy() <= 0.0 {
0.0
} else {
1.0
};
let tot_energy = wl_energy + ll_energy;
// Decide either world or local lights, and select and sample a light.
if tot_energy <= 0.0 {
return SceneLightSample::None;
} else {
let wl_prob = wl_energy / tot_energy;
if n < wl_prob {
// World lights
let n = n / wl_prob;
let (i, p) = weighted_choice(self.world.lights, n, |l| l.approximate_energy());
let (ss, sv, pdf) =
self.world.lights[i].sample_from_point(uvw.0, uvw.1, wavelength, time);
return SceneLightSample::Distant {
color: ss,
direction: sv,
pdf: pdf,
selection_pdf: p * wl_prob,
};
} else {
// Local lights
let n = (n - wl_prob) / (1.0 - wl_prob);
if let Some((ss, sgeo, pdf, spdf)) =
self.root
.sample_lights(xform_stack, n, uvw, wavelength, time, intr)
{
return SceneLightSample::Surface {
color: ss,
sample_geo: sgeo,
pdf: pdf,
selection_pdf: spdf * (1.0 - wl_prob),
};
} else {
return SceneLightSample::None;
}
}
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum SceneLightSample {
None,
Distant {
color: SpectralSample,
direction: Vector,
pdf: f32,
selection_pdf: f32,
},
Surface {
color: SpectralSample,
sample_geo: (Point, Normal, f32),
pdf: f32,
selection_pdf: f32,
},
}
impl SceneLightSample {
pub fn is_none(&self) -> bool {
if let SceneLightSample::None = *self {
true
} else {
false
}
}
pub fn color(&self) -> SpectralSample {
match *self {
SceneLightSample::None => panic!(),
SceneLightSample::Distant { color, .. } => color,
SceneLightSample::Surface { color, .. } => color,
}
}
pub fn pdf(&self) -> f32 {
match *self {
SceneLightSample::None => panic!(),
SceneLightSample::Distant { pdf, .. } => pdf,
SceneLightSample::Surface { pdf, .. } => pdf,
}
}
pub fn selection_pdf(&self) -> f32 {
match *self {
SceneLightSample::None => panic!(),
SceneLightSample::Distant { selection_pdf, .. } => selection_pdf,
SceneLightSample::Surface { selection_pdf, .. } => selection_pdf,
}
}
pub shaders: HashMap<String, Box<dyn SurfaceShader>>, // Name, Shader
pub root_assembly: Assembly<'a>,
}