1use core::fmt::Debug;
4use embedded_graphics_core::pixelcolor::{Rgb565, RgbColor};
5
6#[derive(Debug, Clone, Copy)]
8pub struct FogConfig {
9 pub color: Rgb565,
11 pub near: u32,
13 pub far: u32,
15}
16
17impl FogConfig {
18 pub fn new(color: Rgb565, near: f32, far: f32) -> Self {
20 Self {
21 color,
22 near: (near * 65536.0) as u32,
23 far: (far * 65536.0) as u32,
24 }
25 }
26
27 #[inline]
29 pub fn apply(&self, base_color: Rgb565, depth: u32) -> Rgb565 {
30 let fog_factor = if depth <= self.near {
31 0u32
32 } else if depth >= self.far {
33 65536u32
34 } else {
35 let numerator = (depth - self.near) as u64;
36 let denominator = (self.far - self.near) as u64;
37 ((numerator * 65536) / denominator) as u32
38 };
39
40 let base_r = base_color.r() as u32;
41 let base_g = base_color.g() as u32;
42 let base_b = base_color.b() as u32;
43
44 let fog_r = self.color.r() as u32;
45 let fog_g = self.color.g() as u32;
46 let fog_b = self.color.b() as u32;
47
48 let r = ((base_r * (65536 - fog_factor) + fog_r * fog_factor) / 65536) as u8;
49 let g = ((base_g * (65536 - fog_factor) + fog_g * fog_factor) / 65536) as u8;
50 let b = ((base_b * (65536 - fog_factor) + fog_b * fog_factor) / 65536) as u8;
51
52 Rgb565::new(r, g, b)
53 }
54}
55
56#[derive(Debug, Clone, Copy)]
58pub struct DitherConfig {
59 pub intensity: u8,
61}
62
63impl DitherConfig {
64 const BAYER_MATRIX: [[u8; 4]; 4] =
66 [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];
67
68 pub fn new(intensity: u8) -> Self {
70 Self { intensity }
71 }
72
73 #[inline]
75 pub fn apply(&self, color: Rgb565, x: i32, y: i32) -> Rgb565 {
76 if self.intensity == 0 {
77 return color;
78 }
79
80 let matrix_x = (x & 3) as usize;
81 let matrix_y = (y & 3) as usize;
82 let threshold = Self::BAYER_MATRIX[matrix_y][matrix_x];
83
84 let scaled_threshold = ((threshold as u16 * self.intensity as u16) / 15) as u8;
85
86 let r = color.r();
87 let g = color.g();
88 let b = color.b();
89
90 let r = if r > scaled_threshold {
91 r.saturating_sub(scaled_threshold / 2)
92 } else {
93 r.saturating_add(scaled_threshold / 2)
94 };
95
96 let g = if g > scaled_threshold {
97 g.saturating_sub(scaled_threshold / 2)
98 } else {
99 g.saturating_add(scaled_threshold / 2)
100 };
101
102 let b = if b > scaled_threshold {
103 b.saturating_sub(scaled_threshold / 2)
104 } else {
105 b.saturating_add(scaled_threshold / 2)
106 };
107
108 Rgb565::new(r, g, b)
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
114pub enum DepthInterpolationMode {
115 #[default]
117 Exact,
118 FastAverage,
120 FastMax,
122}
123
124impl DepthInterpolationMode {
125 #[inline(always)]
127 pub fn process_depths(&self, z1: f32, z2: f32, z3: f32) -> (f32, f32, f32) {
128 match self {
129 Self::Exact => (z1, z2, z3),
130 Self::FastAverage => {
131 let avg = (z1 + z2 + z3) * (1.0 / 3.0);
132 (avg, avg, avg)
133 }
134 Self::FastMax => {
135 let max_z = if z1 >= z2 && z1 >= z3 {
136 z1
137 } else if z2 >= z3 {
138 z2
139 } else {
140 z3
141 };
142 (max_z, max_z, max_z)
143 }
144 }
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
150pub enum InterlaceField {
151 #[default]
153 Progressive,
154 Even,
156 Odd,
158}
159
160impl InterlaceField {
161 #[inline(always)]
163 pub fn includes_scanline(&self, y: i32) -> bool {
164 match self {
165 Self::Progressive => true,
166 Self::Even => (y & 1) == 0,
167 Self::Odd => (y & 1) != 0,
168 }
169 }
170
171 #[inline]
173 pub fn toggle(&self) -> Self {
174 match self {
175 Self::Progressive => Self::Progressive,
176 Self::Even => Self::Odd,
177 Self::Odd => Self::Even,
178 }
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
188pub struct ScreenDoorConfig {
189 pub alpha: u8,
191}
192
193impl ScreenDoorConfig {
194 const BAYER_THRESHOLD: [[u8; 4]; 4] = [
195 [0, 128, 32, 160],
196 [192, 64, 224, 96],
197 [48, 176, 16, 144],
198 [240, 112, 208, 80],
199 ];
200
201 #[inline(always)]
202 pub const fn new(alpha: u8) -> Self {
203 Self { alpha }
204 }
205
206 #[inline(always)]
208 pub fn test(&self, x: i32, y: i32) -> bool {
209 if self.alpha == 255 {
210 return true;
211 }
212 if self.alpha == 0 {
213 return false;
214 }
215 let mx = (x & 3) as usize;
216 let my = (y & 3) as usize;
217 self.alpha > Self::BAYER_THRESHOLD[my][mx]
218 }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
223pub enum CheckerboardField {
224 #[default]
226 Disabled,
227 Even,
229 Odd,
231}
232
233impl CheckerboardField {
234 #[inline(always)]
236 pub fn includes_pixel(&self, x: i32, y: i32) -> bool {
237 match self {
238 Self::Disabled => true,
239 Self::Even => ((x ^ y) & 1) == 0,
240 Self::Odd => ((x ^ y) & 1) != 0,
241 }
242 }
243
244 #[inline]
246 pub fn toggle(&self) -> Self {
247 match self {
248 Self::Disabled => Self::Disabled,
249 Self::Even => Self::Odd,
250 Self::Odd => Self::Even,
251 }
252 }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Default)]
267pub struct DepthBias {
268 pub constant: f32,
270 pub slope_scale: f32,
272}
273
274impl DepthBias {
275 pub const ZERO: Self = Self {
277 constant: 0.0,
278 slope_scale: 0.0,
279 };
280
281 #[inline(always)]
283 pub const fn new(constant: f32, slope_scale: f32) -> Self {
284 Self {
285 constant,
286 slope_scale,
287 }
288 }
289
290 #[inline(always)]
293 pub const fn decal() -> Self {
294 Self {
295 constant: -0.005,
296 slope_scale: -0.01,
297 }
298 }
299
300 #[inline(always)]
302 pub const fn shadow() -> Self {
303 Self {
304 constant: -0.002,
305 slope_scale: -0.005,
306 }
307 }
308
309 #[inline]
311 pub fn compute_offset(
312 &self,
313 p1: nalgebra::Point2<i32>,
314 p2: nalgebra::Point2<i32>,
315 p3: nalgebra::Point2<i32>,
316 z1: f32,
317 z2: f32,
318 z3: f32,
319 ) -> f32 {
320 if self.constant == 0.0 && self.slope_scale == 0.0 {
321 return 0.0;
322 }
323 let dx1 = (p2.x - p1.x) as f32;
324 let dy1 = (p2.y - p1.y) as f32;
325 let dz1 = z2 - z1;
326
327 let dx2 = (p3.x - p1.x) as f32;
328 let dy2 = (p3.y - p1.y) as f32;
329 let dz2 = z3 - z1;
330
331 let det = dx1 * dy2 - dx2 * dy1;
332 let slope = if det.abs() > 1e-6 {
333 let dzdx = ((dz1 * dy2 - dz2 * dy1) / det).abs();
334 let dzdy = ((dx1 * dz2 - dx2 * dz1) / det).abs();
335 if dzdx > dzdy { dzdx } else { dzdy }
336 } else {
337 0.0
338 };
339
340 self.constant + self.slope_scale * slope
341 }
342
343 #[inline]
345 pub fn apply(
346 &self,
347 p1: nalgebra::Point2<i32>,
348 p2: nalgebra::Point2<i32>,
349 p3: nalgebra::Point2<i32>,
350 z1: f32,
351 z2: f32,
352 z3: f32,
353 ) -> (f32, f32, f32) {
354 let offset = self.compute_offset(p1, p2, p3, z1, z2, z3);
355 (
356 (z1 + offset).max(0.0),
357 (z2 + offset).max(0.0),
358 (z3 + offset).max(0.0),
359 )
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_screen_door_alpha() {
369 let full = ScreenDoorConfig::new(255);
370 assert!(full.test(0, 0));
371 assert!(full.test(1, 2));
372
373 let none = ScreenDoorConfig::new(0);
374 assert!(!none.test(0, 0));
375 assert!(!none.test(1, 2));
376
377 let half = ScreenDoorConfig::new(128);
378 let mut passed = 0;
380 for y in 0..4 {
381 for x in 0..4 {
382 if half.test(x, y) {
383 passed += 1;
384 }
385 }
386 }
387 assert_eq!(passed, 8);
388 }
389
390 #[test]
391 fn test_checkerboard_field() {
392 let even = CheckerboardField::Even;
393 assert!(even.includes_pixel(0, 0));
394 assert!(!even.includes_pixel(1, 0));
395 assert!(even.includes_pixel(1, 1));
396 assert_eq!(even.toggle(), CheckerboardField::Odd);
397 }
398
399 #[test]
400 fn test_depth_bias() {
401 let p1 = nalgebra::Point2::new(0, 0);
402 let p2 = nalgebra::Point2::new(10, 0);
403 let p3 = nalgebra::Point2::new(0, 10);
404
405 let bias = DepthBias::new(-0.01, -0.05);
407 let (z1, z2, z3) = bias.apply(p1, p2, p3, 5.0, 5.0, 5.0);
408 assert!((z1 - 4.99).abs() < 1e-4);
409 assert!((z2 - 4.99).abs() < 1e-4);
410 assert!((z3 - 4.99).abs() < 1e-4);
411
412 let offset = bias.compute_offset(p1, p2, p3, 0.0, 10.0, 0.0);
414 assert!((offset - (-0.06)).abs() < 1e-4);
416 }
417
418 #[test]
419 fn test_fog_and_dither_configs() {
420 let fog = FogConfig::new(Rgb565::WHITE, 1.0, 3.0);
421 assert_eq!(fog.apply(Rgb565::BLACK, 0), Rgb565::BLACK);
422 assert_eq!(fog.apply(Rgb565::BLACK, fog.far), Rgb565::WHITE);
423 let mid = fog.apply(Rgb565::BLACK, (fog.near + fog.far) / 2);
424 assert!(mid.r() > 5 && mid.r() < 31);
425
426 let zero = DitherConfig::new(0);
427 let color = Rgb565::new(20, 30, 20);
428 assert_eq!(zero.apply(color, 0, 0), color);
429 let heavy = DitherConfig::new(255);
430 assert_ne!(heavy.apply(color, 3, 3), color);
431 }
432
433 #[test]
434 fn test_depth_interpolation_and_interlace() {
435 let (a, b, c) = DepthInterpolationMode::Exact.process_depths(1.0, 2.0, 3.0);
436 assert_eq!((a, b, c), (1.0, 2.0, 3.0));
437 let (a, b, c) = DepthInterpolationMode::FastAverage.process_depths(1.0, 2.0, 3.0);
438 assert!((a - 2.0).abs() < 1e-5);
439 assert_eq!(a, b);
440 assert_eq!(b, c);
441 let (a, b, c) = DepthInterpolationMode::FastMax.process_depths(1.0, 3.0, 2.0);
442 assert_eq!((a, b, c), (3.0, 3.0, 3.0));
443
444 assert!(InterlaceField::Progressive.includes_scanline(0));
445 assert!(InterlaceField::Even.includes_scanline(2));
446 assert!(!InterlaceField::Even.includes_scanline(1));
447 assert!(InterlaceField::Odd.includes_scanline(1));
448 assert_eq!(InterlaceField::Even.toggle(), InterlaceField::Odd);
449 assert_eq!(
450 InterlaceField::Progressive.toggle(),
451 InterlaceField::Progressive
452 );
453 }
454}