use super::BasicScene; use super::entities::*; use crate::Arena; use crate::spectra::get_colorspace_device; use crate::utils::error::FileLoc; use crate::utils::normalize_utf8; use crate::utils::parameters::error_exit; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::parser::ParserTarget; use shared::Float; use shared::core::camera::CameraTransform; use shared::core::geometry::Vector3f; use shared::core::options::RenderingCoordinateSystem; use shared::spectra::RGBColorSpace; use shared::utils::transform; use shared::utils::transform::{AnimatedTransform, Transform}; use std::collections::{HashMap, HashSet}; use std::ops::{Index, IndexMut}; use std::sync::Arc; const MAX_TRANSFORMS: usize = 2; #[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(Default, 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 area_light_name: String, pub area_light_params: ParsedParameterVector, pub area_light_loc: FileLoc, 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, } #[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; 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) { if self.current_block != BlockState::WorldBlock { log::error!("{}: {} not allowed outside WorldBlock", loc, name); } } fn verify_options(&self, name: &str, loc: &FileLoc) { if self.current_block != BlockState::OptionsBlock { log::error!("{}: {} not allowed inside WorldBlock", loc, name); } } } impl ParserTarget for BasicSceneBuilder { fn reverse_orientation(&mut self, loc: FileLoc) { self.verify_world("ReverseOrientation", &loc); self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation; } fn color_space(&mut self, name: &str, loc: FileLoc) { let stdcs = get_colorspace_device(); let _ = match stdcs.get_named(name) { Ok(cs) => { self.graphics_state.color_space = unsafe { Some(Arc::new(*cs.as_ref())) }; } Err(_) => { eprintln!("Error: Color space '{}' unknown at {}", name, loc); } }; } fn identity(&mut self, _loc: FileLoc) { self.for_active_transforms(|_| Transform::identity()); } fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) { let t = Transform::translate(Vector3f::new(dx, dy, dz)); self.for_active_transforms(|cur| cur * &t); } fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) { let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az)); self.for_active_transforms(|cur| cur * &t); } fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) { let t = Transform::scale(sx, sy, sz); self.for_active_transforms(|cur| cur * &t); } 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, ) { let result = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz)); match result { Ok(t) => { self.for_active_transforms(|cur| cur * &t); } Err(e) => { eprintln!("Error: {} at {}", e, loc); } } } fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) { let result = Transform::from_flat(m); match result { Ok(t) => { self.for_active_transforms(|cur| cur * &t); } Err(e) => { eprintln!("Error: {} at {}", e, loc); } } } fn transform(&mut self, m: &[Float; 16], loc: FileLoc) { let result = Transform::from_flat(m); match result { Ok(t) => { self.for_active_transforms(|_| t); } Err(e) => { eprintln!("Error: {} at {}", e, loc); } } } fn coordinate_system(&mut self, name: &str, _loc: FileLoc) { self.named_coordinate_systems .insert(name.to_string(), self.graphics_state.ctm); } fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) { 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 ); } } fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { 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_from_world.t[0]; let parameters = ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); self.current_camera = Some(CameraSceneEntity { base: SceneEntity { name: name.to_string(), loc, parameters, }, camera_transform, medium: self.graphics_state.current_outside_medium.clone(), }); } fn active_transform_all(&mut self, _loc: FileLoc) { self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS; } fn active_transform_end_time(&mut self, _loc: FileLoc) { self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS; } fn active_transform_start_time(&mut self, _loc: FileLoc) { self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS; } fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) { self.verify_options("TransformTimes", &loc); self.graphics_state.transform_start_time = start; self.graphics_state.transform_end_time = end; } fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) { todo!() } fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { let parameters = ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); self.verify_options("PixelFilter", &loc); self.current_filter = Some(SceneEntity { name: name.to_string(), loc, parameters, }); } fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) { let parameters = ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); self.verify_options("Film", &loc); self.current_filter = Some(SceneEntity { name: type_name.to_string(), loc, parameters, }); } fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { let parameters = ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); self.verify_options("PixelFilter", &loc); self.current_filter = Some(SceneEntity { name: name.to_string(), loc, parameters, }); } fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { let parameters = ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); self.verify_options("PixelFilter", &loc); self.current_filter = Some(SceneEntity { name: name.to_string(), loc, parameters, }); } fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { let curr_name = normalize_utf8(name); self.verify_world("MakeNamedMaterial", &loc); if !self.named_material_names.insert(curr_name.to_string()) { eprintln!("Error: {}: named material '{}' redefined.", loc, name); return; } let parameters = ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); let entity = SceneEntity { name: name.to_string(), loc, parameters, }; self.scene.add_named_material(&curr_name, entity); } fn medium_interface(&mut self, inside_name: &str, outside_name: &str, _loc: FileLoc) { 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; } fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { let parameters = ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); self.verify_options("Sampler", &loc); self.current_sampler = Some(SceneEntity { name: name.to_string(), loc, parameters, }) } fn world_begin(&mut self, loc: FileLoc, arena: &Arena) { 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, ); } fn attribute_begin(&mut self, loc: FileLoc) { self.verify_world("AttributeBegin", &loc); self.pushed_graphics_states .push(self.graphics_state.clone()); self.push_stack.push(('a', loc)); } fn attribute_end(&mut self, loc: FileLoc) { self.verify_world("AttributeEnd", &loc); if self.pushed_graphics_states.is_empty() { log::error!( "[{:?}] Unmatched AttributeEnd encountered. Ignoring it.", loc ); return; } 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' { log::error!( "[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd", loc, start_loc ); std::process::exit(1); } else { debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack"); } } } fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc) { 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, _ => { log::error!( "[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \ \"material\", \"medium\", or \"texture\".", loc, target ); return; } }; 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()); } } fn texture( &mut self, orig_name: &str, type_name: &str, tex_name: &str, params: &ParsedParameterVector, loc: FileLoc, arena: Arc, ) { 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" { error_exit( Some(&loc), &format!( "{}: texture type unknown. Must be \"float\" or \"spectrum\".", tex_name ), ); } { let names = if type_name == "float" { &mut self.float_texture_names } else { &mut self.spectrum_texture_names }; if names.contains(&name) { error_exit(Some(&loc), &format!("Redefining texture \"{}\".", name)); } names.insert(name.to_string()); } let base = SceneEntity { name: tex_name.to_string(), parameters: dict, loc, }; let entity = TextureSceneEntity { base, render_from_object: self.graphics_state.ctm[0].clone(), }; 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); } } fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { self.verify_world("material", &loc); let entity = SceneEntity { name: name.to_string(), loc, parameters: ParameterDictionary::new(params.clone(), None), }; self.graphics_state.current_material_name = self.scene.add_material(entity).to_string(); } fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { todo!() } fn named_material(&mut self, _name: &str, _loc: FileLoc) { todo!() } fn light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { todo!() } fn area_light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { todo!() } fn shape(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { todo!() } fn object_begin(&mut self, _name: &str, _loc: FileLoc) { todo!() } fn object_end(&mut self, _loc: FileLoc) { todo!() } fn object_instance(&mut self, _name: &str, _loc: FileLoc) { todo!() } fn end_of_files(&mut self) { todo!() } }