use super::entities::*; use super::BasicScene; use crate::spectra::get_colorspace_device; use crate::utils::error::FileLoc; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::parser::{ParserError, ParserTarget}; use crate::Arena; use anyhow::Context; use shared::core::camera::CameraTransform; use shared::core::geometry::Vector3f; use shared::spectra::RGBColorSpace; use shared::utils::options::RenderingCoordinateSystem; use shared::utils::transform; use shared::utils::transform::{AnimatedTransform, Transform}; use shared::Float; use std::collections::{HashMap, HashSet}; use std::ops::{Index, IndexMut}; use std::sync::Arc; use unicode_normalization::UnicodeNormalization; const MAX_TRANSFORMS: usize = 2; fn normalize_utf8(input: &str) -> String { input.nfc().collect::() } #[derive(Debug, Default, Clone, Copy)] struct TransformSet { t: [Transform; MAX_TRANSFORMS], } impl TransformSet { fn is_animated(&self) -> bool { self.t[0] != self.t[1] } fn inverse(&self) -> Self { Self { t: [self.t[0].inverse(), self.t[1].inverse()], } } } impl Index for TransformSet { type Output = Transform; fn index(&self, i: usize) -> &Self::Output { &self.t[i] } } impl IndexMut for TransformSet { fn index_mut(&mut self, i: usize) -> &mut Self::Output { &mut self.t[i] } } #[derive(Clone, Debug)] struct PendingAreaLight { name: String, params: ParsedParameterVector, loc: FileLoc, } #[derive(Debug, Clone)] struct GraphicsState { pub current_inside_medium: String, pub current_outside_medium: String, pub current_material_name: String, pub current_material_index: Option, pub pending_area_light: Option, pub shape_attributes: ParsedParameterVector, pub light_attributes: ParsedParameterVector, pub material_attributes: ParsedParameterVector, pub medium_attributes: ParsedParameterVector, pub texture_attributes: ParsedParameterVector, pub reverse_orientation: bool, pub color_space: Option>, pub ctm: TransformSet, pub active_transform_bits: u32, pub transform_start_time: Float, pub transform_end_time: Float, } impl Default for GraphicsState { fn default() -> Self { Self { color_space: Some(crate::spectra::default_colorspace_arc()), active_transform_bits: BasicSceneBuilder::ALL_TRANSFORM_BITS, current_inside_medium: String::new(), current_outside_medium: String::new(), current_material_name: String::new(), current_material_index: None, pending_area_light: None, shape_attributes: Vec::new(), light_attributes: Vec::new(), material_attributes: Vec::new(), medium_attributes: Vec::new(), texture_attributes: Vec::new(), reverse_orientation: false, ctm: TransformSet::default(), transform_start_time: 0.0, transform_end_time: 1.0, } } } #[derive(PartialEq, Eq)] enum BlockState { OptionsBlock, WorldBlock, } pub struct BasicSceneBuilder { scene: Arc, current_block: BlockState, graphics_state: GraphicsState, pushed_graphics_states: Vec, push_stack: Vec<(char, FileLoc)>, render_from_world: Transform, named_coordinate_systems: HashMap, active_instance_definition: Option, float_texture_names: HashSet, spectrum_texture_names: HashSet, named_material_names: HashSet, medium_names: HashSet, current_camera: Option, current_film: Option, current_integrator: Option, current_sampler: Option, current_filter: Option, current_accelerator: Option, } impl BasicSceneBuilder { pub const START_TRANSFORM_BITS: u32 = 1 << 0; pub const END_TRANSFORM_BITS: u32 = 1 << 1; pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1; fn render_from_object_at(&self, index: usize) -> Transform { self.render_from_world * self.graphics_state.ctm[index] } fn render_from_object(&self) -> AnimatedTransform { AnimatedTransform::new( &self.render_from_object_at(0), self.graphics_state.transform_start_time, &self.render_from_object_at(1), self.graphics_state.transform_end_time, ) } pub fn new(scene: Arc) -> Self { Self { scene, current_block: BlockState::OptionsBlock, graphics_state: GraphicsState { active_transform_bits: Self::ALL_TRANSFORM_BITS, ..Default::default() }, pushed_graphics_states: Vec::new(), push_stack: Vec::new(), render_from_world: Transform::identity(), named_coordinate_systems: HashMap::new(), active_instance_definition: None, float_texture_names: HashSet::new(), spectrum_texture_names: HashSet::new(), named_material_names: HashSet::new(), medium_names: HashSet::new(), current_camera: Some(CameraSceneEntity { base: SceneEntity { name: "perspective".into(), ..Default::default() }, camera_transform: CameraTransform::from_world( AnimatedTransform::default(), RenderingCoordinateSystem::World, ), medium: String::new(), }), current_sampler: Some(SceneEntity { name: "zsobol".into(), ..Default::default() }), current_filter: Some(SceneEntity { name: "gaussian".into(), ..Default::default() }), current_integrator: Some(SceneEntity { name: "volpath".into(), ..Default::default() }), current_accelerator: Some(SceneEntity { name: "bvh".into(), ..Default::default() }), current_film: Some(SceneEntity { name: "rgb".into(), ..Default::default() }), } } fn for_active_transforms(&mut self, f: F) where F: Fn(&Transform) -> Transform, { let bits = self.graphics_state.active_transform_bits; if (bits & 1) != 0 { self.graphics_state.ctm.t[0] = f(&self.graphics_state.ctm.t[0]); } if (bits & 2) != 0 { self.graphics_state.ctm.t[1] = f(&self.graphics_state.ctm.t[1]); } } fn verify_world(&self, name: &str, loc: &FileLoc) -> Result<(), ParserError> { if self.current_block != BlockState::WorldBlock { return Err(ParserError::Generic( format!("{} not allowed outside WorldBlock", name), loc.clone(), )); } Ok(()) } fn verify_options(&self, name: &str, loc: &FileLoc) -> Result<(), ParserError> { if self.current_block != BlockState::OptionsBlock { return Err(ParserError::Generic( format!("{} not allowed inside WorldBlock", name), loc.clone(), )); } Ok(()) } fn make_params( &self, params: ParsedParameterVector, loc: &FileLoc, ) -> Result { ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()) .map_err(|e| ParserError::Generic(format!("parameter error: {}", e), loc.clone())) } } impl From for ParserError { fn from(e: anyhow::Error) -> Self { ParserError::Generic(e.to_string(), FileLoc::default()) } } impl ParserTarget for BasicSceneBuilder { fn reverse_orientation(&mut self, loc: FileLoc) -> Result<(), ParserError> { self.verify_world("ReverseOrientation", &loc)?; self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation; Ok(()) } fn color_space(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> { let stdcs = get_colorspace_device(); let _ = match stdcs.get_named(name) { Some(cs) => { self.graphics_state.color_space = unsafe { Some(Arc::new(*cs.as_ref())) }; } None => { eprintln!("Error: Color space '{}' unknown at {}", name, loc); } }; Ok(()) } fn identity(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.for_active_transforms(|_| Transform::identity()); Ok(()) } fn translate( &mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc, ) -> Result<(), ParserError> { let t = Transform::translate(Vector3f::new(dx, dy, dz)); self.for_active_transforms(|cur| cur * &t); Ok(()) } fn rotate( &mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc, ) -> Result<(), ParserError> { let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az)); self.for_active_transforms(|cur| cur * &t); Ok(()) } fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) -> Result<(), ParserError> { let t = Transform::scale(sx, sy, sz); self.for_active_transforms(|cur| cur * &t); Ok(()) } fn look_at( &mut self, ex: Float, ey: Float, ez: Float, lx: Float, ly: Float, lz: Float, ux: Float, uy: Float, uz: Float, loc: FileLoc, ) -> Result<(), ParserError> { let t = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz)) .with_context(|| format!("at {}", loc))?; self.for_active_transforms(|cur| cur * &t); Ok(()) } fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) -> Result<(), ParserError> { let result = Transform::from_flat(m); match result { Some(t) => { self.for_active_transforms(|cur| cur * &t); } None => { eprintln!("Error: Could not invert transform at {}", loc); } } Ok(()) } fn transform(&mut self, m: &[Float; 16], loc: FileLoc) -> Result<(), ParserError> { let result = Transform::from_flat(m); match result { Some(t) => { self.for_active_transforms(|_| t); } None => { eprintln!("Error: Could not invert transform at {}", loc); } } Ok(()) } fn coordinate_system(&mut self, name: &str, _loc: FileLoc) -> Result<(), ParserError> { self.named_coordinate_systems .insert(name.to_string(), self.graphics_state.ctm); Ok(()) } fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> { if let Some(saved_ctm) = self.named_coordinate_systems.get(name) { self.graphics_state.ctm = *saved_ctm; } else { eprintln!( "Warning: Couldn't find named coordinate system \"{}\" at {}", name, loc ); } Ok(()) } fn camera( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_options("Camera", &loc)?; let camera_from_world = self.graphics_state.ctm; let world_from_camera = camera_from_world.inverse(); self.named_coordinate_systems .insert("camera".to_string(), world_from_camera); let animated_world_from_cam = AnimatedTransform::new( &world_from_camera.t[0], self.graphics_state.transform_start_time, &world_from_camera.t[1], self.graphics_state.transform_end_time, ); let rendering_space = RenderingCoordinateSystem::CameraWorld; let camera_transform = CameraTransform::from_world(animated_world_from_cam, rendering_space); self.render_from_world = camera_transform.render_from_world(); let parameters = self.make_params(params, &loc)?; self.current_camera = Some(CameraSceneEntity { base: SceneEntity { name: name.to_string(), loc, parameters, }, camera_transform, medium: self.graphics_state.current_outside_medium.clone(), }); Ok(()) } fn active_transform_all(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS; Ok(()) } fn active_transform_end_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS; Ok(()) } fn active_transform_start_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS; Ok(()) } fn transform_times( &mut self, start: Float, end: Float, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_options("TransformTimes", &loc)?; self.graphics_state.transform_start_time = start; self.graphics_state.transform_end_time = end; Ok(()) } fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) -> Result<(), ParserError> { todo!() } fn pixel_filter( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_options("PixelFilter", &loc)?; let parameters = self.make_params(params, &loc)?; self.current_filter = Some(SceneEntity { name: name.to_string(), loc, parameters, }); Ok(()) } fn film( &mut self, type_name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_options("Film", &loc)?; let parameters = self.make_params(params, &loc)?; self.current_film = Some(SceneEntity { name: type_name.to_string(), loc, parameters, }); Ok(()) } fn accelerator( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_options("Accelerator", &loc)?; let parameters = self.make_params(params, &loc)?; self.current_accelerator = Some(SceneEntity { name: name.to_string(), loc, parameters, }); Ok(()) } fn integrator( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_options("Integrator", &loc)?; let parameters = self.make_params(params, &loc)?; self.current_integrator = Some(SceneEntity { name: name.to_string(), loc, parameters, }); Ok(()) } fn make_named_medium( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_world("MakeNamedMaterial", &loc)?; let curr_name = normalize_utf8(name); if !self.named_material_names.insert(curr_name.to_string()) { return Err(ParserError::Generic( format!("Named material '{}' redefined.", name), loc, )); } let parameters = self.make_params(params, &loc)?; let entity = SceneEntity { name: name.to_string(), loc, parameters, }; self.scene.add_named_material(&curr_name, entity); Ok(()) } fn medium_interface( &mut self, inside_name: &str, outside_name: &str, _loc: FileLoc, ) -> Result<(), ParserError> { let inside = normalize_utf8(inside_name); let outside = normalize_utf8(outside_name); self.graphics_state.current_inside_medium = inside; self.graphics_state.current_outside_medium = outside; Ok(()) } fn sampler( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_options("Sampler", &loc)?; let parameters = self.make_params(params, &loc)?; self.current_sampler = Some(SceneEntity { name: name.to_string(), loc, parameters, }); Ok(()) } fn world_begin(&mut self, loc: FileLoc, arena: &Arena) -> Result<(), ParserError> { self.verify_options("WorldBegin", &loc)?; self.current_block = BlockState::WorldBlock; for i in 0..MAX_TRANSFORMS { self.graphics_state.ctm[i] = Transform::default(); } self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS; self.named_coordinate_systems .insert("world".to_string(), self.graphics_state.ctm); let scene = Arc::clone(&self.scene); scene.set_options( self.current_filter .take() .expect("Filter not set before WorldBegin"), self.current_film .take() .expect("Film not set before WorldBegin"), self.current_camera .take() .expect("Camera not set before WorldBegin"), self.current_sampler .take() .expect("Sampler not set before WorldBegin"), self.current_integrator .take() .expect("Integrator not set before WorldBegin"), self.current_accelerator .take() .expect("Accelerator not set before WorldBegin"), &arena, ); Ok(()) } fn attribute_begin(&mut self, loc: FileLoc) -> Result<(), ParserError> { self.verify_world("AttributeBegin", &loc)?; self.pushed_graphics_states .push(self.graphics_state.clone()); self.push_stack.push(('a', loc)); Ok(()) } fn attribute_end(&mut self, loc: FileLoc) -> Result<(), ParserError> { self.verify_world("AttributeEnd", &loc)?; if self.pushed_graphics_states.is_empty() { return Err(ParserError::Generic( "Unmatched AttributeEnd encountered".into(), loc, )); } if let Some(state) = self.pushed_graphics_states.pop() { self.graphics_state = state; } if let Some((kind, start_loc)) = self.push_stack.pop() { if kind == 'o' { return Err(ParserError::Generic( format!("Mismatched nesting: open ObjectBegin from {}", start_loc), loc, )); } else { debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack"); } } Ok(()) } fn attribute( &mut self, target: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { let current_attributes = match target { "shape" => &mut self.graphics_state.shape_attributes, "light" => &mut self.graphics_state.light_attributes, "material" => &mut self.graphics_state.material_attributes, "medium" => &mut self.graphics_state.medium_attributes, "texture" => &mut self.graphics_state.texture_attributes, _ => { return Err(ParserError::Generic( format!( "Unknown attribute target \"{}\". Must be \"shape\", \"light\", \ \"material\", \"medium\", or \"texture\".", target ), loc, )); } }; let active_color_space = self.graphics_state.color_space.clone(); for mut p in params { p.may_be_unused = true; p.color_space = active_color_space.clone(); current_attributes.push(p.clone()); } Ok(()) } fn texture( &mut self, orig_name: &str, type_name: &str, tex_name: &str, params: ParsedParameterVector, loc: FileLoc, arena: Arc, ) -> Result<(), ParserError> { eprintln!( "TEXTURE: name='{}' type='{}' tex='{}'", orig_name, type_name, tex_name ); let name = normalize_utf8(orig_name); self.verify_world("Texture", &loc)?; let dict = ParameterDictionary::from_array( params.clone(), &self.graphics_state.texture_attributes, self.graphics_state.color_space.clone(), )?; if type_name != "float" && type_name != "spectrum" { return Err(ParserError::Generic( format!( "{}: texture type unknown. Must be \"float\" or \"spectrum\".", tex_name ), loc, )); } { let names = if type_name == "float" { &mut self.float_texture_names } else { &mut self.spectrum_texture_names }; if names.contains(&name) { return Err(ParserError::Generic( format!("Redefining texture \"{}\".", name), loc, )); } names.insert(name.to_string()); } let base = SceneEntity { name: tex_name.to_string(), parameters: dict, loc, }; let entity = TextureSceneEntity { base, render_from_object: self.render_from_object(), }; if type_name == "float" { self.scene .add_float_texture(name.to_string(), entity, arena); } else { self.scene .add_spectrum_texture(name.to_string(), entity, arena); } Ok(()) } fn material( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_world("material", &loc)?; let entity = SceneEntity { name: name.to_string(), loc, parameters: ParameterDictionary::new(params.clone(), None).unwrap(), }; self.graphics_state.current_material_name = self.scene.add_material(entity).to_string(); Ok(()) } fn make_named_material( &mut self, _name: &str, _params: ParsedParameterVector, _loc: FileLoc, ) -> Result<(), ParserError> { todo!() } fn named_material(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> { todo!() } fn light_source( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_world("LightSource", &loc)?; let dict = ParameterDictionary::from_array( params.clone(), &self.graphics_state.medium_attributes, self.graphics_state.color_space.clone(), )?; let render_from_light = self.render_from_object(); let entity = LightSceneEntity { transformed_base: TransformedSceneEntity { base: SceneEntity { name: name.to_string(), loc, parameters: dict, }, render_from_object: render_from_light, }, medium: self.graphics_state.current_outside_medium.clone(), }; self.scene.add_light(entity); Ok(()) } fn area_light_source( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_world("AreaLightSource", &loc)?; self.graphics_state.pending_area_light = Some(PendingAreaLight { name: name.to_string(), params: params.clone(), loc, }); Ok(()) } fn shape( &mut self, name: &str, params: ParsedParameterVector, loc: FileLoc, ) -> Result<(), ParserError> { self.verify_world("Shape", &loc)?; let dict = ParameterDictionary::from_array( params.clone(), &self.graphics_state.shape_attributes, self.graphics_state.color_space.clone(), )?; let render_from_object = self.render_from_object_at(0); let object_from_render = render_from_object.inverse(); let light_index = if let Some(ref al) = self.graphics_state.pending_area_light { let al_dict = self.make_params(al.params.clone(), &al.loc)?; let light_entity = SceneEntity { name: al.name.clone(), loc: al.loc.clone(), parameters: al_dict, }; Some(self.scene.add_area_light(light_entity)) } else { None }; let material = if !self.graphics_state.current_material_name.is_empty() { MaterialRef::Name(self.graphics_state.current_material_name.clone()) } else if let Some(idx) = self.graphics_state.current_material_index { MaterialRef::Index(idx) } else { MaterialRef::None }; let entity = ShapeSceneEntity { base: SceneEntity { name: name.to_string(), loc, parameters: dict, }, render_from_object: Arc::new(render_from_object), object_from_render: Arc::new(object_from_render), reverse_orientation: self.graphics_state.reverse_orientation, material, light_index, inside_medium: self.graphics_state.current_inside_medium.clone(), outside_medium: self.graphics_state.current_outside_medium.clone(), }; if self.active_instance_definition.is_some() { self.active_instance_definition .as_mut() .unwrap() .shapes .push(entity) } else { self.scene.add_shape(entity); } Ok(()) } fn object_begin(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> { Ok(()) } fn object_end(&mut self, _loc: FileLoc) -> Result<(), ParserError> { Ok(()) } fn object_instance(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> { Ok(()) } fn end_of_files(&mut self) -> Result<(), ParserError> { Ok(()) } }