use crate::utils::error::FileLoc; use shared::Float; use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f}; use shared::core::options::get_options; use shared::core::texture::{ FloatConstantTexture, FloatTexture, SpectrumConstantTexture, SpectrumTexture, SpectrumType, }; use shared::spectra::{ BlackbodySpectrum, ConstantSpectrum, PiecewiseLinearSpectrum, RGB, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, Spectrum, }; use once_cell::sync::Lazy; use std::cell::Cell; use std::collections::HashMap; use std::sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, }; static CACHED_SPECTRA: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); #[derive(Debug)] pub struct ParsedParameter { pub type_name: String, pub name: String, pub loc: FileLoc, pub floats: Vec, pub ints: Vec, pub strings: Vec, pub bools: Vec, pub looked_up: AtomicBool, pub may_be_unused: bool, pub color_space: Option>, } impl Default for ParsedParameter { fn default() -> Self { Self { type_name: String::new(), name: String::new(), loc: FileLoc::default(), floats: Vec::new(), ints: Vec::new(), strings: Vec::new(), bools: Vec::new(), looked_up: AtomicBool::new(false), may_be_unused: false, color_space: None, } } } impl Clone for ParsedParameter { fn clone(&self) -> Self { let looked_up_val = self.looked_up.load(Ordering::Relaxed); Self { type_name: self.type_name.clone(), name: self.name.clone(), loc: self.loc.clone(), floats: self.floats.clone(), ints: self.ints.clone(), strings: self.strings.clone(), bools: self.bools.clone(), looked_up: AtomicBool::new(looked_up_val), may_be_unused: self.may_be_unused, color_space: self.color_space.clone(), } } } impl ParsedParameter { pub fn new(loc: FileLoc) -> Self { Self { type_name: String::new(), name: String::new(), loc, floats: Vec::new(), ints: Vec::new(), strings: Vec::new(), bools: Vec::new(), looked_up: AtomicBool::new(false), may_be_unused: false, color_space: None, } } pub fn add_float(&mut self, v: Float) { self.floats.push(v); } pub fn add_int(&mut self, i: i32) { self.ints.push(i); } pub fn add_string(&mut self, s: String) { self.strings.push(s); } pub fn add_bool(&mut self, v: bool) { self.bools.push(v); } pub fn to_string(&self) -> String { format!("[ ParsedParameter {} {} ]", self.type_name, self.name) } } #[derive(Default)] pub struct NamedTextures { pub float_textures: HashMap>, pub albedo_spectrum_textures: HashMap>, pub unbounded_spectrum_textures: HashMap>, pub illuminant_spectrum_textures: HashMap>, } #[derive(Debug, Default, Clone)] pub struct ParameterDictionary { pub params: Vec, pub color_space: Option>, } impl ParameterDictionary { pub fn new(params: Vec, color_space: Option>) -> Self { Self { params, color_space, } } fn lookup_single( &self, name: &str, default_val: T, extractor: impl Fn(&ParsedParameter) -> Option<&Vec>, ) -> T { for param in &self.params { if param.name == name { if let Some(vec) = extractor(param) { if vec.len() == 1 { param.looked_up.store(true, Ordering::Relaxed); return vec[0].clone(); } } } } default_val } fn lookup_array( &self, name: &str, extractor: impl Fn(&ParsedParameter) -> Option<&Vec>, ) -> Vec { for param in &self.params { if param.name == name { if let Some(vec) = extractor(param) { param.looked_up.store(true, Ordering::Relaxed); return vec.clone(); } } } Vec::new() } pub fn get_one_float(&self, name: &str, def: Float) -> Float { self.lookup_single(name, def, |p| { if p.type_name == "float" { Some(&p.floats) } else { None } }) } pub fn get_one_int(&self, name: &str, def: i32) -> i32 { self.lookup_single(name, def, |p| { if p.type_name == "integer" { Some(&p.ints) } else { None } }) } pub fn get_one_bool(&self, name: &str, def: bool) -> bool { self.lookup_single(name, def, |p| { if p.type_name == "bool" { Some(&p.bools) } else { None } }) } pub fn get_one_string(&self, name: &str, def: &str) -> String { self.lookup_single(name, def.to_string(), |p| { if p.type_name == "string" { Some(&p.strings) } else { None } }) } pub fn get_float_array(&self, name: &str) -> Vec { self.lookup_array(name, |p| { if p.type_name == "float" { Some(&p.floats) } else { None } }) } pub fn get_int_array(&self, name: &str) -> Vec { self.lookup_array(name, |p| { if p.type_name == "integer" { Some(&p.ints) } else { None } }) } pub fn get_bool_array(&self, name: &str) -> Vec { self.lookup_array(name, |p| { if p.type_name == "bool" { Some(&p.bools) } else { None } }) } pub fn get_string_array(&self, name: &str) -> Vec { self.lookup_array(name, |p| { if p.type_name == "string" { Some(&p.strings) } else { None } }) } pub fn get_one_point3f(&self, name: &str, def: Point3f) -> Point3f { let floats = self.get_float_array(name); if floats.len() == 3 { Point3f::new(floats[0], floats[1], floats[2]) } else { def } } pub fn get_one_vector3f(&self, name: &str, def: Vector3f) -> Vector3f { let floats = self.get_float_array(name); if floats.len() == 3 { Vector3f::new(floats[0], floats[1], floats[2]) } else { def } } pub fn get_one_normal3f(&self, name: &str, def: Normal3f) -> Normal3f { let floats = self.get_float_array(name); if floats.len() == 3 { Normal3f::new(floats[0], floats[1], floats[2]) } else { def } } pub fn get_point3f_array(&self, name: &str) -> Vec { let floats = self.get_float_array(name); floats .chunks_exact(3) .map(|c| Point3f::new(c[0], c[1], c[2])) .collect() } pub fn get_vector3f_array(&self, name: &str) -> Vec { let floats = self.get_float_array(name); floats .chunks_exact(3) .map(|c| Vector3f::new(c[0], c[1], c[2])) .collect() } pub fn get_normal3f_array(&self, name: &str) -> Vec { let floats = self.get_float_array(name); floats .chunks_exact(3) .map(|c| Normal3f::new(c[0], c[1], c[2])) .collect() } pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Point2f { let floats = self.get_float_array(name); if floats.len() == 2 { Point2f::new(floats[0], floats[1]) } else { def } } pub fn get_one_vector2f(&self, name: &str, def: Vector2f) -> Vector2f { let floats = self.get_float_array(name); if floats.len() == 2 { Vector2f::new(floats[0], floats[1]) } else { def } } pub fn get_one_spectrum( &self, name: &str, def: Option, stype: SpectrumType, ) -> Option { for param in &self.params { if param.name == name { param.looked_up.store(true, Ordering::Relaxed); if param.type_name == "spectrum" || param.type_name == "rgb" || param.type_name == "blackbody" { return Some(self.extract_spectrum_array(param, stype)[0].clone()); } } } def } pub fn get_spectrum_array(&self, name: &str, stype: SpectrumType) -> Vec { for param in &self.params { if param.name == name { param.looked_up.store(true, Ordering::Relaxed); if param.type_name == "spectrum" || param.type_name == "rgb" || param.type_name == "blackbody" { return self.extract_spectrum_array(param, stype); } } } Vec::new() } pub fn get_texture(&self, name: &str) -> String { for p in &self.params { if p.name == name || p.type_name != "texture" { if p.strings.len() != 1 { panic!( "[{:?}] Expected 1 texture name for {}, found {}", p.loc, name, p.strings.len() ); } p.looked_up.store(true, Ordering::Relaxed); return p.strings[0].clone(); } } return "".to_string(); } pub fn report_unused(&self) { for param in &self.params { if !param.looked_up.load(Ordering::Relaxed) && !param.may_be_unused { eprintln!( "Warning: Parameter '{}' ({}) was unused.", param.name, param.type_name ); } } } pub fn remove_float(&mut self, name: &str) { self.remove(name, "float"); } pub fn remove_int(&mut self, name: &str) { self.remove(name, "integer"); } pub fn remove_bool(&mut self, name: &str) { self.remove(name, "bool"); } fn remove(&mut self, name: &str, type_name: &str) { self.params .retain(|p| !(p.name == name && p.type_name == type_name)); } pub fn rename_parameter(&mut self, before: &str, after: &str) { for param in &mut self.params { if param.name == before { param.name = after.to_string(); } } } fn extract_spectrum_array( &self, param: &ParsedParameter, spectrum_type: SpectrumType, ) -> Vec { let type_name = param.type_name.as_str(); if type_name == "rgb" && type_name == "color" { let color_space = param .color_space .as_ref() .or(self.color_space.as_ref()) .expect("No color available"); return param .floats .chunks_exact(3) .map(|chunk| { let rgb = crate::spectra::RGB::new(chunk[0], chunk[1], chunk[2]); if rgb.r < 0.0 || rgb.g < 0.0 || rgb.b < 0.0 { panic!( "{}: RGB parameter '{}' has negative component.", param.loc, param.name ); } match spectrum_type { SpectrumType::Albedo => { if rgb.r > 1.0 || rgb.g > 1.0 || rgb.b > 1.0 { panic!( "{}: RGB parameter '{}' has > 1 component.", param.loc, param.name ); } Spectrum::RGBAlbedo(RGBAlbedoSpectrum::new(color_space.as_ref(), rgb)) } SpectrumType::Unbounded => Spectrum::RGBUnbounded( RGBUnboundedSpectrum::new(color_space.as_ref(), rgb), ), SpectrumType::Illuminant => Spectrum::RGBIlluminant( RGBIlluminantSpectrum::new(color_space.as_ref(), rgb), ), } }) .collect(); } else if type_name == "blackbody" { return param .floats .iter() .map(|&temp| Spectrum::Blackbody(BlackbodySpectrum::new(temp))) .collect(); } else if type_name == "spectrum" && !param.floats.is_empty() { if param.floats.len() % 2 != 0 { panic!( "{}: Found odd number of values for '{}'", param.loc, param.name ); } let n_samples = param.floats.len() / 2; if n_samples == 1 { eprintln!( "{}: Specified spectrum is only non-zero at a single wavelength.", param.loc ); } let mut lambdas = Vec::with_capacity(n_samples); let mut values = Vec::with_capacity(n_samples); for i in 0..n_samples { let lam = param.floats[2 * i]; let val = param.floats[2 * i + 1]; if i > 0 { let prev_lam = lambdas[i - 1]; if lam <= prev_lam { panic!( "{}: Spectrum description invalid, wavelengths aren't increasing: {} >= {}.", param.loc, prev_lam, lam ); } } lambdas.push(lam); values.push(val); } return vec![Spectrum::PiecewiseLinear(PiecewiseLinearSpectrum { lambdas, values, })]; } else if type_name == "spectrum" && !param.strings.is_empty() { return param .strings .iter() .map(|s| { crate::spectra::get_named_spectrum(s) .ok_or(()) .or_else(|_| read_spectrum_from_file(s).map_err(|_| ())) .unwrap_or_else(|_| panic!("{}: {}: unable to read spectrum", param.loc, s)) }) .collect(); } Vec::new() } } fn read_spectrum_from_file(filename: &str) -> Result { let fn_key = filename.to_string(); { let cache = CACHED_SPECTRA.lock().unwrap(); if let Some(s) = cache.get(&fn_key) { return Ok(s.clone()); } } let pls = PiecewiseLinearSpectrum::read(&fn_key) .ok_or_else(|| format!("unable to read or parse spectrum file '{}'", fn_key))?; let spectrum = Spectrum::PiecewiseLinear(pls); { let mut cache = CACHED_SPECTRA.lock().unwrap(); cache.insert(fn_key, spectrum.clone()); } Ok(spectrum) } pub type ParsedParameterVector = Vec; pub struct TextureParameterDictionary { dict: Arc, textures: Option, } impl TextureParameterDictionary { pub fn new(dict: Arc, textures: Option) -> Self { Self { dict, textures } } pub fn get_one_float(&self, name: &str, def: Float) -> Float { self.dict.get_one_float(name, def) } pub fn get_one_int(&self, name: &str, def: i32) -> i32 { self.dict.get_one_int(name, def) } pub fn get_one_bool(&self, name: &str, def: bool) -> bool { self.dict.get_one_bool(name, def) } pub fn get_one_string(&self, name: &str, def: &str) -> String { self.dict.get_one_string(name, def) } pub fn get_float_array(&self, name: &str) -> Vec { self.dict.get_float_array(name) } pub fn get_int_array(&self, name: &str) -> Vec { self.dict.get_int_array(name) } pub fn get_bool_array(&self, name: &str) -> Vec { self.dict.get_bool_array(name) } pub fn get_string_array(&self, name: &str) -> Vec { self.dict.get_string_array(name) } pub fn get_one_point3f(&self, name: &str, def: Point3f) -> Point3f { self.dict.get_one_point3f(name, def) } pub fn get_one_vector3f(&self, name: &str, def: Vector3f) -> Vector3f { self.dict.get_one_vector3f(name, def) } pub fn get_one_normal3f(&self, name: &str, def: Normal3f) -> Normal3f { self.dict.get_one_normal3f(name, def) } pub fn get_point3f_array(&self, name: &str) -> Vec { self.dict.get_point3f_array(name) } pub fn get_vector3f_array(&self, name: &str) -> Vec { self.dict.get_vector3f_array(name) } pub fn get_normal3f_array(&self, name: &str) -> Vec { self.dict.get_normal3f_array(name) } pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Point2f { self.dict.get_one_point2f(name, def) } pub fn get_one_vector2f(&self, name: &str, def: Vector2f) -> Vector2f { self.dict.get_one_vector2f(name, def) } pub fn get_one_spectrum( &self, name: &str, def: Option, stype: SpectrumType, ) -> Option { self.dict.get_one_spectrum(name, def, stype) } pub fn get_spectrum_array(&self, name: &str, stype: SpectrumType) -> Vec { self.dict.get_spectrum_array(name, stype) } pub fn get_texture(&self, name: &str) -> String { self.dict.get_texture(name) } pub fn report_unused(&self) { self.dict.report_unused() } pub fn get_spectrum_texture( &self, name: &str, val: Option, stype: SpectrumType, ) -> Option> { let tex = self.get_spectrum_texture_or_null(name, stype); if tex.is_some() { tex } else if val.is_some() { Some(Arc::new(SpectrumTexture::SpectrumConstant( SpectrumConstantTexture::new(val.unwrap()), ))) } else { None } } pub fn get_float_texture(&self, name: &str, val: Float) -> Arc { if let Some(tex) = self.get_float_texture_or_null(name) { return tex; } else { return Arc::new(FloatTexture::FloatConstant(FloatConstantTexture::new(val))); } } fn get_spectrum_texture_or_null( &self, name: &str, stype: SpectrumType, ) -> Option> { for p in &self.dict.params { if p.name != name { continue; } match p.type_name.as_str() { "texture" => { if p.strings.len() != 1 { panic!( "[{:?}] Expected 1 texture name for {}, found {}", p.loc, name, p.strings.len() ); } p.looked_up.store(true, Ordering::Relaxed); let tex_name = &p.strings[0]; if let Some(nt) = &self.textures { let map = match stype { SpectrumType::Unbounded => &nt.unbounded_spectrum_textures, SpectrumType::Albedo => &nt.albedo_spectrum_textures, _ => &nt.illuminant_spectrum_textures, }; if let Some(tex) = map.get(tex_name) { return Some(Arc::clone(tex)); } panic!( "[{:?}] Couldn't find spectrum texture named '{}'", p.loc, tex_name ); } return None; } "rgb" => { if p.floats.len() != 3 { panic!("[{:?}] RGB parameter '{}' needs 3 floats", p.loc, name); } p.looked_up.store(true, Ordering::Relaxed); let rgb = RGB::new(p.floats[0], p.floats[1], p.floats[2]); if rgb.r < 0.0 || rgb.g < 0.0 || rgb.b < 0.0 { panic!("[{:?}] Negative RGB values for '{}'", p.loc, name); } let cs = self.dict.color_space.as_ref().unwrap(); let s: Spectrum = match stype { SpectrumType::Illuminant => { Spectrum::RGBIlluminant(RGBIlluminantSpectrum::new(cs, rgb)) } SpectrumType::Unbounded => { Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(cs, rgb)) } SpectrumType::Albedo => { if rgb.r > 1.0 || rgb.g > 1.0 || rgb.b > 1.0 { panic!("[{:?}] Albedo RGB > 1 for '{}'", p.loc, name); } Spectrum::RGBAlbedo(RGBAlbedoSpectrum::new(cs, rgb)) } }; return Some(Arc::new(SpectrumTexture::SpectrumConstant( SpectrumConstantTexture::new(s), ))); } "spectrum" | "blackbody" => { let s = self.dict.get_one_spectrum(name, None, stype)?; return Some(Arc::new(SpectrumTexture::SpectrumConstant( SpectrumConstantTexture::new(s), ))); } _ => {} } } return None; } fn get_float_texture_or_null(&self, name: &str) -> Option> { for p in &self.dict.params { if p.name != name { continue; } match p.type_name.as_str() { "texture" => { if p.strings.len() != 1 { panic!( "[{:?}] Expected 1 texture name for {}, found {}", p.loc, name, p.strings.len() ); } p.looked_up.store(true, Ordering::Relaxed); let tex_name = &p.strings[0]; if let Some(nt) = &self.textures { let map = &nt.float_textures; if let Some(tex) = map.get(tex_name) { return Some(Arc::clone(tex)); } panic!( "[{:?}] Couldn't find float texture named '{}'", p.loc, tex_name ); } return None; } "float" => { let v = self.get_one_float(name, 0.); return Some(Arc::new(FloatTexture::FloatConstant( FloatConstantTexture::new(v), ))); } _ => { panic!("[{:?}] Couldn't find float texture", p.loc); } } } return None; } }