use crate::core::texture::{get_texture_cache, CreateTextureMapping, TexInfo}; use crate::core::texture::{ CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, }; use crate::utils::mipmap::{MIPMap, MIPMapFilterOptions}; use crate::utils::{resolve_filename, FileLoc, TextureParameterDictionary}; use crate::Arena; use anyhow::Result; use shared::core::color::RGB; use shared::core::color::{ColorEncoding, SRGBEncoding}; use shared::core::geometry::Vector2f; use shared::core::image::{FilterFunction, WrapMode}; use shared::core::spectrum::SpectrumTrait; use shared::core::texture::{SpectrumType, TexCoord2D, TextureEvalContext, TextureMapping2D}; use shared::spectra::{ RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, }; use shared::utils::Transform; use shared::Float; use std::path::Path; use std::sync::Arc; #[derive(Clone, Debug)] pub struct ImageTextureBase { pub mapping: TextureMapping2D, pub filename: String, pub scale: Float, pub invert: bool, pub mipmap: Arc, } impl ImageTextureBase { pub fn new( mapping: TextureMapping2D, filename: String, filter_options: MIPMapFilterOptions, wrap_mode: WrapMode, scale: Float, invert: bool, encoding: ColorEncoding, ) -> Self { let tex_info = TexInfo { filename: filename.clone(), filter_options, wrap_mode, encoding, }; let cache_mutex = get_texture_cache(); { let cache = cache_mutex.lock().unwrap(); if let Some(mipmap) = cache.get(&tex_info) { return Self { mapping, filename, scale, invert, mipmap: mipmap.clone(), }; } } let path = Path::new(&filename); let mipmap_raw = MIPMap::create_from_file(path, filter_options, wrap_mode, encoding) .expect("Failed to create MIPMap from file"); let mipmap_arc = Arc::new(mipmap_raw); { let mut cache = cache_mutex.lock().unwrap(); let stored_mipmap = cache.entry(tex_info).or_insert(mipmap_arc); Self { mapping, filename, scale, invert, mipmap: stored_mipmap.clone(), } } } pub fn clear_cache() { let mut cache = get_texture_cache().lock().unwrap(); cache.clear(); } pub fn multiply_scale(&mut self, s: Float) { self.scale *= s; } } #[derive(Clone, Debug)] pub struct SpectrumImageTexture { pub base: ImageTextureBase, pub spectrum_type: SpectrumType, } impl SpectrumImageTexture { #[allow(clippy::too_many_arguments)] pub fn new( mapping: TextureMapping2D, filename: String, filter_options: MIPMapFilterOptions, wrap_mode: WrapMode, scale: Float, invert: bool, encoding: ColorEncoding, spectrum_type: SpectrumType, ) -> Self { let base = ImageTextureBase::new( mapping, filename, filter_options, wrap_mode, scale, invert, encoding, ); Self { base, spectrum_type, } } } impl SpectrumTextureTrait for SpectrumImageTexture { fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum { let mut c = self.base.mapping.map(ctx); c.st[1] = 1. - c.st[1]; let dst0 = Vector2f::new(c.dsdx, c.dtdx); let dst1 = Vector2f::new(c.dsdy, c.dtdy); let rgb_unclamp = self.base.scale * self.base.mipmap.filter::(c.st, dst0, dst1); let rgb = RGB::clamp_zero(&rgb_unclamp); if let Some(cs) = self.base.mipmap.get_rgb_colorspace() { match self.spectrum_type { SpectrumType::Unbounded => { return RGBUnboundedSpectrum::new(&cs, rgb).sample(lambda); } SpectrumType::Albedo => { return RGBAlbedoSpectrum::new(&cs, rgb.clamp(0., 1.)).sample(lambda); } _ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda), } } assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]); SampledSpectrum::new(rgb[0]) } } impl CreateSpectrumTexture for SpectrumImageTexture { fn create( render_from_texture: Transform, parameters: TextureParameterDictionary, spectrum_type: SpectrumType, loc: FileLoc, ) -> Result { let mapping = TextureMapping2D::create(¶meters, &render_from_texture, &loc)?; let filename = crate::utils::resolve_filename(¶meters.get_one_string("filename", "")?); let scale = parameters.get_one_float("scale", 1.0)?; let invert = parameters.get_one_bool("invert", false)?; let filter_options = MIPMapFilterOptions::default(); let wrap_str = parameters.get_one_string("wrap", "repeat")?; // let wrap_mode = WrapMode::parse(wrap_str)?; let wrap_mode = match wrap_str.as_str() { "repeat" => WrapMode::Repeat, "clamp" => WrapMode::Clamp, "black" => WrapMode::Black, _ => WrapMode::Repeat, }; let encoding = ColorEncoding::SRGB(SRGBEncoding); let tex = SpectrumImageTexture::new( mapping, filename, filter_options, wrap_mode, scale, invert, encoding, spectrum_type, ); Ok(SpectrumTexture::Image(tex)) } } #[derive(Debug, Clone)] pub struct FloatImageTexture { pub base: ImageTextureBase, } impl FloatImageTexture { pub fn new( mapping: TextureMapping2D, filename: String, filter_options: MIPMapFilterOptions, wrap_mode: WrapMode, scale: Float, invert: bool, encoding: ColorEncoding, ) -> Self { Self { base: ImageTextureBase::new( mapping, filename, filter_options, wrap_mode, scale, invert, encoding, ), } } } impl FloatTextureTrait for FloatImageTexture { fn evaluate(&self, ctx: &TextureEvalContext) -> Float { let mut c: TexCoord2D = self.base.mapping.map(ctx); c.st[1] = 1. - c.st[1]; let v: Float = self.base.scale * self.base.mipmap.filter::( c.st, Vector2f::new(c.dsdx, c.dtdx), Vector2f::new(c.dsdy, c.dtdy), ); if self.base.invert { (1. - v).max(0.) } else { v } } } impl CreateFloatTexture for FloatImageTexture { fn create( render_from_texture: Transform, parameters: TextureParameterDictionary, loc: FileLoc, _arena: &Arena, ) -> Result { let mapping = TextureMapping2D::create(¶meters, &render_from_texture, &loc)?; let max_aniso = parameters.get_one_float("maxanisotropy", 8.)?; let filter = parameters.get_one_string("filter", "bilinear")?; let mut filter_options = MIPMapFilterOptions::default(); filter_options.max_anisotropy = max_aniso; let ff = FilterFunction::parse(&filter)?; filter_options.filter = ff; let wrap_string = parameters.get_one_string("wrap", "repeat")?; let wrap_mode = WrapMode::parse(&wrap_string)?; let scale = parameters.get_one_float("scale", 1.)?; let invert = parameters.get_one_bool("invert", false)?; let filename = resolve_filename(¶meters.get_one_string("filename", "")?); let default_encoding = if Path::new(&filename) .extension() .map_or(false, |ext| ext == "png") { "sRGB" } else { "linear" }; let encoding_str = parameters.get_one_string("encoding", default_encoding)?; let encoding = ColorEncoding::from_name(&encoding_str)?; let tex = FloatImageTexture::new( mapping, filename, filter_options, wrap_mode, scale, invert, encoding, ); Ok(FloatTexture::Image(tex)) } }