168 lines
5 KiB
Rust
168 lines
5 KiB
Rust
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::sampling::PiecewiseConstant2D;
|
|
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;
|
|
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 fn create(
|
|
render_from_light: Transform,
|
|
medium: Option<Medium>,
|
|
params: &ParameterDictionary,
|
|
loc: &FileLoc,
|
|
_shape: &Shape,
|
|
_alpha_text: &FloatTexture,
|
|
colorspace: Option<&RGBColorSpace>,
|
|
arena: &Arena,
|
|
) -> Result<Light> {
|
|
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<Image> = 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 mi = match medium {
|
|
Some(m) => {
|
|
let ptr = arena.alloc(m);
|
|
MediumInterface {
|
|
inside: ptr,
|
|
outside: ptr,
|
|
}
|
|
}
|
|
None => MediumInterface::default(),
|
|
};
|
|
let base = LightBase::new(LightType::DeltaPosition, render_from_light, mi);
|
|
|
|
let iemit = lookup_spectrum(&i);
|
|
|
|
let image_ptr = if !image.is_null() {
|
|
let distrib = PiecewiseConstant2D::from_image(&image);
|
|
let distrib_ptr = distrib.upload(arena);
|
|
let img_ptr = image.upload(arena);
|
|
(img_ptr, distrib_ptr)
|
|
} else {
|
|
(Ptr::null(), Ptr::null())
|
|
};
|
|
|
|
let specific = GoniometricLight {
|
|
base,
|
|
iemit: arena.alloc(iemit.device()),
|
|
scale,
|
|
image: image_ptr.0,
|
|
distrib: image_ptr.1,
|
|
};
|
|
|
|
Ok(Light::Goniometric(specific))
|
|
}
|
|
|
|
fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> {
|
|
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
|
|
}
|