use super::{IllustrationStyle, TemporalOptions, TemporalState};
use crate::{BackdropStyle, DisplayTransform};
use molgfx_math::{Camera, Mat4, Projection, Vec3};
fn camera() -> Camera {
Camera {
eye: Vec3::new(0.0, 0.0, 8.0),
target: Vec3::ZERO,
up: Vec3::Y,
projection: Projection::Perspective {
fov_y: 0.8,
aspect: 1.0,
near: 0.1,
far: 100.0,
},
}
}
fn options(quality: bool) -> TemporalOptions {
TemporalOptions {
extent: [800, 800],
reset: false,
quality,
publication: false,
illustration: IllustrationStyle::default(),
depth_cue: [0.0; 4],
optics: [8.0, 0.0, 0.0, 0.0],
motion_blur: [0.0; 4],
atmosphere: crate::engine::backdrop::pack(
BackdropStyle::default(),
DisplayTransform::default(),
false,
[0.0; 4],
),
lighting: crate::LightingEnvironment::default().packed(),
shadow_view: Mat4::IDENTITY,
shadow_projection: Mat4::IDENTITY,
shadow_view_proj: Mat4::IDENTITY,
}
}
#[test]
fn the_first_sample_rejects_history_and_the_second_reprojects_it() {
let mut state = TemporalState::default();
let first = state.prepare(&camera(), &options(false));
let second = state.prepare(&camera(), &options(false));
assert_eq!(first.temporal[0].to_bits(), 0.0f32.to_bits());
assert_eq!(second.temporal[0].to_bits(), 1.0f32.to_bits());
assert_ne!(first.proj, second.proj);
assert_eq!(state.write_index(), 1);
}
#[test]
fn a_camera_cut_discards_the_previous_history() {
let mut state = TemporalState::default();
let _ = state.prepare(&camera(), &options(false));
let mut cut = camera();
cut.eye = Vec3::new(20.0, 0.0, 8.0);
let after_cut = state.prepare(&cut, &options(false));
assert_eq!(after_cut.temporal[0].to_bits(), 0.0f32.to_bits());
assert_eq!(after_cut.temporal[3].to_bits(), 0.0f32.to_bits());
}
#[test]
fn any_camera_motion_blocks_quality_until_a_stable_frame() {
let mut state = TemporalState::default();
assert!(state.camera_changed(&camera()));
let _ = state.prepare(&camera(), &options(true));
assert!(!state.camera_changed(&camera()));
let mut moved = camera();
moved.target.x = 0.01;
assert!(state.camera_changed(&moved));
}
#[test]
fn camera_motion_uses_an_unjittered_projection() {
let mut state = TemporalState::default();
let _ = state.prepare(&camera(), &options(false));
let mut moved = camera();
moved.eye.x += 0.01;
let frame = state.prepare(&moved, &options(false));
assert_eq!(frame.proj, moved.projection.matrix());
}
#[test]
fn caller_scheduling_stops_after_each_mode_reaches_its_sample_budget() {
let mut realtime = TemporalState::default();
for _ in 0..8 {
let _ = realtime.prepare(&camera(), &options(false));
}
assert!(!realtime.needs_another_frame(8));
let mut quality = TemporalState::default();
for _ in 0..63 {
let _ = quality.prepare(&camera(), &options(true));
}
assert!(quality.needs_another_frame(64));
let _ = quality.prepare(&camera(), &options(true));
assert!(!quality.needs_another_frame(64));
}
#[test]
fn a_small_camera_move_restarts_refinement_without_discarding_reprojectable_history() {
let mut state = TemporalState::default();
for _ in 0..8 {
state.prepare(&camera(), &options(false));
}
let mut moved = camera();
moved.eye.x += 0.01;
let first = state.prepare(&moved, &options(false));
assert_ne!(first.temporal[0].to_bits(), 0.0_f32.to_bits());
assert!(state.needs_another_frame(8));
for _ in 0..7 {
state.prepare(&moved, &options(false));
}
assert!(!state.needs_another_frame(8));
state.invalidate_convergence();
assert!(state.needs_another_frame(8));
}
#[test]
fn convergence_is_a_four_sample_budget_reached_in_order_and_restarted_by_any_change() {
const BUDGET: u8 = 4;
let mut state = TemporalState::default();
let mut samples = 0_u8;
while state.needs_another_frame(BUDGET) {
let _ = state.prepare(&camera(), &options(false));
samples = samples.saturating_add(1);
assert!(samples <= BUDGET, "the budget bounds the sample count");
}
assert_eq!(samples, BUDGET, "four samples converge the image");
state.invalidate_convergence();
assert!(state.needs_another_frame(BUDGET));
for _ in 0..BUDGET {
let _ = state.prepare(&camera(), &options(false));
}
assert!(!state.needs_another_frame(BUDGET));
let mut moved = camera();
moved.eye.x += 0.05;
let _ = state.prepare(&moved, &options(false));
assert!(state.needs_another_frame(BUDGET));
}