pub const RANGE_MARGIN: f32 = 1.25;
pub fn flame_light_offset(height: f32) -> [f32; 3] {
let height = if height.is_finite() { height.max(0.0) } else { 0.0 };
[0.0, height * 0.5, 0.0]
}
pub fn lean_light_shift(height: f32, speed: f32, diameter: f32, toward: [f32; 2]) -> [f32; 3] {
let half = if height.is_finite() { height.max(0.0) * 0.5 } else { 0.0 };
if !speed.is_finite() || !toward.iter().all(|v| v.is_finite()) {
return [0.0; 3];
}
let tilt = crate::model::fire::flame_tilt(speed, diameter);
let (sin, cos) = tilt.sin_cos();
[half * sin * toward[0], half * (cos - 1.0), half * sin * toward[1]]
}
pub fn raised_light(intensity: f32, reach: f32, over: f32) -> (f32, f32) {
let reach = reach.max(1.0);
let slant = reach.hypot(over.max(0.0));
(intensity * (slant / reach).powi(2), slant * RANGE_MARGIN)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn a_raised_light_is_brightened_by_its_slant_and_reaches_past_it() {
let (intensity, range) = raised_light(1000.0, 30.0, 40.0);
assert!((intensity - 1000.0 * (50.0f32 / 30.0).powi(2)).abs() < 1e-3);
assert!((range - 62.5).abs() < 1e-5);
}
#[test]
fn a_fire_light_sits_at_the_middle_of_its_flame() {
for height in [0.05, 0.35, 0.5, 1.1, 4.0] {
let offset = flame_light_offset(height);
assert_eq!(offset, [0.0, height * 0.5, 0.0], "{height} m");
}
assert_eq!(flame_light_offset(0.35)[0], 0.0);
assert_eq!(flame_light_offset(0.35)[2], 0.0);
}
#[test]
fn a_degenerate_flame_height_puts_its_light_on_the_ground() {
for height in [0.0, -2.0, f32::NAN, f32::NEG_INFINITY] {
let offset = flame_light_offset(height);
assert!(
offset.iter().all(|v| v.is_finite()) && offset[1] >= 0.0,
"a {height} m flame gave {offset:?}"
);
assert_eq!(offset, [0.0, 0.0, 0.0], "a {height} m flame");
}
assert_eq!(flame_light_offset(f32::INFINITY), [0.0, 0.0, 0.0]);
}
#[test]
fn a_degenerate_reach_or_a_sunken_light_still_answers() {
let (on_the_ground, range) = raised_light(500.0, 0.0, 3.0);
assert!(
(on_the_ground - 500.0 * 10.0).abs() < 1e-2,
"reach clamps to one world unit: {on_the_ground}"
);
assert!((range - 1.25 * 10.0f32.sqrt()).abs() < 1e-4);
let (below, _) = raised_light(500.0, 10.0, -4.0);
assert!((below - 500.0).abs() < 1e-3, "over clamps at 0: {below}");
}
}