178 lines
5.5 KiB
Rust
178 lines
5.5 KiB
Rust
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<Image>,
|
|
) -> Self;
|
|
}
|
|
|
|
impl CreateGoniometricLight for GoniometricLight {
|
|
fn new(
|
|
render_from_light: Transform,
|
|
medium_interface: MediumInterface,
|
|
le: Spectrum,
|
|
scale: Float,
|
|
image: Ptr<Image>,
|
|
) -> 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<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 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<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
|
|
}
|