use super::rig::unit_radiance;
use super::sun::{SUN_SOLID_ANGLE, sun_radiance_factor};
use super::{LightingModel, LightingPreset};
use crate::optics::studio_rig::StudioRig;
use glam::{DVec3, Vec3};
use std::f64::consts::PI;
use std::sync::{Mutex, PoisonError};
pub const TRAY_MODEL_VERSION: u32 = 1;
pub const TRAY_ALBEDO_WHITE: f32 = 0.90;
pub const TRAY_ALBEDO_GREY: f32 = 0.18;
pub const TRAY_ALBEDO_BLACK: f32 = 0.02;
const IRRADIANCE_SAMPLES: u32 = 32_768;
static CACHE: Mutex<Option<(LightingPreset, u32, u32, f32)>> = Mutex::new(None);
fn hemisphere_direction(index: u32) -> (Vec3, f64) {
let y = (f64::from(index) + 0.5) / f64::from(IRRADIANCE_SAMPLES);
let radius = y.mul_add(-y, 1.0).max(0.0).sqrt();
let phi = f64::from(index) * (PI * (3.0 - 5.0_f64.sqrt()));
let (sin_phi, cos_phi) = phi.sin_cos();
(
DVec3::new(radius * cos_phi, y, radius * sin_phi).as_vec3(),
y,
)
}
#[allow(
clippy::cast_possible_truncation,
reason = "the f64 accumulation is rounded to the f32 the scene carries, once"
)]
fn irradiance_over_pi_uncached(preset: LightingPreset, light_yaw: f32, light_pitch: f32) -> f32 {
if preset.model() == LightingModel::Aset {
return 1.0;
}
let rig = StudioRig::new(light_yaw, light_pitch);
let geometry = if preset.model() == LightingModel::DaylightSun {
LightingPreset::DaylightDome
} else {
preset
};
let mut sum = 0.0_f64;
for index in 0..IRRADIANCE_SAMPLES {
let (dir, cos_theta) = hemisphere_direction(index);
sum = f64::from(unit_radiance(dir, geometry, &rig)).mul_add(cos_theta, sum);
}
let mut over_pi = (2.0 / f64::from(IRRADIANCE_SAMPLES)) * sum;
if preset.model() == LightingModel::DaylightSun {
let key = rig.key_dir;
over_pi += f64::from(sun_radiance_factor(key))
* f64::from(SUN_SOLID_ANGLE)
* f64::from(key.y.max(0.0))
/ PI;
}
over_pi as f32
}
#[must_use]
pub fn rig_irradiance_over_pi(preset: LightingPreset, light_yaw: f32, light_pitch: f32) -> f32 {
let (yaw_bits, pitch_bits) = (light_yaw.to_bits(), light_pitch.to_bits());
let hit = (*CACHE.lock().unwrap_or_else(PoisonError::into_inner))
.filter(|entry| entry.0 == preset && entry.1 == yaw_bits && entry.2 == pitch_bits);
if let Some((_, _, _, value)) = hit {
return value;
}
let value = irradiance_over_pi_uncached(preset, light_yaw, light_pitch);
*CACHE.lock().unwrap_or_else(PoisonError::into_inner) =
Some((preset, yaw_bits, pitch_bits, value));
value
}
#[must_use]
pub fn tray_radiance(albedo: f32, preset: LightingPreset, light_yaw: f32, light_pitch: f32) -> f32 {
if albedo > 0.0 {
albedo * rig_irradiance_over_pi(preset, light_yaw, light_pitch)
} else {
0.0
}
}