use crate::render_types::FogParams;
const MAX_DENSITY: f32 = 10.0;
const MAX_HEIGHT_FALLOFF: f32 = 4.0;
const MAX_DISTANCE_CAP: f32 = 2_000.0;
const MIN_DISTANCE: f32 = 1.0;
const MIN_VIEWPORT: f32 = 1.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FogSettings {
pub color: [f32; 3],
pub density: f32,
pub height_falloff: f32,
pub height_reference: f32,
pub max_distance: f32,
pub phase_g: f32,
pub ambient: f32,
}
impl FogSettings {
pub fn resolve(
color: [f32; 3],
density: f32,
height_falloff: f32,
height_reference: f32,
max_distance: f32,
phase_g: f32,
ambient: f32,
) -> Self {
let max_distance = if max_distance.is_finite() {
max_distance.clamp(MIN_DISTANCE, MAX_DISTANCE_CAP)
} else {
MIN_DISTANCE
};
let color = [color[0].max(0.0), color[1].max(0.0), color[2].max(0.0)];
Self {
color,
density: density.clamp(0.0, MAX_DENSITY),
height_falloff: height_falloff.clamp(0.0, MAX_HEIGHT_FALLOFF),
height_reference,
max_distance,
phase_g: phase_g.clamp(-0.95, 0.95),
ambient: ambient.clamp(0.0, MAX_DENSITY),
}
}
pub fn params(
&self,
inv_vp: [[f32; 4]; 4],
cam_pos: [f32; 3],
sun_dir: [f32; 3],
sun_color: [f32; 3],
viewport: [f32; 2],
) -> FogParams {
let viewport = [viewport[0].max(MIN_VIEWPORT), viewport[1].max(MIN_VIEWPORT)];
FogParams {
inv_vp,
color: [self.color[0], self.color[1], self.color[2], 1.0],
cam_pos,
_pad0: 0.0,
sun_dir,
_pad1: 0.0,
sun_color,
_pad2: 0.0,
density: self.density,
height_falloff: self.height_falloff,
height_reference: self.height_reference,
max_distance: self.max_distance,
phase_g: self.phase_g,
ambient: self.ambient,
viewport,
inv_max_distance: 1.0 / self.max_distance,
_pad3: [0.0; 3],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const IDENTITY: [[f32; 4]; 4] = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
#[test]
fn resolve_clamps_density_falloff_and_distance() {
let s = FogSettings::resolve([1.0, 1.0, 1.0], 100.0, 20.0, 0.0, 1e9, 1.5, -1.0);
assert_eq!(s.density, MAX_DENSITY);
assert_eq!(s.height_falloff, MAX_HEIGHT_FALLOFF);
assert_eq!(s.max_distance, MAX_DISTANCE_CAP);
assert!(s.phase_g <= 0.95 && s.phase_g > 0.0);
assert_eq!(s.ambient, 0.0);
}
#[test]
fn resolve_passes_through_in_range_values() {
let s = FogSettings::resolve([0.6, 0.7, 0.8], 0.08, 0.25, 1.5, 120.0, 0.4, 0.2);
assert_eq!(s.color, [0.6, 0.7, 0.8]);
assert!((s.density - 0.08).abs() < 1e-6);
assert!((s.phase_g - 0.4).abs() < 1e-6);
assert!((s.max_distance - 120.0).abs() < 1e-6);
}
#[test]
fn resolve_handles_non_finite_distance() {
let s = FogSettings::resolve([0.6; 3], 0.05, 0.2, 0.0, f32::NAN, 0.4, 0.15);
assert!(s.max_distance.is_finite());
assert!(s.max_distance >= MIN_DISTANCE);
}
#[test]
fn params_derive_inverse_max_distance() {
let s = FogSettings::resolve([0.7; 3], 0.05, 0.2, 0.0, 50.0, 0.4, 0.15);
let p = s.params(
IDENTITY,
[0.0; 3],
[0.0, 1.0, 0.0],
[1.0; 3],
[1280.0, 720.0],
);
assert!((p.inv_max_distance - (1.0 / 50.0)).abs() < 1e-6);
assert_eq!(p.viewport, [1280.0, 720.0]);
}
#[test]
fn params_floor_a_degenerate_viewport() {
let s = FogSettings::resolve([0.7; 3], 0.05, 0.2, 0.0, 50.0, 0.4, 0.15);
let p = s.params(IDENTITY, [0.0; 3], [0.0, 1.0, 0.0], [1.0; 3], [0.0, 0.0]);
assert!(p.viewport[0] >= MIN_VIEWPORT);
assert!(p.viewport[1] >= MIN_VIEWPORT);
}
#[test]
fn params_zero_padding_words_are_zero() {
let s = FogSettings::resolve([0.7; 3], 0.05, 0.2, 0.0, 50.0, 0.4, 0.15);
let p = s.params(IDENTITY, [0.0; 3], [0.0, 1.0, 0.0], [1.0; 3], [1.0, 1.0]);
assert_eq!(p._pad0, 0.0);
assert_eq!(p._pad1, 0.0);
assert_eq!(p._pad2, 0.0);
assert_eq!(p._pad3, [0.0; 3]);
}
}