use crate::core::spectrum::SPECTRUM_FILE_CACHE; use crate::spectra::piecewise::ReadFromFile; use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::spectra::data::get_named_spectrum; use crate::utils::FileLoc; use anyhow::{bail, Result}; use shared::core::color::RGB; use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f}; use shared::core::spectrum::Spectrum; use shared::core::texture::{FloatConstantTexture, SpectrumConstantTexture, SpectrumType}; use shared::spectra::{ PiecewiseLinearSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, }; use shared::Float; use std::collections::HashMap; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; pub fn error_exit(loc: Option<&FileLoc>, message: &str) -> String { if let Some(l) = loc { format!("{}: {}", l, message) } else { message.to_string() } } #[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) } } pub type ParsedParameterVector = Vec; pub trait PBRTParameter: Sized { type Raw: Clone; const TYPE_NAME: &'static str; const N_PER_ITEM: usize; fn convert(v: &[Self::Raw]) -> Self; fn get_values(param: &ParsedParameter) -> &[Self::Raw]; } impl PBRTParameter for bool { type Raw = bool; const TYPE_NAME: &'static str = "bool"; const N_PER_ITEM: usize = 1; fn convert(v: &[Self::Raw]) -> Self { v[0] } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.bools } } impl PBRTParameter for Float { type Raw = Float; const TYPE_NAME: &'static str = "float"; const N_PER_ITEM: usize = 1; fn convert(v: &[Self::Raw]) -> Self { v[0] } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.floats } } impl PBRTParameter for i32 { type Raw = i32; const TYPE_NAME: &'static str = "integer"; const N_PER_ITEM: usize = 1; fn convert(v: &[Self::Raw]) -> Self { v[0] } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.ints } } impl PBRTParameter for Point2f { type Raw = Float; const TYPE_NAME: &'static str = "point2"; const N_PER_ITEM: usize = 2; fn convert(v: &[Self::Raw]) -> Self { Point2f::new(v[0], v[1]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.floats } } impl PBRTParameter for Point3f { type Raw = Float; const TYPE_NAME: &'static str = "point3"; const N_PER_ITEM: usize = 3; fn convert(v: &[Self::Raw]) -> Self { Point3f::new(v[0], v[1], v[2]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.floats } } impl PBRTParameter for Vector2f { type Raw = Float; const TYPE_NAME: &'static str = "vector2"; const N_PER_ITEM: usize = 2; fn convert(v: &[Self::Raw]) -> Self { Vector2f::new(v[0], v[1]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.floats } } impl PBRTParameter for Vector3f { type Raw = Float; const TYPE_NAME: &'static str = "vector3"; const N_PER_ITEM: usize = 3; fn convert(v: &[Self::Raw]) -> Self { Vector3f::new(v[0], v[1], v[2]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.floats } } impl PBRTParameter for Normal3f { type Raw = Float; const TYPE_NAME: &'static str = "normal"; const N_PER_ITEM: usize = 3; fn convert(v: &[Self::Raw]) -> Self { Normal3f::new(v[0], v[1], v[2]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.floats } } impl PBRTParameter for String { type Raw = String; const TYPE_NAME: &'static str = "string"; const N_PER_ITEM: usize = 1; fn convert(v: &[Self::Raw]) -> Self { v[0].clone() } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { ¶m.strings } } #[derive(Default, Clone)] pub struct NamedTextures { pub float_textures: Arc>>, pub albedo_spectrum_textures: Arc>>, pub unbounded_spectrum_textures: Arc>>, pub illuminant_spectrum_textures: Arc>>, } #[derive(Debug, Default, Clone)] pub struct ParameterDictionary { pub params: ParsedParameterVector, pub color_space: Option>, pub n_owned_params: usize, } impl ParameterDictionary { pub fn new( mut params: ParsedParameterVector, color_space: Option>, ) -> Result { let n_owned_params = params.len(); params.reverse(); let dict = Self { params, color_space, n_owned_params, }; dict.check_parameter_types()?; Ok(dict) } pub fn from_array( mut p0: ParsedParameterVector, params: &[ParsedParameter], color_space: Option>, ) -> Result { let n_owned_params = params.len(); p0.extend(params.iter().rev().cloned()); let dict = Self { params: p0, color_space, n_owned_params, }; dict.check_parameter_types()?; Ok(dict) } fn check_parameter_types(&self) -> Result<()> { for p in &self.params { match p.type_name.as_str() { bool::TYPE_NAME => { if p.bools.is_empty() { bail!( "{}: non-Boolean values provided for Boolean-valued parameter \"{}\"", &p.loc, p.name ); } } Float::TYPE_NAME | i32::TYPE_NAME | Point2f::TYPE_NAME | Vector2f::TYPE_NAME | Point3f::TYPE_NAME | Vector3f::TYPE_NAME | Normal3f::TYPE_NAME | "rgb" | "blackbody" => { if p.ints.is_empty() && p.floats.is_empty() { bail!( "{}: non-numeric values provided for numeric-valued parameter \"{}\"", &p.loc, p.name ); } } String::TYPE_NAME | "texture" => { if p.strings.is_empty() { bail!( "{}: non-string values provided for string-valued parameter \"{}\"", &p.loc, p.name ); } } "spectrum" => { if p.strings.is_empty() && p.ints.is_empty() && p.floats.is_empty() { bail!( "{}: expecting string or numeric-valued parameter for spectrum parameter \"{}\"", &p.loc, p.name ); } } unknown => { bail!( "{}: unknown parameter type \"{}\" '{}'", &p.loc, p.name, unknown, ); } } } Ok(()) } fn lookup_single(&self, name: &str, default_val: T) -> Result where T: PBRTParameter, { for param in &self.params { if param.name == name && param.type_name == T::TYPE_NAME { let values = T::get_values(param); if values.is_empty() { bail!( "{}: No values provided for parameter \"{}\".", ¶m.loc, name ); } if values.len() != T::N_PER_ITEM { bail!( "{}: Expected {} values for parameter \"{}\". Found {}.", ¶m.loc, T::N_PER_ITEM, name, values.len() ); } param.looked_up.store(true, Ordering::Relaxed); return Ok(T::convert(values)); } } Ok(default_val) } pub fn lookup_array(&self, name: &str) -> Result> where T: PBRTParameter, { for param in &self.params { if param.name == name && param.type_name == T::TYPE_NAME { let values = T::get_values(param); if values.len() % T::N_PER_ITEM != 0 { bail!( "{}: Number of values for \"{}\" is not a multiple of {}", ¶m.loc, name, T::N_PER_ITEM ); } param.looked_up.store(true, Ordering::Relaxed); return Ok(values .chunks(T::N_PER_ITEM) .map(|chunk| T::convert(chunk)) .collect()); } } Ok(Vec::new()) } pub fn get_one_float(&self, name: &str, def: Float) -> Result { self.lookup_single(name, def) } pub fn get_one_int(&self, name: &str, def: i32) -> Result { self.lookup_single(name, def) } pub fn get_one_bool(&self, name: &str, def: bool) -> Result { self.lookup_single(name, def) } pub fn get_one_string(&self, name: &str, def: &str) -> Result { self.lookup_single(name, def.to_string()) } pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Result { self.lookup_single(name, def) } pub fn get_one_point3f(&self, name: &str, def: Point3f) -> Result { self.lookup_single(name, def) } pub fn get_one_vector2f(&self, name: &str, def: Vector2f) -> Result { self.lookup_single(name, def) } pub fn get_one_vector3f(&self, name: &str, def: Vector3f) -> Result { self.lookup_single(name, def) } pub fn get_one_normal3f(&self, name: &str, def: Normal3f) -> Result { self.lookup_single(name, def) } pub fn get_float_array(&self, name: &str) -> Result> { self.lookup_array(name) } pub fn get_int_array(&self, name: &str) -> Result> { self.lookup_array(name) } pub fn get_bool_array(&self, name: &str) -> Result> { self.lookup_array(name) } pub fn get_string_array(&self, name: &str) -> Result> { self.lookup_array(name) } pub fn get_point2f_array(&self, name: &str) -> Result> { self.lookup_array(name) } pub fn get_point3f_array(&self, name: &str) -> Result> { self.lookup_array(name) } pub fn get_vector3f_array(&self, name: &str) -> Result> { self.lookup_array(name) } pub fn get_normal3f_array(&self, name: &str) -> Result> { self.lookup_array(name) } 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(); } } String::new() } 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 { match param.type_name.as_str() { "rgb" | "color" => self.extract_rgb_spectrum(param, spectrum_type), "blackbody" => self.extract_file_spectrum(param), "spectrum" => self.extract_sampled_spectrum(param), _ => Vec::new(), } } fn extract_rgb_spectrum( &self, param: &ParsedParameter, spectrum_type: SpectrumType, ) -> Vec { let color_space = param .color_space .as_ref() .or(self.color_space.as_ref()) .expect("No color available"); param .floats .chunks_exact(3) .map(|chunk| { let rgb = 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() } fn extract_sampled_spectrum(&self, param: &ParsedParameter) -> Vec { 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 (lambdas, values): (Vec, Vec) = param .floats .chunks(2) .enumerate() .map(|(i, chunk)| { let (lam, val) = (chunk[0], chunk[1]); if i > 0 && lam <= param.floats[(i - 1) * 2] { panic!( "{}: Spectrum invalid, wavelengths not increasing: {} >= {}.", param.loc, param.floats[(i - 1) * 2], lam ); } (lam as Float, val as Float) }) .unzip(); vec![Spectrum::Piecewise(PiecewiseLinearSpectrum { lambdas: lambdas.as_ptr().into(), values: values.as_ptr().into(), count: lambdas.len() as u32, })] } fn extract_file_spectrum(&self, param: &ParsedParameter) -> Vec { param .strings .iter() .map(|s| { 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() } } fn read_spectrum_from_file(filename: &str) -> Result { let fn_key = filename.to_string(); { let cache = SPECTRUM_FILE_CACHE.lock(); 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::Piecewise(*pls); { let mut cache = SPECTRUM_FILE_CACHE.lock(); cache.insert(fn_key, spectrum.clone()); } Ok(spectrum) } pub struct TextureParameterDictionary { dict: Arc, textures: Option, } impl TextureParameterDictionary { pub fn new(dict: Arc, textures: Option<&NamedTextures>) -> Self { Self { dict, textures: textures.cloned(), } } pub fn get_one_float(&self, name: &str, def: Float) -> Result { self.dict.get_one_float(name, def) } pub fn get_one_int(&self, name: &str, def: i32) -> Result { self.dict.get_one_int(name, def) } pub fn get_one_bool(&self, name: &str, def: bool) -> Result { self.dict.get_one_bool(name, def) } pub fn get_one_string(&self, name: &str, def: &str) -> Result { self.dict.get_one_string(name, def) } pub fn get_float_array(&self, name: &str) -> Result> { self.dict.get_float_array(name) } pub fn get_int_array(&self, name: &str) -> Result> { self.dict.get_int_array(name) } pub fn get_bool_array(&self, name: &str) -> Result> { self.dict.get_bool_array(name) } pub fn get_string_array(&self, name: &str) -> Result> { self.dict.get_string_array(name) } pub fn get_one_point3f(&self, name: &str, def: Point3f) -> Result { self.dict.get_one_point3f(name, def) } pub fn get_one_vector3f(&self, name: &str, def: Vector3f) -> Result { self.dict.get_one_vector3f(name, def) } pub fn get_one_normal3f(&self, name: &str, def: Normal3f) -> Result { self.dict.get_one_normal3f(name, def) } pub fn get_point3f_array(&self, name: &str) -> Result> { self.dict.get_point3f_array(name) } pub fn get_vector3f_array(&self, name: &str) -> Result> { self.dict.get_vector3f_array(name) } pub fn get_normal3f_array(&self, name: &str) -> Result> { self.dict.get_normal3f_array(name) } pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Result { self.dict.get_one_point2f(name, def) } pub fn get_one_vector2f(&self, name: &str, def: Vector2f) -> Result { 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::Constant( SpectrumConstantTexture::new(val.unwrap()), ))) } else { None } } pub fn get_float_texture(&self, name: &str, val: Float) -> Result> { if let Some(tex) = self.get_float_texture_or_null(name)? { return Ok(tex); } else { return Ok(Arc::new(FloatTexture::Constant(FloatConstantTexture::new( val, )))); } } pub fn get_float_texture_with_fallback( &self, primary: &str, fallback: &str, default: Float, ) -> Result> { match self.get_float_texture_or_null(primary)? { Some(tex) => Ok(tex), None => self.get_float_texture(fallback, default), } } pub 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::Constant( SpectrumConstantTexture::new(s), ))); } "spectrum" | "blackbody" => { let s = self.dict.get_one_spectrum(name, None, stype)?; return Some(Arc::new(SpectrumTexture::Constant( SpectrumConstantTexture::new(s), ))); } _ => {} } } return None; } pub fn get_float_texture_or_null(&self, name: &str) -> Result>> { 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 Ok(Some(Arc::clone(tex))); } panic!( "[{:?}] Couldn't find float texture named '{}'", p.loc, tex_name ); } return Ok(None); } "float" => { let v = self.get_one_float(name, 0.)?; return Ok(Some(Arc::new(FloatTexture::Constant( FloatConstantTexture::new(v), )))); } _ => { panic!("[{:?}] Couldn't find float texture", p.loc); } } } Ok(None) } }