use super::helpers::stable_orthonormal_basis;
use glam::Vec3;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BirefringenceParams {
pub delta_n: f32,
pub c_axis: [f32; 3],
}
impl BirefringenceParams {
#[must_use]
pub fn new(delta_n: f32, c_axis: Vec3) -> Self {
let norm_c = c_axis.normalize_or_zero();
Self {
delta_n,
c_axis: [norm_c.x, norm_c.y, norm_c.z],
}
}
#[must_use]
pub const fn c_axis_vec3(&self) -> Vec3 {
Vec3::from_array(self.c_axis)
}
#[must_use]
pub fn effective_extraordinary_index(n_o: f32, n_e: f32, theta: f32) -> f32 {
if (n_o - n_e).abs() < 1e-5 {
return n_o;
}
let sin_t = theta.sin();
let cos_t = theta.cos();
let denom_sq = (n_e * cos_t).mul_add(n_e * cos_t, (n_o * sin_t).powi(2));
if denom_sq <= 1e-8 {
return n_o;
}
(n_o * n_e) / denom_sq.sqrt()
}
#[must_use]
pub fn walk_off_angle(n_o: f32, n_e: f32, theta: f32) -> f32 {
Self::walk_off_from_sin_cos(n_o, n_e, theta.sin(), theta.cos())
}
#[must_use]
#[expect(
clippy::similar_names,
reason = "n_o2/n_e2 are the squared ordinary/extraordinary indices, mirrored by the WGSL twin"
)]
pub fn walk_off_from_sin_cos(n_o: f32, n_e: f32, sin_t: f32, cos_t: f32) -> f32 {
if (n_o - n_e).abs() < 1e-5 {
return 0.0;
}
let n_o2 = n_o * n_o;
let n_e2 = n_e * n_e;
let tan_rho = ((n_o2 - n_e2) * sin_t * cos_t)
/ (n_e2 * cos_t)
.mul_add(cos_t, n_o2 * sin_t * sin_t)
.max(1e-6);
tan_rho.atan()
}
#[must_use]
pub fn extraordinary_poynting_dir(wave_normal: Vec3, c_axis: Vec3, n_o: f32, n_e: f32) -> Vec3 {
let cos_theta = wave_normal.dot(c_axis).clamp(-1.0, 1.0);
let abs_cos = cos_theta.abs();
let sin_t = ((1.0 - abs_cos) * (1.0 + abs_cos)).max(0.0).sqrt();
let delta = Self::walk_off_from_sin_cos(n_o, n_e, sin_t, abs_cos);
if delta.abs() < 1e-5 {
return wave_normal;
}
let c_proj = (c_axis - cos_theta * wave_normal).normalize_or_zero();
if c_proj.length_squared() < 1e-6 {
return wave_normal;
}
let sign = if cos_theta >= 0.0 { 1.0 } else { -1.0 };
(wave_normal * delta.cos() - sign * c_proj * delta.sin()).normalize()
}
#[must_use]
pub fn ordinary_eigen_polarization(wave_normal: Vec3, c_axis: Vec3) -> Vec3 {
let cross = wave_normal.cross(c_axis);
if cross.length_squared() > 1e-8 {
cross.normalize()
} else {
stable_orthonormal_basis(wave_normal.normalize_or_zero()).0
}
}
#[must_use]
pub fn extraordinary_eigen_polarization(wave_normal: Vec3, c_axis: Vec3) -> Vec3 {
let o_hat = Self::ordinary_eigen_polarization(wave_normal, c_axis);
wave_normal.cross(o_hat).normalize_or_zero()
}
}