use super::{
camera::{FacetFinish, HitRecord, Ray},
environment::{EnvironmentSource, environment_supports_nee},
intersect::build_plane_soa,
};
use crate::{
geometry::{plane::GpuFacetPlane, tool::StoneGeometry},
optics::{fluorescence::Fluorescence, materials::GemMaterial},
};
use glam::Vec3;
mod attributed;
mod bounce;
mod inner;
mod path_events;
#[cfg(test)]
mod tests;
pub use attributed::trace_spectral_ray_attributed_soa_geom;
pub use path_events::{ExitEvent, PathEvents};
pub(super) use bounce::apply_russian_roulette;
#[must_use]
pub fn wrapped_hero_wavelengths<const N: usize>(hero_rand: f32) -> [f32; N] {
const SPECTRUM_SPAN: f32 = 780.0 - 380.0;
let channel_width = SPECTRUM_SPAN / N as f32;
let lambda_hero = hero_rand.mul_add(SPECTRUM_SPAN, 380.0);
std::array::from_fn(|k| {
let offset = (k as f32).mul_add(channel_width, lambda_hero - 380.0);
380.0 + offset.rem_euclid(SPECTRUM_SPAN)
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PathTermination {
Escaped,
ScatterAbsorbed,
RussianRoulette,
HitCap,
}
#[expect(
clippy::too_many_arguments,
reason = "the crate's public raytracing entry point: each parameter is one \
independent input a caller supplies once per traced ray; wrapping it in \
a context struct would only make every caller build a struct instead of \
calling a function"
)]
#[must_use]
pub fn trace_spectral_ray(
initial_ray: Ray,
planes: &[GpuFacetPlane],
material: &GemMaterial,
max_bounces: u32,
environment: EnvironmentSource<'_>,
rng_seed: u32,
hero_rand: f32,
primary_hit_out: Option<&mut Option<HitRecord>>,
) -> Vec3 {
let plane_soa = build_plane_soa(planes);
inner::trace_spectral_ray_inner(
initial_ray,
planes,
&plane_soa,
&[],
material,
max_bounces,
environment,
rng_seed,
hero_rand,
primary_hit_out,
true,
true,
environment_supports_nee(environment),
None,
)
}
#[expect(
clippy::too_many_arguments,
reason = "see trace_spectral_ray's own reason, plus the per-facet finish slice"
)]
#[must_use]
pub fn trace_spectral_ray_with_finish(
initial_ray: Ray,
planes: &[GpuFacetPlane],
facet_finishes: &[FacetFinish],
material: &GemMaterial,
max_bounces: u32,
environment: EnvironmentSource<'_>,
rng_seed: u32,
hero_rand: f32,
primary_hit_out: Option<&mut Option<HitRecord>>,
) -> Vec3 {
let plane_soa = build_plane_soa(planes);
trace_spectral_ray_with_finish_soa(
initial_ray,
planes,
&plane_soa,
facet_finishes,
material,
max_bounces,
environment,
rng_seed,
hero_rand,
primary_hit_out,
)
}
#[expect(
clippy::too_many_arguments,
reason = "see trace_spectral_ray's own reason, plus the per-facet finish slice and \
the caller-supplied plane_soa arena alongside the planes slice it was \
built from"
)]
#[must_use]
pub fn trace_spectral_ray_with_finish_soa(
initial_ray: Ray,
planes: &[GpuFacetPlane],
plane_soa: &crate::simd::PlanesSoA32,
facet_finishes: &[FacetFinish],
material: &GemMaterial,
max_bounces: u32,
environment: EnvironmentSource<'_>,
rng_seed: u32,
hero_rand: f32,
primary_hit_out: Option<&mut Option<HitRecord>>,
) -> Vec3 {
inner::trace_spectral_ray_inner(
initial_ray,
planes,
plane_soa,
facet_finishes,
material,
max_bounces,
environment,
rng_seed,
hero_rand,
primary_hit_out,
true,
true,
environment_supports_nee(environment),
None,
)
}
#[expect(
clippy::too_many_arguments,
reason = "see trace_spectral_ray's own reason"
)]
#[must_use]
pub fn trace_spectral_ray_geom(
initial_ray: Ray,
geom: StoneGeometry<'_>,
material: &GemMaterial,
fluorescence: &Fluorescence,
max_bounces: u32,
environment: EnvironmentSource<'_>,
rng_seed: u32,
hero_rand: f32,
primary_hit_out: Option<&mut Option<HitRecord>>,
) -> Vec3 {
let plane_soa = build_plane_soa(geom.planes);
trace_spectral_ray_with_finish_soa_geom(
initial_ray,
geom,
&plane_soa,
&[],
material,
fluorescence,
max_bounces,
environment,
rng_seed,
hero_rand,
primary_hit_out,
)
}
#[expect(
clippy::too_many_arguments,
reason = "see trace_spectral_ray_with_finish_soa's own reason"
)]
#[must_use]
pub fn trace_spectral_ray_with_finish_soa_geom(
initial_ray: Ray,
geom: StoneGeometry<'_>,
plane_soa: &crate::simd::PlanesSoA32,
facet_finishes: &[FacetFinish],
material: &GemMaterial,
fluorescence: &Fluorescence,
max_bounces: u32,
environment: EnvironmentSource<'_>,
rng_seed: u32,
hero_rand: f32,
primary_hit_out: Option<&mut Option<HitRecord>>,
) -> Vec3 {
inner::trace_spectral_ray_inner_geom(
initial_ray,
geom.planes,
geom.tools,
plane_soa,
facet_finishes,
material,
fluorescence,
max_bounces,
environment,
rng_seed,
hero_rand,
primary_hit_out,
true,
true,
environment_supports_nee(environment),
None,
)
}
#[expect(
clippy::too_many_arguments,
reason = "see trace_spectral_ray's own reason, plus the per-facet finish slice and \
the bounce_cost harness's own instrumentation"
)]
#[must_use]
pub fn trace_spectral_ray_with_finish_instrumented(
initial_ray: Ray,
planes: &[GpuFacetPlane],
facet_finishes: &[FacetFinish],
material: &GemMaterial,
max_bounces: u32,
environment: EnvironmentSource<'_>,
rng_seed: u32,
hero_rand: f32,
) -> (Vec3, u32, PathTermination) {
let plane_soa = build_plane_soa(planes);
let mut termination = (max_bounces, PathTermination::HitCap);
let radiance = inner::trace_spectral_ray_inner(
initial_ray,
planes,
&plane_soa,
facet_finishes,
material,
max_bounces,
environment,
rng_seed,
hero_rand,
None,
true,
true,
environment_supports_nee(environment),
Some(&mut termination),
);
(radiance, termination.0, termination.1)
}