use glam::Vec3;
use crate::{
geometry::tool::ToolPrimitive,
optics::raytracer::{HitRecord, Ray, intersect_stone::intersect_stone_soa},
simd::PlanesSoA32,
};
#[derive(Debug, Clone, Copy)]
pub(super) enum RayFate {
EntryBlocked,
Leaked,
ExitedUpward(ExitPath),
Absorbed,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct ExitPath {
pub(super) dir: Vec3,
pub(super) transmittance: f32,
pub(super) exit_cos_theta: f32,
pub(super) facet_idx: usize,
pub(super) bounces: u32,
pub(super) path_len: f32,
}
fn fresnel_transmittance(n1: f32, n2: f32, cos_i: f32, cos_t: f32) -> f32 {
let denom_s = n2.mul_add(cos_t, n1 * cos_i);
let denom_p = n2.mul_add(cos_i, n1 * cos_t);
if denom_s.abs() < 1e-6 || denom_p.abs() < 1e-6 {
return 0.0;
}
let rs = (n2.mul_add(-cos_t, n1 * cos_i) / denom_s).powi(2);
let rp = (n2.mul_add(-cos_i, n1 * cos_t) / denom_p).powi(2);
let r = f32::midpoint(rs, rp);
(1.0 - r).clamp(0.0, 1.0)
}
#[derive(Clone, Copy)]
pub(super) struct StoneArena<'a> {
pub(super) plane_soa: &'a PlanesSoA32,
pub(super) tools: &'a [ToolPrimitive],
}
impl StoneArena<'_> {
pub(super) fn intersect(&self, ray: Ray) -> Option<HitRecord> {
intersect_stone_soa(ray, self.plane_soa, self.plane_soa.len(), self.tools)
}
}
pub(super) fn trace_wavelength(
entry_point: Vec3,
incoming_dir: Vec3,
n_entry: Vec3,
cos_i: f32,
stone: StoneArena<'_>,
index: f32,
) -> RayFate {
let sin2_t = (1.0 / (index * index)) * cos_i.mul_add(-cos_i, 1.0);
if sin2_t > 1.0 {
return RayFate::EntryBlocked;
}
let cos_t = (1.0 - sin2_t).sqrt();
let mut curr_dir =
((1.0 / index) * incoming_dir + (1.0 / index).mul_add(cos_i, -cos_t) * n_entry).normalize();
let mut hit_point = entry_point + curr_dir * 1e-4;
let sin_crit = 1.0 / index;
let entry_transmittance = fresnel_transmittance(1.0, index, cos_i, cos_t);
let mut leaked = false;
let mut exited_upwards = false;
let mut exit_dir = Vec3::ZERO;
let mut exit_transmittance = 0.0f32;
let mut exit_cos_theta = 0.0f32;
let mut exit_facet_idx = usize::MAX;
let mut exit_bounces = 0u32;
let mut path_len = 0.0f32;
for bounce in 0..10 {
let inside_ray = Ray {
origin: hit_point,
dir: curr_dir,
};
let next_hit = stone.intersect(inside_ray);
let Some(next_rec) = next_hit else { break };
path_len += next_rec.t;
let next_point = inside_ray.origin + next_rec.t * inside_ray.dir;
let n_out = next_rec.normal;
let cos_theta = curr_dir.dot(n_out).clamp(0.0, 1.0);
let sin_theta = cos_theta.mul_add(-cos_theta, 1.0).max(0.0).sqrt();
if sin_theta < sin_crit {
let sin2_out = (index * index) * cos_theta.mul_add(-cos_theta, 1.0);
if sin2_out <= 1.0 {
let cos_out = (1.0 - sin2_out).sqrt();
let out_dir =
(index * curr_dir + index.mul_add(-cos_theta, cos_out) * n_out).normalize();
if n_out.y < -0.05 {
leaked = true; } else if out_dir.y > 0.05 {
exited_upwards = true;
exit_dir = out_dir;
exit_transmittance = fresnel_transmittance(index, 1.0, cos_theta, cos_out);
exit_cos_theta = cos_out;
exit_facet_idx = next_rec.facet_idx;
exit_bounces = bounce;
}
break;
}
}
curr_dir = (curr_dir - 2.0 * cos_theta * n_out).normalize();
hit_point = next_point + curr_dir * 1e-4;
}
if leaked {
RayFate::Leaked
} else if exited_upwards {
RayFate::ExitedUpward(ExitPath {
dir: exit_dir,
transmittance: entry_transmittance * exit_transmittance,
exit_cos_theta,
facet_idx: exit_facet_idx,
bounces: exit_bounces,
path_len,
})
} else {
RayFate::Absorbed
}
}