1use crate::render_types::FogParams;
6
7const MAX_DENSITY: f32 = 10.0;
12const MAX_HEIGHT_FALLOFF: f32 = 4.0;
16const MAX_DISTANCE_CAP: f32 = 2_000.0;
21const MIN_DISTANCE: f32 = 1.0;
24const MIN_VIEWPORT: f32 = 1.0;
27
28#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct FogSettings {
33 pub color: [f32; 3],
35 pub density: f32,
37 pub height_falloff: f32,
39 pub height_reference: f32,
41 pub max_distance: f32,
43 pub phase_g: f32,
45 pub ambient: f32,
47}
48
49impl FogSettings {
50 pub fn resolve(
53 color: [f32; 3],
54 density: f32,
55 height_falloff: f32,
56 height_reference: f32,
57 max_distance: f32,
58 phase_g: f32,
59 ambient: f32,
60 ) -> Self {
61 let max_distance = if max_distance.is_finite() {
62 max_distance.clamp(MIN_DISTANCE, MAX_DISTANCE_CAP)
63 } else {
64 MIN_DISTANCE
65 };
66 let color = [color[0].max(0.0), color[1].max(0.0), color[2].max(0.0)];
67 Self {
68 color,
69 density: density.clamp(0.0, MAX_DENSITY),
70 height_falloff: height_falloff.clamp(0.0, MAX_HEIGHT_FALLOFF),
71 height_reference,
72 max_distance,
73 phase_g: phase_g.clamp(-0.95, 0.95),
76 ambient: ambient.clamp(0.0, MAX_DENSITY),
77 }
78 }
79
80 pub fn params(
87 &self,
88 inv_vp: [[f32; 4]; 4],
89 cam_pos: [f32; 3],
90 sun_dir: [f32; 3],
91 sun_color: [f32; 3],
92 viewport: [f32; 2],
93 ) -> FogParams {
94 let viewport = [viewport[0].max(MIN_VIEWPORT), viewport[1].max(MIN_VIEWPORT)];
95 FogParams {
96 inv_vp,
97 color: [self.color[0], self.color[1], self.color[2], 1.0],
98 cam_pos,
99 _pad0: 0.0,
100 sun_dir,
101 _pad1: 0.0,
102 sun_color,
103 _pad2: 0.0,
104 density: self.density,
105 height_falloff: self.height_falloff,
106 height_reference: self.height_reference,
107 max_distance: self.max_distance,
108 phase_g: self.phase_g,
109 ambient: self.ambient,
110 viewport,
111 inv_max_distance: 1.0 / self.max_distance,
112 _pad3: [0.0; 3],
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 const IDENTITY: [[f32; 4]; 4] = [
122 [1.0, 0.0, 0.0, 0.0],
123 [0.0, 1.0, 0.0, 0.0],
124 [0.0, 0.0, 1.0, 0.0],
125 [0.0, 0.0, 0.0, 1.0],
126 ];
127
128 #[test]
129 fn resolve_clamps_density_falloff_and_distance() {
130 let s = FogSettings::resolve([1.0, 1.0, 1.0], 100.0, 20.0, 0.0, 1e9, 1.5, -1.0);
131 assert_eq!(s.density, MAX_DENSITY);
132 assert_eq!(s.height_falloff, MAX_HEIGHT_FALLOFF);
133 assert_eq!(s.max_distance, MAX_DISTANCE_CAP);
134 assert!(s.phase_g <= 0.95 && s.phase_g > 0.0);
135 assert_eq!(s.ambient, 0.0);
136 }
137
138 #[test]
139 fn resolve_passes_through_in_range_values() {
140 let s = FogSettings::resolve([0.6, 0.7, 0.8], 0.08, 0.25, 1.5, 120.0, 0.4, 0.2);
141 assert_eq!(s.color, [0.6, 0.7, 0.8]);
142 assert!((s.density - 0.08).abs() < 1e-6);
143 assert!((s.phase_g - 0.4).abs() < 1e-6);
144 assert!((s.max_distance - 120.0).abs() < 1e-6);
145 }
146
147 #[test]
148 fn resolve_handles_non_finite_distance() {
149 let s = FogSettings::resolve([0.6; 3], 0.05, 0.2, 0.0, f32::NAN, 0.4, 0.15);
150 assert!(s.max_distance.is_finite());
151 assert!(s.max_distance >= MIN_DISTANCE);
152 }
153
154 #[test]
155 fn params_derive_inverse_max_distance() {
156 let s = FogSettings::resolve([0.7; 3], 0.05, 0.2, 0.0, 50.0, 0.4, 0.15);
157 let p = s.params(
158 IDENTITY,
159 [0.0; 3],
160 [0.0, 1.0, 0.0],
161 [1.0; 3],
162 [1280.0, 720.0],
163 );
164 assert!((p.inv_max_distance - (1.0 / 50.0)).abs() < 1e-6);
165 assert_eq!(p.viewport, [1280.0, 720.0]);
166 }
167
168 #[test]
169 fn params_floor_a_degenerate_viewport() {
170 let s = FogSettings::resolve([0.7; 3], 0.05, 0.2, 0.0, 50.0, 0.4, 0.15);
171 let p = s.params(IDENTITY, [0.0; 3], [0.0, 1.0, 0.0], [1.0; 3], [0.0, 0.0]);
172 assert!(p.viewport[0] >= MIN_VIEWPORT);
173 assert!(p.viewport[1] >= MIN_VIEWPORT);
174 }
175
176 #[test]
177 fn params_zero_padding_words_are_zero() {
178 let s = FogSettings::resolve([0.7; 3], 0.05, 0.2, 0.0, 50.0, 0.4, 0.15);
179 let p = s.params(IDENTITY, [0.0; 3], [0.0, 1.0, 0.0], [1.0; 3], [1.0, 1.0]);
180 assert_eq!(p._pad0, 0.0);
181 assert_eq!(p._pad1, 0.0);
182 assert_eq!(p._pad2, 0.0);
183 assert_eq!(p._pad3, [0.0; 3]);
184 }
185}