use super::{R_UNPOL_PDF_MAX, R_UNPOL_PDF_MIN, context::ExitSplitCtx};
use crate::optics::{
polarization::{MuellerMatrix, StokesVector},
raytracer::{
camera::Ray, environment::sample_environment_channel, intersect_stone::intersect_stone_soa,
},
};
use glam::Vec3;
#[cfg(debug_assertions)]
thread_local! {
static TRUNCATED_ENERGY: std::cell::Cell<f64> = const { std::cell::Cell::new(0.0) };
}
#[doc(hidden)]
#[must_use]
pub fn exit_split_truncated_energy_take() -> f64 {
#[cfg(debug_assertions)]
{
TRUNCATED_ENERGY.with(|e| e.replace(0.0))
}
#[cfg(not(debug_assertions))]
{
0.0
}
}
pub(super) struct ChannelTransmissionInputs {
pub(super) n1k: f32,
pub(super) n2k: f32,
pub(super) cos_i: f32,
pub(super) cos_t_k: f32,
pub(super) r_unpol: f32,
pub(super) entering_anisotropic: bool,
pub(super) entry_mode_azimuth2: Option<(f32, f32)>,
pub(super) incident_stokes_k: StokesVector,
}
#[expect(
clippy::similar_names,
reason = "s/p polarisation pairs (t_s_k/t_p_k, r_s_k/r_p_k) are the established Fresnel names"
)]
pub(super) fn compute_channel_transmission(
inputs: &ChannelTransmissionInputs,
) -> (StokesVector, f32) {
let &ChannelTransmissionInputs {
n1k,
n2k,
cos_i,
cos_t_k,
r_unpol,
entering_anisotropic,
entry_mode_azimuth2,
incident_stokes_k,
} = inputs;
let t_s_k = (2.0 * n1k * cos_i) / f32::mul_add(n2k, cos_t_k, n1k * cos_i);
let t_p_k = (2.0 * n1k * cos_i) / f32::mul_add(n1k, cos_t_k, n2k * cos_i);
let trans_matrix_k =
MuellerMatrix::fresnel_transmission(n1k, n2k, cos_i, cos_t_k, t_s_k, t_p_k);
let incident_k =
if entering_anisotropic && let Some((cos_2psi_x, sin_2psi_x)) = entry_mode_azimuth2 {
let i_k = incident_stokes_k.i;
StokesVector::new(i_k, i_k * cos_2psi_x, i_k * sin_2psi_x, 0.0)
} else {
incident_stokes_k
};
let transmitted = incident_k
.apply_matrix(&trans_matrix_k)
.scale(1.0 / (1.0 - r_unpol));
let r_s_k = f32::mul_add(n2k, -cos_t_k, n1k * cos_i) / f32::mul_add(n2k, cos_t_k, n1k * cos_i);
let r_p_k = f32::mul_add(n1k, -cos_t_k, n2k * cos_i) / f32::mul_add(n1k, cos_t_k, n2k * cos_i);
let r_unpol_k =
(0.5 * r_p_k.mul_add(r_p_k, r_s_k * r_s_k)).clamp(R_UNPOL_PDF_MIN, R_UNPOL_PDF_MAX);
(transmitted, r_unpol_k)
}
pub(super) fn try_split_exit_channel(
exit: &mut ExitSplitCtx<'_>,
hit_point: Vec3,
k: usize,
lambda_k: f32,
dir_k: Vec3,
transmitted_intensity: f32,
) {
let probe = Ray {
origin: hit_point + dir_k * 1e-4,
dir: dir_k,
};
if intersect_stone_soa(probe, exit.plane_soa, exit.plane_soa.len(), exit.tools).is_some() {
#[cfg(debug_assertions)]
TRUNCATED_ENERGY.with(|e| e.set(e.get() + f64::from(transmitted_intensity.max(0.0))));
return;
}
let env_spectral = sample_environment_channel(
exit.environment,
dir_k,
lambda_k,
exit.studio_rig.as_ref(),
exit.observer,
);
exit.split_radiance[k] = f32::mul_add(
transmitted_intensity.max(0.0) * exit.split_mis_weight,
env_spectral,
exit.split_radiance[k],
);
}