use std::path::Path; use crate::core::image::{Image, ImageIO}; use crate::core::light::lookup_spectrum; use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, 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, ShapeTrait}; use shared::core::spectrum::Spectrum; use shared::core::texture::GPUFloatTexture; use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::lights::DiffuseAreaLight; use shared::spectra::RGBColorSpace; use shared::utils::{Ptr, Transform}; use shared::{Float, PI}; pub fn create( render_from_light: Transform, medium: Option, params: &ParameterDictionary, loc: &FileLoc, shape: &Shape, alpha: &FloatTexture, colorspace: Option<&RGBColorSpace>, arena: &Arena, ) -> Result { let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant); let default_cs = crate::spectra::default_colorspace(); let cs = colorspace.unwrap_or(&default_cs); let illum_spec = Spectrum::Dense(cs.illuminant); let mut scale = params.get_one_float("scale", 1.)?; let two_sided = params.get_one_bool("twosided", false)?; let filename = resolve_filename(¶ms.get_one_string("filename", "")?); let (image, image_color_space) = if !filename.is_empty() { if l.is_some() { return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc)); } let im = Image::read(Path::new(&filename), None)?; if im.image.has_any_infinite_pixels() { return Err(anyhow!("{}: image has infinite pixel values", loc)); } if im.image.has_any_nan_pixels() { return Err(anyhow!("{}: image has NaN pixel values", loc)); } let channel_desc = im .image .get_channel_desc(&["R", "G", "B"]) .map_err(|_| anyhow!("{}: image must have R, G, B channels", loc))?; let image = im.image.select_channels(&channel_desc); let cs = im.metadata.get_colorspace(); (Some(image), cs) } else { if l.is_none() { l = Some(illum_spec); } (None, None) }; let l_for_scale = l.as_ref().unwrap_or(&illum_spec); scale /= spectrum_to_photometric(*l_for_scale); let phi_v = params.get_one_float("power", -1.0)?; if phi_v > 0.0 { // k_e is the emissive power of the light as defined by the spectral // distribution and texture and is used to normalize the emitted // radiance such that the user-defined power will be the actual power // emitted by the light. let mut k_e: Float = 1.0; if let Some(ref img) = image { // Get the appropriate luminance vector from the image colour space let lum_vec = image_color_space.unwrap().luminance_vector(); let mut sum_k_e = 0.0; let res = img.resolution(); for y in 0..res.y() { for x in 0..res.x() { let r = img.get_channel(Point2i::new(x, y), 0); let g = img.get_channel(Point2i::new(x, y), 1); let b = img.get_channel(Point2i::new(x, y), 2); sum_k_e += r * lum_vec[0] + g * lum_vec[1] + b * lum_vec[2]; } } k_e = sum_k_e / (res.x() * res.y()) as Float; } let side_factor = if two_sided { 2.0 } else { 1.0 }; k_e *= side_factor * shape.area() * PI; // now multiply up scale to hit the target power scale *= phi_v / k_e; } let alpha_ptr = alpha.upload(arena); let is_constant_zero = match &*alpha_ptr { GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0, _ => false, }; let (light_type, stored_alpha) = if is_constant_zero { (LightType::DeltaPosition, None) } else { (LightType::Area, Some(alpha)) }; let mi = match medium { Some(m) => { let ptr = arena.alloc(m); MediumInterface { inside: ptr, outside: ptr, } } None => MediumInterface::default(), }; let base = LightBase::new(light_type, render_from_light, mi); if let Some(ref img) = image { let desc = img .get_channel_desc(&["R", "G", "B"]) .expect("Image used for DiffuseAreaLight doesn't have R, G, B channels"); assert_eq!(3, desc.size()); assert!(desc.is_identity()); assert!( image_color_space.is_some(), "Image provided but ColorSpace is missing" ); } let is_triangle_or_bilinear = matches!(*shape, Shape::Triangle(_) | Shape::BilinearPatch(_)); if render_from_light.has_scale(None) && !is_triangle_or_bilinear { println!( "Scaling detected in rendering to light space transformation! \ Proceed at your own risk; your image may have errors." ); } let shape_ptr = shape.upload(arena); let image_ptr = image .as_ref() .map(|img| arena.alloc(*img.device())) .unwrap_or(Ptr::null()); let colorspace_ptr = image_color_space .map(|cs| cs.upload(arena)) .unwrap_or(Ptr::null()); let lemit_ptr = arena.alloc(lookup_spectrum(l_for_scale).device()); let specific = DiffuseAreaLight { base, area: shape.area(), shape: shape_ptr, alpha: alpha_ptr, image: image_ptr, colorspace: colorspace_ptr, lemit: lemit_ptr, two_sided, scale, }; Ok(Light::DiffuseArea(specific)) }