diff --git a/src/main.rs b/src/main.rs index f802c77..2091f8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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::() ); - println!("LightPath size: {} bytes", mem::size_of::()); + // println!("LightPath size: {} bytes", mem::size_of::()); println!("BBox size: {} bytes", mem::size_of::()); // println!("BVHNode size: {} bytes", mem::size_of::()); println!("BVH4Node size: {} bytes", mem::size_of::()); @@ -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); + // } } } } diff --git a/src/parse/data_tree.rs b/src/parse/data_tree.rs index ace1aa0..e43b357 100644 --- a/src/parse/data_tree.rs +++ b/src/parse/data_tree.rs @@ -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, .. } => { diff --git a/src/parse/psy.rs b/src/parse/psy.rs index fb7d0e6..5ef9907 100644 --- a/src/parse/psy.rs +++ b/src/parse/psy.rs @@ -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, PsyParseError> { +) -> Result, 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 { @@ -553,6 +572,44 @@ fn parse_world<'a>(arena: &'a Arena, tree: &'a DataTree) -> Result, Ps } } +fn parse_shaders<'a>( + tree: &'a DataTree, +) -> Result>, 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 { 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, diff --git a/src/parse/psy_assembly.rs b/src/parse/psy_assembly.rs index 9083c5b..dc3ad0c 100644 --- a/src/parse/psy_assembly.rs +++ b/src/parse/psy_assembly.rs @@ -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, 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); } diff --git a/src/parse/psy_surface_shader.rs b/src/parse/psy_surface_shader.rs index b0b35f3..a934d9f 100644 --- a/src/parse/psy_surface_shader.rs +++ b/src/parse/psy_surface_shader.rs @@ -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, 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!(), diff --git a/src/scene/assembly.rs b/src/scene/assembly.rs index 3d36c87..dff79f5 100644 --- a/src/scene/assembly.rs +++ b/src/scene/assembly.rs @@ -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>, // Name, Object. + pub xforms: Vec, } -// 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, - xforms: Vec, - - // Shader list - surface_shaders: Vec<&'a dyn SurfaceShader>, - surface_shader_map: HashMap, // map Name -> Index - - // Object list - objects: Vec>, - object_map: HashMap, // map Name -> Index - - // Assembly list - assemblies: Vec>, - assembly_map: HashMap, // map Name -> Index + // One range per instance, indexing into the assembly's xforms array. + pub instance_xform_idxs: Vec>, } -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, Vec) { - 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, - 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), + Light(Box), + Assembly(Box>), } diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 3861c76..76a7c53 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -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, 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>, // Name, Shader + pub root_assembly: Assembly<'a>, }