mod support;
use bevy::prelude::*;
use bevy::tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool};
use bevy_elements::*;
fn world_at(hour: f32) -> App {
IoTaskPool::get_or_init(bevy::tasks::TaskPool::default);
ComputeTaskPool::get_or_init(bevy::tasks::TaskPool::default);
AsyncComputeTaskPool::get_or_init(bevy::tasks::TaskPool::default);
let mut app = App::new();
app.add_plugins(support::headless_plugins())
.add_plugins((ClimatePlugin, CelestialPlugin))
.insert_resource(WorldClock::new(CivilDate::new(2026, 4, 15)).at_hour(hour as f64))
.insert_resource(GeoLocation::default());
support::finish(&mut app);
app
}
fn exposure_at(app: &mut App) -> f32 {
app.update();
app.world().resource::<ElementsExposure>().exposure
}
#[test]
fn the_crate_publishes_the_camera_s_exposure() {
let mut app = world_at(12.0);
let noon = exposure_at(&mut app);
assert!(noon > 0.0, "the exposure is a positive number: {noon}");
let mut app = world_at(22.0);
let night = exposure_at(&mut app);
assert!(night > 0.0, "the exposure is a positive number: {night}");
}
#[test]
fn the_night_is_exposed_far_more_than_the_noon() {
let mut app = world_at(12.0);
let noon = exposure_at(&mut app);
let mut app = world_at(22.0);
let night = exposure_at(&mut app);
assert!(
night > noon * 10.0,
"the night is opened up: {night} against {noon}"
);
}
#[test]
fn a_light_balanced_at_noon_still_reads_at_midnight() {
const ALBEDO: f32 = 0.14;
const DISPLAY: f32 = 0.18;
const DISTANCE_M: f32 = 2.0;
let mut app = world_at(12.0);
let noon = exposure_at(&mut app);
let noon_luminance = bevy_elements::light::luminance_for_display(DISPLAY, noon);
let noon_intensity = bevy_elements::light::light_intensity(noon_luminance, DISTANCE_M, ALBEDO);
let mut app = world_at(22.0);
let night = exposure_at(&mut app);
let night_luminance = bevy_elements::light::luminance_for_display(DISPLAY, night);
let night_intensity =
bevy_elements::light::light_intensity(night_luminance, DISTANCE_M, ALBEDO);
assert!(
noon_intensity > night_intensity * 1000.0,
"the noon needs far more lumens for the same picture value: {noon_intensity} against {night_intensity}"
);
let ratio = noon_intensity / night_intensity;
let expected = night / noon;
assert!(
(ratio - expected).abs() <= 0.01 * expected,
"the power ratio {ratio} should be the exposure ratio {expected}"
);
for (exposure, intensity) in [(noon, noon_intensity), (night, night_intensity)] {
let illuminance = bevy_elements::light::light_illuminance(intensity, DISTANCE_M);
let luminance = illuminance * ALBEDO / std::f32::consts::PI;
let read = bevy_elements::light::display_value(luminance, exposure);
assert!(
(read - DISPLAY).abs() <= 1e-3 * DISPLAY,
"at the exposure {exposure} the surface reads at {read}, not {DISPLAY}"
);
}
}
#[test]
fn a_game_reads_the_exposure_through_the_environment() {
let mut app = world_at(22.0);
app.update();
let exposure = app.world().resource::<ElementsExposure>().exposure;
assert!(exposure > 0.0);
let c = bevy_elements::light::night_light_colour();
assert!(c.z > c.y, "the scotopic colour is cool: {c:?}");
assert!(c.z > c.x, "the scotopic colour is cool: {c:?}");
}