use glam::Vec3;
use crate::{
geometry::tool::ToolPrimitive,
optics::raytracer::{FacetFinish, HitRecord, Ray, intersect_stone::intersect_stone_soa_side},
renderer::frame_scene::FrameScene,
simd::{PlanesSoA32, exp_lane},
};
use super::{
CUTOFF, WORKLIST_CAPACITY, WhittedPrep,
fresnel::{dot, fresnel_unpolarised_f32, reflect, refract, scale_add},
};
const RESTART: f32 = 1.0e-4;
#[derive(Clone, Copy)]
struct Branch {
ray: Ray,
weight: Vec3,
inside: bool,
depth: u32,
primary: bool,
}
struct Worklist {
slots: [Option<Branch>; WORKLIST_CAPACITY],
len: usize,
cutoff: f32,
}
impl Worklist {
const fn new(cutoff: f32) -> Self {
Self {
slots: [None; WORKLIST_CAPACITY],
len: 0,
cutoff,
}
}
fn push(&mut self, branch: Branch, max_depth: u32) {
if branch.depth <= max_depth && branch.weight.max_element() >= self.cutoff {
debug_assert!(self.len < WORKLIST_CAPACITY);
self.slots[self.len] = Some(branch);
self.len += 1;
}
}
const fn pop(&mut self) -> Option<Branch> {
if self.len == 0 {
return None;
}
self.len -= 1;
self.slots[self.len].take()
}
}
fn restarted(point: Vec3, dir: Vec3) -> Ray {
Ray {
origin: scale_add(dir, RESTART, point),
dir,
}
}
#[expect(
clippy::too_many_arguments,
reason = "flat scalar chain state mirrors the WGSL twin's parameter list"
)]
fn emit_outside(
work: &mut Worklist,
sum: &mut Vec3,
prep: &WhittedPrep<'_>,
tools: &[ToolPrimitive],
ray: Ray,
weight: Vec3,
observer: Vec3,
depth: u32,
) {
if tools.is_empty() || work.len >= WORKLIST_CAPACITY - 1 {
*sum += weight * prep.env.leaf(ray.dir, observer);
} else {
work.push(
Branch {
ray,
weight,
inside: false,
depth,
primary: false,
},
prep.max_depth,
);
}
}
#[expect(
clippy::too_many_arguments,
reason = "flat scalar chain state mirrors the WGSL twin's parameter list"
)]
fn split_at_surface(
work: &mut Worklist,
sum: &mut Vec3,
prep: &WhittedPrep<'_>,
tools: &[ToolPrimitive],
branch: Branch,
point: Vec3,
normal: Vec3,
observer: Vec3,
n: f32,
) {
let cos_i = (-dot(branch.ray.dir, normal)).clamp(0.0, 1.0);
let eta = if branch.inside { n } else { 1.0 / n };
let reflectance = fresnel_unpolarised_f32(cos_i, eta);
let reflection = restarted(point, reflect(branch.ray.dir, normal));
let depth = branch.depth.saturating_add(1);
if branch.inside {
if let Some(transmitted) = refract(branch.ray.dir, normal, eta) {
emit_outside(
work,
sum,
prep,
tools,
restarted(point, transmitted),
branch.weight * (1.0 - reflectance),
observer,
depth,
);
}
work.push(
Branch {
ray: reflection,
weight: branch.weight * reflectance,
inside: true,
depth,
primary: false,
},
prep.max_depth,
);
} else {
let glare = if branch.primary { prep.glare } else { 1.0 };
emit_outside(
work,
sum,
prep,
tools,
reflection,
branch.weight * (reflectance * glare),
observer,
depth,
);
if let Some(transmitted) = refract(branch.ray.dir, normal, eta) {
work.push(
Branch {
ray: restarted(point, transmitted),
weight: branch.weight * (1.0 - reflectance),
inside: true,
depth,
primary: false,
},
prep.max_depth,
);
}
}
}
#[expect(
clippy::too_many_arguments,
reason = "the frame scene, prepared state and one camera ray stay flat to mirror the WGSL twin"
)]
pub(super) fn trace_chain(
scene: &FrameScene<'_>,
tools: &[ToolPrimitive],
plane_soa: &PlanesSoA32,
prep: &WhittedPrep<'_>,
camera_ray: Ray,
n: f32,
observer: Vec3,
primary_hit_out: Option<&mut Option<HitRecord>>,
) -> Vec3 {
trace_chain_with_cutoff(
scene,
tools,
plane_soa,
prep,
camera_ray,
n,
observer,
primary_hit_out,
CUTOFF,
)
}
#[expect(
clippy::too_many_arguments,
reason = "trace_chain's parameters plus the explicit cutoff"
)]
pub(super) fn trace_chain_with_cutoff(
scene: &FrameScene<'_>,
tools: &[ToolPrimitive],
plane_soa: &PlanesSoA32,
prep: &WhittedPrep<'_>,
camera_ray: Ray,
n: f32,
observer: Vec3,
mut primary_hit_out: Option<&mut Option<HitRecord>>,
cutoff: f32,
) -> Vec3 {
if let Some(out) = primary_hit_out.as_deref_mut() {
*out = None;
}
let mut sum = Vec3::ZERO;
let mut work = Worklist::new(cutoff);
work.push(
Branch {
ray: camera_ray,
weight: Vec3::ONE,
inside: false,
depth: 0,
primary: true,
},
prep.max_depth,
);
while let Some(mut branch) = work.pop() {
let hit = if scene.planes.is_empty() {
None
} else {
intersect_stone_soa_side(branch.ray, plane_soa, scene.planes.len(), tools)
};
let Some((hit, entering)) = hit else {
sum += branch.weight * prep.env.leaf(branch.ray.dir, observer);
continue;
};
if branch.primary
&& let Some(out) = primary_hit_out.as_deref_mut()
{
*out = Some(hit);
}
if let Some(entering) = entering {
branch.inside = !entering;
}
if branch.inside {
let attenuation = -prep.alpha.at(branch.ray.dir) * hit.t;
branch.weight *= Vec3::new(
exp_lane(attenuation.x),
exp_lane(attenuation.y),
exp_lane(attenuation.z),
);
}
if matches!(
scene.facet_finishes.get(hit.facet_idx),
Some(FacetFinish::Frosted)
) {
sum += branch.weight * prep.frost.get(hit.facet_idx).copied().unwrap_or(Vec3::ZERO);
continue;
}
let point = scale_add(branch.ray.dir, hit.t, branch.ray.origin);
let normal = if branch.inside {
-hit.normal
} else {
hit.normal
};
split_at_surface(
&mut work, &mut sum, prep, tools, branch, point, normal, observer, n,
);
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cavity_overflow_preserves_the_continuation_and_resolves_the_leaf() {
let prep = WhittedPrep::for_tests(1.5, Vec3::ZERO, Vec3::new(0.3, 0.7, 0.9), 64, 1);
let ray = Ray {
origin: Vec3::ZERO,
dir: Vec3::Z,
};
let branch = Branch {
ray,
weight: Vec3::ONE,
inside: true,
depth: 1,
primary: false,
};
let mut work = Worklist::new(CUTOFF);
for _ in 0..WORKLIST_CAPACITY - 1 {
work.push(branch, prep.max_depth);
}
let tools = [ToolPrimitive::ball(Vec3::ZERO, 0.2)];
let mut sum = Vec3::ZERO;
emit_outside(
&mut work,
&mut sum,
&prep,
&tools,
ray,
Vec3::splat(0.25),
Vec3::NEG_Z,
2,
);
assert_eq!(work.len, WORKLIST_CAPACITY - 1);
assert!((sum - Vec3::new(0.3, 0.7, 0.9) * 0.25).abs().max_element() < 1e-7);
work.push(branch, prep.max_depth);
assert_eq!(work.len, WORKLIST_CAPACITY);
assert!(work.pop().expect("continuation").inside);
}
}