use super::PathTermination;
use crate::optics::raytracer::camera::HitRecord;
use glam::Vec3;
#[derive(Clone, Copy, Debug)]
pub struct ExitEvent {
pub facet: usize,
pub normal: Vec3,
pub dir: Vec3,
pub bounce: u32,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PathEvents {
pub first_facet: Option<(usize, bool)>,
pub first_exit: Option<ExitEvent>,
pub bounces_before_exit: u32,
pub scattered: bool,
pub fluoresced: bool,
pub reentered: bool,
pub termination: Option<PathTermination>,
pub escaped_dir: Option<Vec3>,
}
impl PathEvents {
pub(crate) const fn facet(
&mut self,
bounce: u32,
hit: HitRecord,
inside_before: bool,
inside_after: bool,
dir_after: Vec3,
) {
if self.first_facet.is_none() {
self.first_facet = Some((hit.facet_idx, inside_after));
}
let exited = self.first_exit.is_some();
match (inside_before, inside_after) {
(true, false) if !exited => {
self.first_exit = Some(ExitEvent {
facet: hit.facet_idx,
normal: hit.normal,
dir: dir_after,
bounce,
});
}
(true, true) if !exited => self.bounces_before_exit += 1,
(false, true) if exited => self.reentered = true,
_ => {}
}
}
pub(crate) const fn miss(&mut self, bounce: u32, dir: Vec3) {
debug_assert!(
bounce == 0 || self.first_facet.is_some(),
"a miss after bounce 0 follows a facet event"
);
self.escaped_dir = Some(dir);
}
pub(crate) const fn scatter(&mut self) {
self.scattered = true;
}
pub(crate) const fn fluoresce(&mut self) {
self.fluoresced = true;
}
pub(crate) const fn terminated(&mut self, termination: PathTermination) {
self.termination = Some(termination);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hit(facet_idx: usize, normal: Vec3) -> HitRecord {
HitRecord {
t: 1.0,
normal,
facet_idx,
}
}
#[test]
fn an_outside_reflection_is_a_first_facet_that_did_not_enter() {
let mut events = PathEvents::default();
events.facet(0, hit(3, Vec3::Y), false, false, Vec3::Y);
assert_eq!(events.first_facet, Some((3, false)));
assert!(events.first_exit.is_none());
}
#[test]
fn entry_reflections_exit_and_reentry_are_told_apart() {
let mut events = PathEvents::default();
events.facet(0, hit(1, Vec3::Y), false, true, -Vec3::Y);
events.facet(1, hit(2, -Vec3::Y), true, true, Vec3::Y);
events.facet(2, hit(4, Vec3::X), true, false, Vec3::X);
events.facet(3, hit(5, Vec3::Z), false, true, Vec3::Z);
events.facet(4, hit(6, Vec3::X), true, true, Vec3::Z);
events.facet(5, hit(7, Vec3::X), true, false, Vec3::X);
assert_eq!(events.first_facet, Some((1, true)));
let exit = events.first_exit.expect("the path exited");
assert_eq!((exit.facet, exit.bounce), (4, 2));
assert_eq!(events.bounces_before_exit, 1);
assert!(events.reentered);
}
#[test]
fn flags_and_termination_are_recorded() {
let mut events = PathEvents::default();
events.scatter();
events.fluoresce();
events.terminated(PathTermination::RussianRoulette);
events.miss(0, Vec3::X);
assert!(events.scattered && events.fluoresced);
assert_eq!(events.termination, Some(PathTermination::RussianRoulette));
assert_eq!(events.escaped_dir, Some(Vec3::X));
}
}