use std::path::Path; use crate::core::image::{Image, ImageIO}; use crate::core::light::{CreateLight, lookup_spectrum}; use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename}; use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::{Medium, MediumInterface}; use shared::core::shape::Shape; use shared::core::spectrum::Spectrum; use shared::core::texture::SpectrumType; use shared::lights::GoniometricLight; use shared::spectra::RGBColorSpace; use shared::utils::{Ptr, Transform}; use shared::{Float, PI}; pub trait CreateGoniometricLight { fn new( render_from_light: Transform, medium_interface: MediumInterface, le: Spectrum, scale: Float, image: Ptr, ) -> Self; } impl CreateGoniometricLight for GoniometricLight { fn new( render_from_light: Transform, medium_interface: MediumInterface, le: Spectrum, scale: Float, image: Ptr, ) -> Self { let base = LightBase::new( LightType::DeltaPosition, render_from_light, medium_interface, ); let iemit = lookup_spectrum(&le); let distrib = PiecewiseConstant2D::from_image(&image); Self { base, iemit: Ptr::from(&iemit.device()), scale, image: Ptr::from(image.device()), distrib: Ptr::from(&distrib.device), } } } impl CreateLight for GoniometricLight { fn create( render_from_light: Transform, medium: Medium, params: &ParameterDictionary, loc: &FileLoc, _shape: &Shape, _alpha_text: &FloatTexture, colorspace: Option<&RGBColorSpace>, _arena: &Arena, ) -> Result { let i = params .get_one_spectrum( "I", Some(Spectrum::Dense(colorspace.unwrap().illuminant)), SpectrumType::Illuminant, ) .expect("Could not retrieve spectrum"); let mut scale = params.get_one_float("scale", 1.)?; let filename = resolve_filename(¶ms.get_one_string("filename", "")?); let image: Ptr = if filename.is_empty() { Ptr::null() } else { let im = Image::read(Path::new(&filename), None) .map_err(|e| anyhow!("could not load image '{}': {}", filename, e))?; let loaded = im.image; let res = loaded.resolution(); if loaded.has_any_infinite_pixels() { return Err(anyhow!( "image '{}' has infinite pixels, not suitable for light", filename )); } if res.x() != res.y() { return Err(anyhow!( "image resolution ({}, {}) is non-square; unlikely to be an equal-area map", res.x(), res.y() )); } Ptr::from(&convert_to_luminance_image(&loaded, &filename, loc)?) }; scale /= spectrum_to_photometric(i); let phi_v = params.get_one_float("power", -1.0)?; if phi_v > 0.0 { let k_e = compute_emissive_power(&image); scale *= phi_v / k_e; } let swap_yz: [Float; 16] = [ 1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1., ]; let t = Transform::from_flat(&swap_yz) .expect("Could not create transform for GoniometricLight"); let final_render_from_light = render_from_light * t; let specific = GoniometricLight::new(final_render_from_light, medium.into(), i, scale, image); Ok(Light::Goniometric(specific)) } } fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result { let res = image.resolution(); let rgb_desc = image.get_channel_desc(&["R", "G", "B"]); let y_desc = image.get_channel_desc(&["Y"]); match (rgb_desc, y_desc) { (Ok(_), Ok(_)) => Err(anyhow!( "{}: Image '{}' has both RGB and Y channels; ambiguous", loc, filename )), (Ok(_), Err(_)) => { // Convert RGB to Y (luminance) let mut y_pixels = Vec::with_capacity((res.x() * res.y()) as usize); for y in 0..res.y() { for x in 0..res.x() { let r = image.get_channel(Point2i::new(x, y), 0); let g = image.get_channel(Point2i::new(x, y), 1); let b = image.get_channel(Point2i::new(x, y), 2); y_pixels.push((r + g + b) / 3.0); } } Ok(Image::from_f32(y_pixels, res, &["Y"].to_vec())) } (Err(_), Ok(_)) => { // Already has Y channel Ok(image.clone()) } (Err(_), Err(_)) => Err(anyhow!( "{}: Image '{}' has neither RGB nor Y channels", loc, filename )), } } fn compute_emissive_power(image: &Image) -> Float { let res = image.resolution(); let mut sum_y = 0.0; for y in 0..res.y() { for x in 0..res.x() { sum_y += image.get_channel(Point2i::new(x, y), 0); } } 4.0 * PI * sum_y / (res.x() * res.y()) as Float }