use crate::hit::Hittable;
use crate::ray::Ray;
use crate::vec3::Vec3;
use rand::Rng;
use rand_xoshiro::Xoshiro256PlusPlus;
pub const C: f32 = 2.998e8;
pub struct TransientFrame {
pub width: usize,
pub height: usize,
pub num_bins: usize,
pub dt: f32,
data: Vec<f32>,
}
impl TransientFrame {
pub fn new(width: usize, height: usize, num_bins: usize, dt: f32) -> Self {
Self {
width, height, num_bins, dt,
data: vec![0.0; width * height * num_bins * 3],
}
}
#[inline]
fn idx(&self, bin: usize, x: usize, y: usize, ch: usize) -> usize {
((bin * self.height + y) * self.width + x) * 3 + ch
}
#[inline]
pub fn accumulate(&mut self, bin: usize, x: usize, y: usize, color: Vec3) {
if bin >= self.num_bins { return; }
let i = self.idx(bin, x, y, 0);
self.data[i] += color.x;
self.data[i + 1] += color.y;
self.data[i + 2] += color.z;
}
pub fn slice(&self, bin: usize) -> &[f32] {
let base = bin * self.height * self.width * 3;
&self.data[base..base + self.height * self.width * 3]
}
pub fn merge_tile(&mut self, tile: &TransientFrame, x0: usize, y0: usize) {
debug_assert_eq!(self.num_bins, tile.num_bins);
debug_assert!(x0 + tile.width <= self.width);
debug_assert!(y0 + tile.height <= self.height);
for bin in 0..self.num_bins {
for ly in 0..tile.height {
let li = tile.idx(bin, 0, ly, 0);
let gi = self.idx(bin, x0, y0 + ly, 0);
let n = tile.width * 3;
let (lhs, rhs) = (
&mut self.data[gi..gi + n],
&tile.data[li..li + n],
);
for (a, b) in lhs.iter_mut().zip(rhs) { *a += *b; }
}
}
}
pub fn scale(&mut self, k: f32) { for v in &mut self.data { *v *= k; } }
}
pub struct Pulse {
pub sigma: f32,
}
impl Pulse {
#[inline]
pub fn weight(&self, t_center: f32) -> f32 {
let z = t_center / self.sigma;
(-0.5 * z * z).exp()
}
}
#[allow(clippy::too_many_arguments)]
pub fn trace_path(
ray_in: Ray,
world: &dyn Hittable,
pulse: &Pulse,
max_depth: u32,
px: usize,
py: usize,
frame: &mut TransientFrame,
rng: &mut Xoshiro256PlusPlus,
) {
let mut ray = ray_in;
let mut throughput = Vec3::ONE;
for depth in 0..max_depth {
let Some(rec) = world.hit(&ray, 1e-3, f32::INFINITY) else {
return;
};
let emitted = rec.material.emitted();
if emitted.length_squared() > 0.0 {
let total_length = ray.path_length + rec.t;
let t_arrival = total_length / C;
if t_arrival.is_finite() && t_arrival >= 0.0 {
let half_window = (4.0 * pulse.sigma / frame.dt).ceil() as isize;
let center_bin = (t_arrival / frame.dt) as isize;
let first = (center_bin - half_window).max(0) as usize;
let last_excl = ((center_bin + half_window + 1).max(0) as usize)
.min(frame.num_bins);
for bin in first..last_excl {
let t_bin_center = (bin as f32 + 0.5) * frame.dt;
let w = pulse.weight(t_bin_center - t_arrival);
let contribution = throughput * emitted * w;
frame.accumulate(bin, px, py, contribution);
}
}
return;
}
let Some(scatter) = rec.material.scatter(&ray, &rec, rng) else {
return;
};
throughput *= scatter.attenuation;
if depth > 3 {
let p_continue = throughput.max_element().min(0.95);
if rng.r#gen::<f32>() > p_continue { return; }
throughput /= p_continue;
}
ray = scatter.scattered;
}
}
#[derive(Clone, Copy, Debug)]
pub struct Deposit {
pub pixel: (usize, usize),
pub bin: usize,
pub color: Vec3,
}
impl TransientFrame {
pub fn deposit(&mut self, d: Deposit) {
self.accumulate(d.bin, d.pixel.0, d.pixel.1, d.color);
}
}