use glam::Vec3;
use super::{
fan::FanGeometry,
lighting::ExitLighting,
ray_trace::{RayFate, StoneArena, trace_wavelength},
visibility::ray_is_visibly_returned,
};
use crate::optics::raytracer::Ray;
pub(super) struct ApertureSampleContext<'a> {
pub(super) stone: StoneArena<'a>,
pub(super) nd: f32,
pub(super) n_f: f32,
pub(super) n_c: f32,
pub(super) cam_forward: Vec3,
pub(super) cam_right: Vec3,
pub(super) cam_up: Vec3,
pub(super) fan: FanGeometry,
pub(super) lighting: ExitLighting<'a>,
}
pub(super) enum RayClassification {
EntryBlocked,
Windowed,
Extinct,
Returned(ReturnedRay),
}
#[derive(Clone, Copy)]
pub(super) struct ReturnedRay {
pub(super) fire: Option<(f32, f32)>,
pub(super) transmittance: f32,
pub(super) path_len: f32,
}
pub(super) fn classify_aperture_sample(
ctx: &ApertureSampleContext,
u: f32,
v: f32,
dx_sub: f32,
dz_sub: f32,
) -> Option<RayClassification> {
let ray_dir = (ctx.cam_forward + dx_sub * ctx.cam_right + dz_sub * ctx.cam_up).normalize();
let ray_origin = ctx
.fan
.origin(ctx.cam_forward, ctx.cam_right, ctx.cam_up, u, v);
let ray = Ray {
origin: ray_origin,
dir: ray_dir,
};
let hit = ctx.stone.intersect(ray);
let hit_rec = hit?;
let hit_point_entry = ray.origin + hit_rec.t * ray.dir;
let n_entry = hit_rec.normal;
let cos_i = (-ray.dir).dot(n_entry).clamp(0.0, 1.0);
match trace_wavelength(hit_point_entry, ray.dir, n_entry, cos_i, ctx.stone, ctx.nd) {
RayFate::EntryBlocked => Some(RayClassification::EntryBlocked),
RayFate::Leaked => Some(RayClassification::Windowed),
RayFate::ExitedUpward(exit_d) => {
let exit_dir = exit_d.dir;
let transmittance = exit_d.transmittance;
if !ray_is_visibly_returned(exit_dir, ctx.cam_forward, &ctx.lighting) {
return Some(RayClassification::Extinct);
}
let fate_f =
trace_wavelength(hit_point_entry, ray.dir, n_entry, cos_i, ctx.stone, ctx.n_f);
let fate_c =
trace_wavelength(hit_point_entry, ray.dir, n_entry, cos_i, ctx.stone, ctx.n_c);
let fire = match (fate_f, fate_c) {
(RayFate::ExitedUpward(exit_f), RayFate::ExitedUpward(exit_c)) => {
if exit_f.facet_idx != exit_c.facet_idx || exit_f.bounces != exit_c.bounces {
None
} else {
let transmittance_f = exit_f.transmittance;
let transmittance_c = exit_c.transmittance;
let exit_cos_f = exit_f.exit_cos_theta;
let exit_cos_c = exit_c.exit_cos_theta;
let cos_sep = exit_f.dir.dot(exit_c.dir).clamp(-1.0, 1.0);
let angle_deg = cos_sep.acos().to_degrees();
let energy_weight = transmittance * transmittance_f * transmittance_c;
let radiance_weight = exit_cos_f.max(0.0) * exit_cos_c.max(0.0);
let weight = energy_weight * radiance_weight;
Some((angle_deg, weight))
}
}
_ => None,
};
Some(RayClassification::Returned(ReturnedRay {
fire,
transmittance,
path_len: exit_d.path_len,
}))
}
RayFate::Absorbed => Some(RayClassification::Extinct),
}
}