use glam::Vec3;
use crate::{
optics::{
raytracer::{
EnvironmentSource, LightingModel, LightingPreset, sample_studio_environment_with_rig,
},
studio_rig::StudioRig,
},
renderer::env_map::EnvironmentMap,
};
const PROBE_WAVELENGTH_NM: f32 = 550.0;
const PROBE_EXPOSURE: f32 = 1.0;
pub(super) const HEMISPHERE_DIRECTIONS: usize = 96;
const GOLDEN_ANGLE_RAD: f32 = 2.399_963_2;
const SOURCE_OVER_AMBIENT: f32 = 4.0;
const UNIFORM_FRACTION_OF_AMBIENT: f32 = 0.5;
const LUMINANCE_WEIGHTS: [f32; 3] = [0.2126, 0.7152, 0.0722];
#[derive(Clone)]
enum RadianceSource<'a> {
Studio {
preset: LightingPreset,
rig: Box<StudioRig>,
},
Hdr(&'a EnvironmentMap),
}
impl RadianceSource<'_> {
fn radiance(&self, dir: Vec3) -> f32 {
match self {
Self::Studio { preset, rig } => sample_studio_environment_with_rig(
dir,
PROBE_WAVELENGTH_NM,
*preset,
PROBE_EXPOSURE,
rig,
Vec3::ZERO,
),
Self::Hdr(map) => {
let [r, g, b] = map.radiance_rgb(dir);
LUMINANCE_WEIGHTS[0]
.mul_add(r, LUMINANCE_WEIGHTS[1].mul_add(g, LUMINANCE_WEIGHTS[2] * b))
}
}
}
}
pub(super) fn hemisphere_direction(index: usize) -> Vec3 {
let y = (index as f32 + 0.5) / HEMISPHERE_DIRECTIONS as f32;
let ring_radius = y.mul_add(-y, 1.0).max(0.0).sqrt();
let (sin_azimuth, cos_azimuth) = (index as f32 * GOLDEN_ANGLE_RAD).sin_cos();
Vec3::new(ring_radius * cos_azimuth, y, ring_radius * sin_azimuth)
}
fn ambient_level(source: &RadianceSource<'_>) -> f32 {
let mut samples: [f32; HEMISPHERE_DIRECTIONS] =
std::array::from_fn(|index| source.radiance(hemisphere_direction(index)));
samples.sort_by(f32::total_cmp);
samples[HEMISPHERE_DIRECTIONS / 2]
}
#[derive(Clone)]
pub(super) struct ExitLighting<'a> {
source: RadianceSource<'a>,
threshold: f32,
}
impl<'a> ExitLighting<'a> {
pub(super) fn new(environment: EnvironmentSource<'a>) -> Self {
let (source, uniform) = match environment {
EnvironmentSource::Studio {
preset,
light_yaw,
light_pitch,
..
} => (
RadianceSource::Studio {
preset,
rig: Box::new(StudioRig::new(light_yaw, light_pitch)),
},
preset.model() == LightingModel::IsoHemisphere,
),
EnvironmentSource::HdrMap(map) => (RadianceSource::Hdr(map), false),
};
let factor = if uniform {
UNIFORM_FRACTION_OF_AMBIENT
} else {
SOURCE_OVER_AMBIENT
};
let threshold = (ambient_level(&source) * factor).max(f32::MIN_POSITIVE);
Self { source, threshold }
}
pub(super) fn is_illuminated(&self, exit_dir: Vec3) -> bool {
self.source.radiance(exit_dir) > self.threshold
}
}