1#[cfg(feature = "row_width_320")]
3const MAX_ROW_WIDTH: usize = 320;
4#[cfg(all(feature = "row_width_240", not(feature = "row_width_320")))]
5const MAX_ROW_WIDTH: usize = 240;
6#[cfg(all(
7 feature = "row_width_160",
8 not(feature = "row_width_240"),
9 not(feature = "row_width_320"),
10 not(feature = "row_width_96")
11))]
12const MAX_ROW_WIDTH: usize = 160;
13#[cfg(all(
14 feature = "row_width_96",
15 not(feature = "row_width_160"),
16 not(feature = "row_width_240"),
17 not(feature = "row_width_320")
18))]
19const MAX_ROW_WIDTH: usize = 96;
20#[cfg(not(any(
21 feature = "row_width_320",
22 feature = "row_width_240",
23 feature = "row_width_160",
24 feature = "row_width_96"
25)))]
26const MAX_ROW_WIDTH: usize = 100;
27
28use core::fmt::Debug;
29use embedded_graphics_core::draw_target::DrawTarget;
30use embedded_graphics_core::pixelcolor::Rgb565;
31use embedded_graphics_core::pixelcolor::RgbColor;
32use embedded_graphics_core::prelude::Point;
33use heapless::Vec;
34
35use crate::DrawPrimitive;
36use crate::retro::{PaletteMode, ScreenTint, StippleMode, TextureMapping};
37
38#[cfg(feature = "aa")]
46pub trait ReadPixel {
47 fn read_pixel(&self, point: Point) -> Rgb565;
48}
49
50#[inline(always)]
54pub fn fast_blend_rgb565(bg: Rgb565, fg: Rgb565, alpha: u8) -> Rgb565 {
55 if alpha == 255 {
56 return fg;
57 }
58 if alpha == 0 {
59 return bg;
60 }
61 let a = alpha as u32;
62 let inv = 255 - a;
63 let r = (bg.r() as u32 * inv + fg.r() as u32 * a) / 255;
64 let g = (bg.g() as u32 * inv + fg.g() as u32 * a) / 255;
65 let b = (bg.b() as u32 * inv + fg.b() as u32 * a) / 255;
66 Rgb565::new(r as u8, g as u8, b as u8)
67}
68
69#[inline(always)]
73pub fn fast_blend_rgba8888(bg: [u8; 4], fg: [u8; 4]) -> [u8; 4] {
74 let a = fg[3] as u32;
75 if a == 255 {
76 return fg;
77 }
78 if a == 0 {
79 return bg;
80 }
81 let inv = 255 - a;
82 let r = (bg[0] as u32 * inv + fg[0] as u32 * a) / 255;
83 let g = (bg[1] as u32 * inv + fg[1] as u32 * a) / 255;
84 let b = (bg[2] as u32 * inv + fg[2] as u32 * a) / 255;
85 let out_a = fg[3] as u32 + (bg[3] as u32 * inv) / 255;
86 [r as u8, g as u8, b as u8, out_a as u8]
87}
88
89#[inline(always)]
93pub fn fast_blend_rgba8888_to_rgb565(bg: Rgb565, fg_rgba: [u8; 4]) -> Rgb565 {
94 let alpha = fg_rgba[3];
95 if alpha == 0 {
96 return bg;
97 }
98 let fg_r = fg_rgba[0] >> 3;
99 let fg_g = fg_rgba[1] >> 2;
100 let fg_b = fg_rgba[2] >> 3;
101 let fg_565 = Rgb565::new(fg_r, fg_g, fg_b);
102 fast_blend_rgb565(bg, fg_565, alpha)
103}
104
105#[inline(always)]
109pub fn reverse_color_rgb565(c: Rgb565) -> Rgb565 {
110 Rgb565::new(31 - c.r(), 63 - c.g(), 31 - c.b())
111}
112
113#[inline(always)]
117pub fn reverse_color_rgba8888(rgba: [u8; 4]) -> [u8; 4] {
118 [255 - rgba[0], 255 - rgba[1], 255 - rgba[2], rgba[3]]
119}
120
121#[cfg(feature = "aa")]
124#[inline(always)]
125fn blend_q8(bg: Rgb565, fg: Rgb565, coverage_q8: u32) -> Rgb565 {
126 let inv = 256 - coverage_q8;
127 let r = (bg.r() as u32 * inv + fg.r() as u32 * coverage_q8) >> 8;
128 let g = (bg.g() as u32 * inv + fg.g() as u32 * coverage_q8) >> 8;
129 let b = (bg.b() as u32 * inv + fg.b() as u32 * coverage_q8) >> 8;
130 Rgb565::new(r as u8, g as u8, b as u8)
131}
132
133#[cfg(feature = "aa-heuristic")]
145#[inline(always)]
146fn aa_pixel<D>(
147 fb: &mut D,
148 x: i32,
149 y: i32,
150 color: Rgb565,
151 z: u32,
152 zbuffer: &mut [u32],
153 width: usize,
154 coverage_q8: u32,
155) where
156 D: DrawTarget<Color = Rgb565> + ReadPixel,
157 <D as DrawTarget>::Error: Debug,
158{
159 if x < 0 || y < 0 || x >= width as i32 || coverage_q8 == 0 {
160 return;
161 }
162 let idx = y as usize * width + x as usize;
163 if idx >= zbuffer.len() {
164 return;
165 }
166 if z >= zbuffer[idx].saturating_add(DEPTH_EPSILON) {
167 return;
168 }
169
170 let pixel_was_virgin = zbuffer[idx] == u32::MAX;
171 let final_color = if coverage_q8 >= 256 || !pixel_was_virgin {
172 color
173 } else {
174 let bg = fb.read_pixel(Point::new(x, y));
175 blend_q8(bg, color, coverage_q8)
176 };
177 zbuffer[idx] = z;
178 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_color)])
179 .unwrap();
180}
181
182const DEPTH_EPSILON: u32 = 128;
198
199#[derive(Debug, Clone, Copy)]
201pub struct FogConfig {
202 pub color: embedded_graphics_core::pixelcolor::Rgb565,
204 pub near: u32,
206 pub far: u32,
208}
209
210impl FogConfig {
211 pub fn new(color: embedded_graphics_core::pixelcolor::Rgb565, near: f32, far: f32) -> Self {
218 Self {
219 color,
220 near: (near * 65536.0) as u32,
221 far: (far * 65536.0) as u32,
222 }
223 }
224
225 #[inline]
227 pub fn apply(
228 &self,
229 base_color: embedded_graphics_core::pixelcolor::Rgb565,
230 depth: u32,
231 ) -> embedded_graphics_core::pixelcolor::Rgb565 {
232 let fog_factor = if depth <= self.near {
234 0u32
235 } else if depth >= self.far {
236 65536u32 } else {
238 let numerator = (depth - self.near) as u64;
240 let denominator = (self.far - self.near) as u64;
241 ((numerator * 65536) / denominator) as u32
242 };
243
244 let base_r = base_color.r() as u32;
247 let base_g = base_color.g() as u32;
248 let base_b = base_color.b() as u32;
249
250 let fog_r = self.color.r() as u32;
251 let fog_g = self.color.g() as u32;
252 let fog_b = self.color.b() as u32;
253
254 let r = ((base_r * (65536 - fog_factor) + fog_r * fog_factor) / 65536) as u8;
256 let g = ((base_g * (65536 - fog_factor) + fog_g * fog_factor) / 65536) as u8;
257 let b = ((base_b * (65536 - fog_factor) + fog_b * fog_factor) / 65536) as u8;
258
259 embedded_graphics_core::pixelcolor::Rgb565::new(r, g, b)
260 }
261}
262
263#[derive(Debug, Clone, Copy)]
265pub struct DitherConfig {
266 pub intensity: u8,
268}
269
270impl DitherConfig {
271 const BAYER_MATRIX: [[u8; 4]; 4] =
274 [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];
275
276 pub fn new(intensity: u8) -> Self {
278 Self { intensity }
279 }
280
281 #[inline]
283 pub fn apply(
284 &self,
285 color: embedded_graphics_core::pixelcolor::Rgb565,
286 x: i32,
287 y: i32,
288 ) -> embedded_graphics_core::pixelcolor::Rgb565 {
289 if self.intensity == 0 {
290 return color;
291 }
292
293 let matrix_x = (x & 3) as usize;
295 let matrix_y = (y & 3) as usize;
296 let threshold = Self::BAYER_MATRIX[matrix_y][matrix_x];
297
298 let scaled_threshold = ((threshold as u16 * self.intensity as u16) / 15) as u8;
302
303 let r = color.r();
305 let g = color.g();
306 let b = color.b();
307
308 let r = if r > scaled_threshold {
310 r.saturating_sub(scaled_threshold / 2)
311 } else {
312 r.saturating_add(scaled_threshold / 2)
313 };
314
315 let g = if g > scaled_threshold {
316 g.saturating_sub(scaled_threshold / 2)
317 } else {
318 g.saturating_add(scaled_threshold / 2)
319 };
320
321 let b = if b > scaled_threshold {
322 b.saturating_sub(scaled_threshold / 2)
323 } else {
324 b.saturating_add(scaled_threshold / 2)
325 };
326
327 embedded_graphics_core::pixelcolor::Rgb565::new(r, g, b)
328 }
329}
330
331const FP_SHIFT: i64 = 16;
333
334#[inline(always)]
335fn fixed_to_i32(value: i64) -> i32 {
336 if value >= 0 {
337 (value >> FP_SHIFT) as i32
338 } else {
339 -((-value) >> FP_SHIFT) as i32
340 }
341}
342
343struct EdgeStepper {
344 x: i64,
345 step: i64,
346}
347
348impl EdgeStepper {
349 fn new(start: Point, end: Point, y: i32) -> Self {
350 let dy = (end.y - start.y) as i64;
351 let (step, x) = if dy != 0 {
352 let s = (((end.x - start.x) as i64) << FP_SHIFT) / dy;
353 let x = ((start.x as i64) << FP_SHIFT) + s * (y - start.y) as i64;
354 (s, x)
355 } else {
356 (0, (start.x as i64) << FP_SHIFT)
357 };
358 Self { x, step }
359 }
360
361 #[inline(always)]
362 fn current_x(&self) -> i32 {
363 fixed_to_i32(self.x)
364 }
365
366 #[inline(always)]
367 fn advance(&mut self) {
368 self.x += self.step;
369 }
370}
371
372#[inline(always)]
373fn fill_triangle<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
374 p1: Point,
375 p2: Point,
376 p3: Point,
377 color: embedded_graphics_core::pixelcolor::Rgb565,
378 fb: &mut D,
379) where
380 <D as DrawTarget>::Error: Debug,
381{
382 let area = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
383 if area == 0 {
384 return;
386 }
387
388 let bounds = fb.bounding_box();
389 let min_x = bounds.top_left.x;
390 let max_x = bounds.bottom_right().unwrap().x;
391
392 let mut pixel_row: [embedded_graphics_core::Pixel<embedded_graphics_core::pixelcolor::Rgb565>;
393 MAX_ROW_WIDTH] = [embedded_graphics_core::Pixel(
394 Point::new(0, 0),
395 embedded_graphics_core::pixelcolor::RgbColor::BLACK,
396 ); MAX_ROW_WIDTH];
397
398 if p2.y - p1.y > 0 {
400 let mut a = EdgeStepper::new(p1, p2, p1.y);
401 let mut b = EdgeStepper::new(p1, p3, p1.y);
402
403 for y in p1.y..p2.y {
404 let ax = a.current_x();
405 let bx = b.current_x();
406 let (start_x, end_x) = if ax < bx { (ax, bx) } else { (bx, ax) };
407 let start_x = start_x.clamp(min_x, max_x);
408 let end_x = end_x.clamp(min_x, max_x);
409 let mut x = start_x;
410 while x <= end_x {
411 let chunk_end = (x + MAX_ROW_WIDTH as i32 - 1).min(end_x);
412 let mut i = 0usize;
413 for sx in x..=chunk_end {
414 pixel_row[i] = embedded_graphics_core::Pixel(Point::new(sx, y), color);
415 i += 1;
416 }
417 fb.draw_iter(pixel_row[..i].iter().copied()).unwrap();
418 x = chunk_end + 1;
419 }
420 a.advance();
421 b.advance();
422 }
423 }
424
425 if p3.y - p2.y > 0 {
427 let mut a = EdgeStepper::new(p2, p3, p2.y);
428 let mut b = EdgeStepper::new(p1, p3, p2.y);
429
430 for y in p2.y..=p3.y {
431 let ax = a.current_x();
432 let bx = b.current_x();
433 let (start_x, end_x) = if ax < bx { (ax, bx) } else { (bx, ax) };
434 let start_x = start_x.clamp(min_x, max_x);
435 let end_x = end_x.clamp(min_x, max_x);
436 let mut x = start_x;
437 while x <= end_x {
438 let chunk_end = (x + MAX_ROW_WIDTH as i32 - 1).min(end_x);
439 let mut i = 0usize;
440 for sx in x..=chunk_end {
441 pixel_row[i] = embedded_graphics_core::Pixel(Point::new(sx, y), color);
442 i += 1;
443 }
444 fb.draw_iter(pixel_row[..i].iter().copied()).unwrap();
445 x = chunk_end + 1;
446 }
447 a.advance();
448 b.advance();
449 }
450 }
451}
452
453#[allow(dead_code)]
454fn fill_bottom_flat_triangle<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
455 p1: Point,
456 p2: Point,
457 p3: Point,
458 color: embedded_graphics_core::pixelcolor::Rgb565,
459 fb: &mut D,
460) where
461 <D as DrawTarget>::Error: Debug,
462{
463 let mut edge1 = EdgeStepper::new(p1, p2, p1.y);
464 let mut edge2 = EdgeStepper::new(p1, p3, p1.y);
465
466 for scanline_y in p1.y..=p2.y {
467 draw_horizontal_line(
468 Point::new(edge1.current_x(), scanline_y),
469 Point::new(edge2.current_x(), scanline_y),
470 color,
471 fb,
472 );
473 edge1.advance();
474 edge2.advance();
475 }
476}
477
478#[allow(dead_code)]
479fn fill_top_flat_triangle<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
480 p1: Point,
481 p2: Point,
482 p3: Point,
483 color: embedded_graphics_core::pixelcolor::Rgb565,
484 fb: &mut D,
485) where
486 <D as DrawTarget>::Error: Debug,
487{
488 let mut edge1 = EdgeStepper::new(p1, p3, p1.y);
490 let mut edge2 = EdgeStepper::new(p2, p3, p1.y);
491
492 for scanline_y in p1.y..=p3.y {
493 draw_horizontal_line(
494 Point::new(edge1.current_x(), scanline_y),
495 Point::new(edge2.current_x(), scanline_y),
496 color,
497 fb,
498 );
499 edge1.advance();
500 edge2.advance();
501 }
502}
503
504#[allow(dead_code)]
505fn draw_horizontal_line<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
506 p1: Point,
507 p2: Point,
508 color: embedded_graphics_core::pixelcolor::Rgb565,
509 fb: &mut D,
510) where
511 <D as DrawTarget>::Error: Debug,
512{
513 let start = p1.x.min(p2.x);
514 let end = p1.x.max(p2.x);
515
516 for x in start..=end {
517 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, p1.y), color)])
518 .unwrap();
519 }
520}
521
522#[derive(Clone, Copy, Default)]
523struct ScreenVert {
524 x: f32,
525 y: f32,
526}
527
528#[inline]
529fn clip_polygon_plane_2d(
530 input: &[ScreenVert],
531 output: &mut [ScreenVert; 8],
532 dist: impl Fn(ScreenVert) -> f32,
533) -> usize {
534 let n = input.len();
535 let mut m = 0usize;
536 for i in 0..n {
537 let prev = input[(n + i - 1) % n];
538 let curr = input[i];
539 let d_prev = dist(prev);
540 let d_curr = dist(curr);
541 if d_curr >= 0.0 {
542 if d_prev < 0.0 {
543 let t = d_prev / (d_prev - d_curr);
544 if m < 8 {
545 output[m] = ScreenVert {
546 x: prev.x + (curr.x - prev.x) * t,
547 y: prev.y + (curr.y - prev.y) * t,
548 };
549 m += 1;
550 }
551 }
552 if m < 8 {
553 output[m] = curr;
554 m += 1;
555 }
556 } else if d_prev >= 0.0 {
557 let t = d_prev / (d_prev - d_curr);
558 if m < 8 {
559 output[m] = ScreenVert {
560 x: prev.x + (curr.x - prev.x) * t,
561 y: prev.y + (curr.y - prev.y) * t,
562 };
563 m += 1;
564 }
565 }
566 }
567 m
568}
569
570#[inline]
571fn tri_area2(a: Point, b: Point, c: Point) -> i32 {
572 (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
573}
574
575#[inline]
576fn round_to_i32(v: f32) -> i32 {
577 if v >= 0.0 {
578 (v + 0.5) as i32
579 } else {
580 (v - 0.5) as i32
581 }
582}
583
584#[inline]
585fn fill_triangle_screen_clipped<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
586 p1: Point,
587 p2: Point,
588 p3: Point,
589 color: embedded_graphics_core::pixelcolor::Rgb565,
590 fb: &mut D,
591) where
592 <D as DrawTarget>::Error: Debug,
593{
594 let bounds = fb.bounding_box();
595 let max_x = bounds.size.width.saturating_sub(1) as f32;
596 let max_y = bounds.size.height.saturating_sub(1) as f32;
597 if max_x < 0.0 || max_y < 0.0 {
598 return;
599 }
600
601 let mut a = [ScreenVert::default(); 8];
602 let mut b = [ScreenVert::default(); 8];
603 a[0] = ScreenVert {
604 x: p1.x as f32,
605 y: p1.y as f32,
606 };
607 a[1] = ScreenVert {
608 x: p2.x as f32,
609 y: p2.y as f32,
610 };
611 a[2] = ScreenVert {
612 x: p3.x as f32,
613 y: p3.y as f32,
614 };
615
616 let n = clip_polygon_plane_2d(&a[..3], &mut b, |v| v.x); if n < 3 {
618 return;
619 }
620 let n = clip_polygon_plane_2d(&b[..n], &mut a, |v| max_x - v.x); if n < 3 {
622 return;
623 }
624 let n = clip_polygon_plane_2d(&a[..n], &mut b, |v| v.y); if n < 3 {
626 return;
627 }
628 let n = clip_polygon_plane_2d(&b[..n], &mut a, |v| max_y - v.y); if n < 3 {
630 return;
631 }
632
633 for i in 1..n - 1 {
634 let t0 = Point::new(round_to_i32(a[0].x), round_to_i32(a[0].y));
635 let t1 = Point::new(round_to_i32(a[i].x), round_to_i32(a[i].y));
636 let t2 = Point::new(round_to_i32(a[i + 1].x), round_to_i32(a[i + 1].y));
637 if tri_area2(t0, t1, t2) == 0 {
638 continue;
639 }
640 fill_triangle(t0, t1, t2, color, fb);
641 }
642}
643
644#[inline]
645pub fn draw<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
646 primitive: DrawPrimitive,
647 fb: &mut D,
648) where
649 <D as DrawTarget>::Error: Debug,
650{
651 match primitive {
652 DrawPrimitive::Line([p1, p2], color) => {
653 fb.draw_iter(
654 line_drawing::Bresenham::new((p1.x, p1.y), (p2.x, p2.y))
655 .map(|(x, y)| embedded_graphics_core::Pixel(Point::new(x, y), color)),
656 )
657 .unwrap();
658 }
659 DrawPrimitive::ColoredPoint(p, c) => {
660 let p = embedded_graphics_core::geometry::Point::new(p.x, p.y);
661
662 fb.draw_iter([embedded_graphics_core::Pixel(p, c)]).unwrap();
663 }
664 DrawPrimitive::ColoredTriangle(mut vertices, color) => {
665 vertices
667 .as_mut_slice()
668 .sort_unstable_by(|a, b| a.y.cmp(&b.y));
669
670 let [p1, p2, p3] = [
671 Point::new(vertices[0].x, vertices[0].y),
672 Point::new(vertices[1].x, vertices[1].y),
673 Point::new(vertices[2].x, vertices[2].y),
674 ];
675 fill_triangle_screen_clipped(p1, p2, p3, color, fb);
676 }
677 DrawPrimitive::ColoredTriangleWithDepth {
678 points,
679 depths: _,
680 color,
681 }
682 | DrawPrimitive::TranslucentTriangleWithDepth {
683 points,
684 depths: _,
685 color,
686 alpha: _,
687 } => {
688 let mut vertices = points;
691 if vertices[0].y > vertices[1].y {
692 vertices.swap(0, 1);
693 }
694 if vertices[0].y > vertices[2].y {
695 vertices.swap(0, 2);
696 }
697 if vertices[1].y > vertices[2].y {
698 vertices.swap(1, 2);
699 }
700
701 let mut buf: Vec<_, 3> = Vec::new();
702 for p in vertices.iter() {
703 buf.push(embedded_graphics_core::geometry::Point::new(p.x, p.y))
704 .unwrap();
705 }
706 let [p1, p2, p3] = buf.into_array().unwrap();
707 fill_triangle_screen_clipped(p1, p2, p3, color, fb);
708 }
709 DrawPrimitive::GouraudTriangle {
710 mut points,
711 mut colors,
712 } => {
713 if points[0].y > points[1].y {
715 points.swap(0, 1);
716 colors.swap(0, 1);
717 }
718 if points[0].y > points[2].y {
719 points.swap(0, 2);
720 colors.swap(0, 2);
721 }
722 if points[1].y > points[2].y {
723 points.swap(1, 2);
724 colors.swap(1, 2);
725 }
726
727 let mut buf: Vec<_, 3> = Vec::new();
728 for p in points.iter() {
729 buf.push(embedded_graphics_core::geometry::Point::new(p.x, p.y))
730 .unwrap();
731 }
732 let [p1, p2, p3] = buf.into_array().unwrap();
733 let [c1, c2, c3] = colors;
734
735 let bounds = fb.bounding_box();
737 let scr_w = bounds.size.width as i32;
738 let scr_h = bounds.size.height as i32;
739 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
740 return;
741 }
742 if p1.x >= scr_w && p2.x >= scr_w && p3.x >= scr_w {
743 return;
744 }
745 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
746 return;
747 }
748 if p1.y >= scr_h && p2.y >= scr_h && p3.y >= scr_h {
749 return;
750 }
751
752 if p2.y == p3.y {
753 fill_bottom_flat_gouraud(p1, p2, p3, c1, c2, c3, fb);
754 } else if p1.y == p2.y {
755 fill_top_flat_gouraud(p1, p2, p3, c1, c2, c3, fb);
756 } else {
757 let t = (p2.y - p1.y) as f32 / (p3.y - p1.y) as f32;
759 let p4 = Point::new((p1.x as f32 + t * (p3.x - p1.x) as f32) as i32, p2.y);
760 let c4 = interpolate_color(c1, c3, t);
762
763 fill_bottom_flat_gouraud(p1, p2, p4, c1, c2, c4, fb);
764 fill_top_flat_gouraud(p2, p4, p3, c2, c4, c3, fb);
765 }
766 }
767 DrawPrimitive::GouraudTriangleWithDepth {
768 points,
769 depths: _,
770 colors,
771 } => {
772 let prim = DrawPrimitive::GouraudTriangle { points, colors };
775 draw(prim, fb);
776 }
777 DrawPrimitive::TexturedTriangle { .. }
778 | DrawPrimitive::TexturedTriangleWithDepth { .. }
779 | DrawPrimitive::LightmappedTriangle { .. } => {
780 }
783 }
784}
785
786#[inline]
788fn interpolate_color(
789 c1: embedded_graphics_core::pixelcolor::Rgb565,
790 c2: embedded_graphics_core::pixelcolor::Rgb565,
791 t: f32,
792) -> embedded_graphics_core::pixelcolor::Rgb565 {
793 let r1 = c1.r() as f32;
794 let g1 = c1.g() as f32;
795 let b1 = c1.b() as f32;
796
797 let r2 = c2.r() as f32;
798 let g2 = c2.g() as f32;
799 let b2 = c2.b() as f32;
800
801 let r = (r1 + t * (r2 - r1)) as u8;
802 let g = (g1 + t * (g2 - g1)) as u8;
803 let b = (b1 + t * (b2 - b1)) as u8;
804
805 embedded_graphics_core::pixelcolor::Rgb565::new(r, g, b)
806}
807
808fn fill_bottom_flat_gouraud<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
810 p1: Point,
811 p2: Point,
812 p3: Point,
813 c1: embedded_graphics_core::pixelcolor::Rgb565,
814 c2: embedded_graphics_core::pixelcolor::Rgb565,
815 c3: embedded_graphics_core::pixelcolor::Rgb565,
816 fb: &mut D,
817) where
818 <D as DrawTarget>::Error: Debug,
819{
820 let height = (p2.y - p1.y) as f32;
821 if height == 0.0 {
822 return;
823 }
824
825 let mut edge1 = EdgeStepper::new(p1, p2, p1.y);
826 let mut edge2 = EdgeStepper::new(p1, p3, p1.y);
827
828 for scanline_y in p1.y..=p2.y {
829 let t = (scanline_y - p1.y) as f32 / height;
830 let color_left = interpolate_color(c1, c2, t);
831 let color_right = interpolate_color(c1, c3, t);
832
833 draw_horizontal_line_gouraud(
834 Point::new(edge1.current_x(), scanline_y),
835 Point::new(edge2.current_x(), scanline_y),
836 color_left,
837 color_right,
838 fb,
839 );
840
841 edge1.advance();
842 edge2.advance();
843 }
844}
845
846fn fill_top_flat_gouraud<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
848 p1: Point,
849 p2: Point,
850 p3: Point,
851 c1: embedded_graphics_core::pixelcolor::Rgb565,
852 c2: embedded_graphics_core::pixelcolor::Rgb565,
853 c3: embedded_graphics_core::pixelcolor::Rgb565,
854 fb: &mut D,
855) where
856 <D as DrawTarget>::Error: Debug,
857{
858 let height = (p3.y - p1.y) as f32;
860 if height == 0.0 {
861 return;
862 }
863
864 let mut edge1 = EdgeStepper::new(p1, p3, p1.y);
865 let mut edge2 = EdgeStepper::new(p2, p3, p1.y);
866
867 for scanline_y in p1.y..=p3.y {
868 let t = (scanline_y - p1.y) as f32 / height;
869 let color_left = interpolate_color(c1, c3, t);
870 let color_right = interpolate_color(c2, c3, t);
871
872 draw_horizontal_line_gouraud(
873 Point::new(edge1.current_x(), scanline_y),
874 Point::new(edge2.current_x(), scanline_y),
875 color_left,
876 color_right,
877 fb,
878 );
879
880 edge1.advance();
881 edge2.advance();
882 }
883}
884
885fn draw_horizontal_line_gouraud<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
887 p1: Point,
888 p2: Point,
889 color1: embedded_graphics_core::pixelcolor::Rgb565,
890 color2: embedded_graphics_core::pixelcolor::Rgb565,
891 fb: &mut D,
892) where
893 <D as DrawTarget>::Error: Debug,
894{
895 let start = p1.x.min(p2.x);
896 let end = p1.x.max(p2.x);
897 let width = (end - start) as f32;
898
899 if width == 0.0 {
900 fb.draw_iter([embedded_graphics_core::Pixel(
901 Point::new(start, p1.y),
902 color1,
903 )])
904 .unwrap();
905 return;
906 }
907
908 for x in start..=end {
909 let t = (x - start) as f32 / width;
910 let color = interpolate_color(color1, color2, t);
911 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, p1.y), color)])
912 .unwrap();
913 }
914}
915
916#[inline]
919pub fn draw_zbuffered<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
920 primitive: DrawPrimitive,
921 fb: &mut D,
922 zbuffer: &mut [u32],
923 width: usize,
924) where
925 <D as DrawTarget>::Error: Debug,
926{
927 draw_zbuffered_with_effects(primitive, fb, zbuffer, width, None, None);
929}
930
931#[cfg(feature = "aa-heuristic")]
940#[inline]
941pub fn draw_zbuffered_aa<D>(primitive: DrawPrimitive, fb: &mut D, zbuffer: &mut [u32], width: usize)
942where
943 D: DrawTarget<Color = Rgb565> + ReadPixel,
944 <D as DrawTarget>::Error: Debug,
945{
946 match primitive {
947 DrawPrimitive::ColoredTriangleWithDepth {
948 mut points,
949 mut depths,
950 color,
951 } => {
952 if points[0].y > points[1].y {
953 points.swap(0, 1);
954 depths.swap(0, 1);
955 }
956 if points[0].y > points[2].y {
957 points.swap(0, 2);
958 depths.swap(0, 2);
959 }
960 if points[1].y > points[2].y {
961 points.swap(1, 2);
962 depths.swap(1, 2);
963 }
964 let [p1, p2, p3] = points;
965 let [z1, z2, z3] = depths;
966
967 let scr_w = width as i32;
969 let scr_h = (zbuffer.len() / width) as i32;
970 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
971 return;
972 }
973 if p1.x >= scr_w && p2.x >= scr_w && p3.x >= scr_w {
974 return;
975 }
976 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
977 return;
978 }
979 if p1.y >= scr_h && p2.y >= scr_h && p3.y >= scr_h {
980 return;
981 }
982
983 fill_triangle_zbuffered_aa(p1, p2, p3, z1, z2, z3, color, fb, zbuffer, width);
984 }
985 DrawPrimitive::Line([p1, p2], color) => {
986 draw_line_aa(p1.x, p1.y, p2.x, p2.y, color, fb);
987 }
988 other => draw_zbuffered(other, fb, zbuffer, width),
991 }
992}
993
994#[cfg(feature = "aa")]
996#[inline]
997pub fn draw_zbuffered_2xssaa<D>(
998 primitive: DrawPrimitive,
999 fb: &mut D,
1000 zbuffer: &mut [u32],
1001 width: usize,
1002) where
1003 D: DrawTarget<Color = Rgb565> + ReadPixel,
1004 <D as DrawTarget>::Error: Debug,
1005{
1006 match primitive {
1007 DrawPrimitive::ColoredTriangleWithDepth {
1008 mut points,
1009 mut depths,
1010 color,
1011 } => {
1012 if points[0].y > points[1].y {
1013 points.swap(0, 1);
1014 depths.swap(0, 1);
1015 }
1016 if points[0].y > points[2].y {
1017 points.swap(0, 2);
1018 depths.swap(0, 2);
1019 }
1020 if points[1].y > points[2].y {
1021 points.swap(1, 2);
1022 depths.swap(1, 2);
1023 }
1024 let [p1, p2, p3] = points;
1025 let [z1, z2, z3] = depths;
1026
1027 let scr_w = width as i32;
1028 let scr_h = (zbuffer.len() / width) as i32;
1029 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
1030 return;
1031 }
1032 if p1.x >= scr_w && p2.x >= scr_w && p3.x >= scr_w {
1033 return;
1034 }
1035 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
1036 return;
1037 }
1038 if p1.y >= scr_h && p2.y >= scr_h && p3.y >= scr_h {
1039 return;
1040 }
1041
1042 fill_triangle_zbuffered_2xssaa(p1, p2, p3, z1, z2, z3, color, fb, zbuffer, width);
1043 }
1044 DrawPrimitive::Line([p1, p2], color) => {
1045 draw_line_aa(p1.x, p1.y, p2.x, p2.y, color, fb);
1046 }
1047 other => draw_zbuffered(other, fb, zbuffer, width),
1048 }
1049}
1050
1051#[cfg(feature = "aa")]
1052#[inline(always)]
1053fn fill_triangle_zbuffered_2xssaa<D>(
1054 p1: nalgebra::Point2<i32>,
1055 p2: nalgebra::Point2<i32>,
1056 p3: nalgebra::Point2<i32>,
1057 z1: f32,
1058 z2: f32,
1059 z3: f32,
1060 color: Rgb565,
1061 fb: &mut D,
1062 zbuffer: &mut [u32],
1063 width: usize,
1064) where
1065 D: DrawTarget<Color = Rgb565> + ReadPixel,
1066 <D as DrawTarget>::Error: Debug,
1067{
1068 let p1_eg = Point::new(p1.x, p1.y);
1069 let p2_eg = Point::new(p2.x, p2.y);
1070 let p3_eg = Point::new(p3.x, p3.y);
1071
1072 let z1_int = (z1 * 65536.0) as u32;
1073 let z2_int = (z2 * 65536.0) as u32;
1074 let z3_int = (z3 * 65536.0) as u32;
1075
1076 if p2_eg.y == p3_eg.y {
1077 fill_bottom_flat_2xssaa(
1078 p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, fb, zbuffer, width,
1079 );
1080 } else if p1_eg.y == p2_eg.y {
1081 fill_top_flat_2xssaa(
1082 p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, fb, zbuffer, width,
1083 );
1084 } else {
1085 let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
1086 let p4 = Point::new(
1087 (p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
1088 p2_eg.y,
1089 );
1090 let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
1091 fill_bottom_flat_2xssaa(
1092 p1_eg, p2_eg, p4, z1_int, z2_int, z4_int, color, fb, zbuffer, width,
1093 );
1094 fill_top_flat_2xssaa(
1095 p2_eg, p4, p3_eg, z2_int, z4_int, z3_int, color, fb, zbuffer, width,
1096 );
1097 }
1098}
1099
1100#[cfg(feature = "aa")]
1101#[inline(always)]
1102fn fill_bottom_flat_2xssaa<D>(
1103 p1: Point,
1104 p2: Point,
1105 p3: Point,
1106 z1: u32,
1107 z2: u32,
1108 z3: u32,
1109 color: Rgb565,
1110 fb: &mut D,
1111 zbuffer: &mut [u32],
1112 width: usize,
1113) where
1114 D: DrawTarget<Color = Rgb565> + ReadPixel,
1115 <D as DrawTarget>::Error: Debug,
1116{
1117 let height = p2.y - p1.y;
1118 if height == 0 {
1119 return;
1120 }
1121 let invslope1 = ((p2.x - p1.x) << 16) / height;
1122 let invslope2 = ((p3.x - p1.x) << 16) / height;
1123
1124 let mut curx1 = p1.x << 16;
1125 let mut curx2 = p1.x << 16;
1126
1127 for scanline_y in p1.y..=p2.y {
1128 let dy = scanline_y - p1.y;
1129 let z_left = (z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1130 let z_right = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1131
1132 ssaa2x_scanline(
1133 curx1, curx2, scanline_y, z_left, z_right, color, fb, zbuffer, width,
1134 );
1135
1136 curx1 += invslope1;
1137 curx2 += invslope2;
1138 }
1139}
1140
1141#[cfg(feature = "aa")]
1142#[inline(always)]
1143fn fill_top_flat_2xssaa<D>(
1144 p1: Point,
1145 p2: Point,
1146 p3: Point,
1147 z1: u32,
1148 z2: u32,
1149 z3: u32,
1150 color: Rgb565,
1151 fb: &mut D,
1152 zbuffer: &mut [u32],
1153 width: usize,
1154) where
1155 D: DrawTarget<Color = Rgb565> + ReadPixel,
1156 <D as DrawTarget>::Error: Debug,
1157{
1158 let height = p3.y - p1.y;
1159 if height == 0 {
1160 return;
1161 }
1162 let invslope1 = ((p3.x - p1.x) << 16) / height;
1163 let invslope2 = ((p3.x - p2.x) << 16) / height;
1164
1165 let mut curx1 = p3.x << 16;
1166 let mut curx2 = p3.x << 16;
1167
1168 for scanline_y in (p1.y..=p3.y).rev() {
1169 let dy = scanline_y - p1.y;
1170 let z_left = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1171 let z_right = (z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32;
1172
1173 ssaa2x_scanline(
1174 curx1, curx2, scanline_y, z_left, z_right, color, fb, zbuffer, width,
1175 );
1176
1177 curx1 -= invslope1;
1178 curx2 -= invslope2;
1179 }
1180}
1181
1182#[cfg(feature = "aa")]
1183#[inline(always)]
1184fn ssaa2x_scanline<D>(
1185 cx1: i32,
1186 cx2: i32,
1187 y: i32,
1188 z_left: u32,
1189 z_right: u32,
1190 color: Rgb565,
1191 fb: &mut D,
1192 zbuffer: &mut [u32],
1193 width: usize,
1194) where
1195 D: DrawTarget<Color = Rgb565> + ReadPixel,
1196 <D as DrawTarget>::Error: Debug,
1197{
1198 let (left_fx, right_fx, z_l, z_r) = if cx1 <= cx2 {
1199 (cx1, cx2, z_left, z_right)
1200 } else {
1201 (cx2, cx1, z_right, z_left)
1202 };
1203
1204 let l_int = left_fx >> 16;
1205 let r_int = right_fx >> 16;
1206 let span = r_int - l_int;
1207
1208 let eval_subsamples = |x: i32| -> u32 {
1209 let fx1 = (x << 16) | 16384;
1210 let fx2 = (x << 16) | 49152;
1211 let s1 = (fx1 >= left_fx && fx1 <= right_fx) as u32;
1212 let s2 = (fx2 >= left_fx && fx2 <= right_fx) as u32;
1213 s1 + s2
1214 };
1215
1216 if l_int == r_int {
1217 let samples = eval_subsamples(l_int);
1218 if samples > 0 {
1219 let cov_q8 = samples * 128;
1220 aa_pixel(fb, l_int, y, color, z_l, zbuffer, width, cov_q8.min(256));
1221 }
1222 return;
1223 }
1224
1225 let left_samples = eval_subsamples(l_int);
1226 if left_samples > 0 {
1227 let cov_q8 = left_samples * 128;
1228 aa_pixel(fb, l_int, y, color, z_l, zbuffer, width, cov_q8.min(256));
1229 }
1230
1231 if span > 1 {
1232 for x in (l_int + 1)..r_int {
1233 let t = (x - l_int) as f32 / span as f32;
1234 let z = (z_l as f32 + t * (z_r as f32 - z_l as f32)) as u32;
1235 aa_pixel(fb, x, y, color, z, zbuffer, width, 256);
1236 }
1237 }
1238
1239 let right_samples = eval_subsamples(r_int);
1240 if right_samples > 0 {
1241 let cov_q8 = right_samples * 128;
1242 aa_pixel(fb, r_int, y, color, z_r, zbuffer, width, cov_q8.min(256));
1243 }
1244}
1245
1246#[cfg(feature = "aa-heuristic")]
1247#[inline(always)]
1248fn fill_triangle_zbuffered_aa<D>(
1249 p1: nalgebra::Point2<i32>,
1250 p2: nalgebra::Point2<i32>,
1251 p3: nalgebra::Point2<i32>,
1252 z1: f32,
1253 z2: f32,
1254 z3: f32,
1255 color: Rgb565,
1256 fb: &mut D,
1257 zbuffer: &mut [u32],
1258 width: usize,
1259) where
1260 D: DrawTarget<Color = Rgb565> + ReadPixel,
1261 <D as DrawTarget>::Error: Debug,
1262{
1263 let p1_eg = Point::new(p1.x, p1.y);
1264 let p2_eg = Point::new(p2.x, p2.y);
1265 let p3_eg = Point::new(p3.x, p3.y);
1266
1267 let z1_int = (z1 * 65536.0) as u32;
1268 let z2_int = (z2 * 65536.0) as u32;
1269 let z3_int = (z3 * 65536.0) as u32;
1270
1271 if p2_eg.y == p3_eg.y {
1272 fill_bottom_flat_aa(
1273 p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, fb, zbuffer, width,
1274 );
1275 } else if p1_eg.y == p2_eg.y {
1276 fill_top_flat_aa(
1277 p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, fb, zbuffer, width,
1278 );
1279 } else {
1280 let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
1281 let p4 = Point::new(
1282 (p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
1283 p2_eg.y,
1284 );
1285 let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
1286 fill_bottom_flat_aa(
1287 p1_eg, p2_eg, p4, z1_int, z2_int, z4_int, color, fb, zbuffer, width,
1288 );
1289 fill_top_flat_aa(
1290 p2_eg, p4, p3_eg, z2_int, z4_int, z3_int, color, fb, zbuffer, width,
1291 );
1292 }
1293}
1294
1295#[cfg(feature = "aa-heuristic")]
1296#[inline(always)]
1297fn fill_bottom_flat_aa<D>(
1298 p1: Point,
1299 p2: Point,
1300 p3: Point,
1301 z1: u32,
1302 z2: u32,
1303 z3: u32,
1304 color: Rgb565,
1305 fb: &mut D,
1306 zbuffer: &mut [u32],
1307 width: usize,
1308) where
1309 D: DrawTarget<Color = Rgb565> + ReadPixel,
1310 <D as DrawTarget>::Error: Debug,
1311{
1312 let height = p2.y - p1.y;
1313 if height == 0 {
1314 return;
1315 }
1316 let invslope1 = ((p2.x - p1.x) << 16) / height;
1317 let invslope2 = ((p3.x - p1.x) << 16) / height;
1318
1319 let mut curx1 = p1.x << 16;
1320 let mut curx2 = p1.x << 16;
1321
1322 for scanline_y in p1.y..=p2.y {
1323 let dy = scanline_y - p1.y;
1324 let z_left = (z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1325 let z_right = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1326
1327 aa_scanline(
1328 curx1, curx2, scanline_y, z_left, z_right, color, fb, zbuffer, width,
1329 );
1330
1331 curx1 += invslope1;
1332 curx2 += invslope2;
1333 }
1334}
1335
1336#[cfg(feature = "aa-heuristic")]
1337#[inline(always)]
1338fn fill_top_flat_aa<D>(
1339 p1: Point,
1340 p2: Point,
1341 p3: Point,
1342 z1: u32,
1343 z2: u32,
1344 z3: u32,
1345 color: Rgb565,
1346 fb: &mut D,
1347 zbuffer: &mut [u32],
1348 width: usize,
1349) where
1350 D: DrawTarget<Color = Rgb565> + ReadPixel,
1351 <D as DrawTarget>::Error: Debug,
1352{
1353 let height = p3.y - p1.y;
1354 if height == 0 {
1355 return;
1356 }
1357 let invslope1 = ((p3.x - p1.x) << 16) / height;
1358 let invslope2 = ((p3.x - p2.x) << 16) / height;
1359
1360 let mut curx1 = p3.x << 16;
1361 let mut curx2 = p3.x << 16;
1362
1363 for scanline_y in (p1.y..=p3.y).rev() {
1364 let dy = scanline_y - p1.y;
1365 let z_left = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1366 let z_right = (z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32;
1367
1368 aa_scanline(
1369 curx1, curx2, scanline_y, z_left, z_right, color, fb, zbuffer, width,
1370 );
1371
1372 curx1 -= invslope1;
1373 curx2 -= invslope2;
1374 }
1375}
1376
1377#[cfg(feature = "aa-heuristic")]
1383#[inline(always)]
1384fn aa_scanline<D>(
1385 cx1: i32,
1386 cx2: i32,
1387 y: i32,
1388 z_left: u32,
1389 z_right: u32,
1390 color: Rgb565,
1391 fb: &mut D,
1392 zbuffer: &mut [u32],
1393 width: usize,
1394) where
1395 D: DrawTarget<Color = Rgb565> + ReadPixel,
1396 <D as DrawTarget>::Error: Debug,
1397{
1398 let (left_fx, right_fx, z_l, z_r) = if cx1 <= cx2 {
1400 (cx1, cx2, z_left, z_right)
1401 } else {
1402 (cx2, cx1, z_right, z_left)
1403 };
1404
1405 let l_int = left_fx >> 16;
1406 let r_int = right_fx >> 16;
1407 let l_frac_q16 = (left_fx & 0xFFFF) as u32;
1408 let r_frac_q16 = (right_fx & 0xFFFF) as u32;
1409
1410 let span = r_int - l_int;
1412
1413 if l_int == r_int {
1414 let cov_q16 = r_frac_q16.saturating_sub(l_frac_q16);
1417 aa_pixel(fb, l_int, y, color, z_l, zbuffer, width, cov_q16 >> 8);
1418 return;
1419 }
1420
1421 let left_cov_q8 = 256 - (l_frac_q16 >> 8);
1423 aa_pixel(fb, l_int, y, color, z_l, zbuffer, width, left_cov_q8);
1424
1425 if span > 1 {
1428 for x in (l_int + 1)..r_int {
1429 if x < 0 {
1430 continue;
1431 }
1432 let idx = y as usize * width + x as usize;
1433 if idx >= zbuffer.len() {
1434 continue;
1435 }
1436 let t_num = (x - l_int) as i64;
1438 let t_den = span as i64;
1439 let z = (z_l as i64 + ((z_r as i64 - z_l as i64) * t_num / t_den)) as u32;
1440 if z < zbuffer[idx].saturating_add(DEPTH_EPSILON) {
1441 zbuffer[idx] = z;
1442 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), color)])
1443 .unwrap();
1444 }
1445 }
1446 }
1447
1448 if r_frac_q16 > 0 {
1450 let right_cov_q8 = r_frac_q16 >> 8;
1451 aa_pixel(fb, r_int, y, color, z_r, zbuffer, width, right_cov_q8);
1452 }
1453}
1454
1455#[cfg(feature = "aa-coverage")]
1472#[inline]
1473pub fn draw_zbuffered_aa_coverage<D>(
1474 primitive: DrawPrimitive,
1475 fb: &mut D,
1476 zbuffer: &mut [u32],
1477 coverage: &mut [u8],
1478 width: usize,
1479) where
1480 D: DrawTarget<Color = Rgb565> + ReadPixel,
1481 <D as DrawTarget>::Error: Debug,
1482{
1483 match primitive {
1484 DrawPrimitive::ColoredTriangleWithDepth {
1485 mut points,
1486 mut depths,
1487 color,
1488 } => {
1489 if points[0].y > points[1].y {
1490 points.swap(0, 1);
1491 depths.swap(0, 1);
1492 }
1493 if points[0].y > points[2].y {
1494 points.swap(0, 2);
1495 depths.swap(0, 2);
1496 }
1497 if points[1].y > points[2].y {
1498 points.swap(1, 2);
1499 depths.swap(1, 2);
1500 }
1501 let [p1, p2, p3] = points;
1502 let [z1, z2, z3] = depths;
1503 fill_triangle_zbuffered_aa_cov(
1504 p1, p2, p3, z1, z2, z3, color, fb, zbuffer, coverage, width,
1505 );
1506 }
1507 DrawPrimitive::Line([p1, p2], color) => {
1508 draw_line_aa_coverage(p1.x, p1.y, p2.x, p2.y, color, fb, coverage, width);
1511 }
1512 other => draw_zbuffered(other, fb, zbuffer, width),
1513 }
1514}
1515
1516#[cfg(feature = "aa-coverage")]
1520pub fn draw_line_aa_coverage<D>(
1521 x0: i32,
1522 y0: i32,
1523 x1: i32,
1524 y1: i32,
1525 color: Rgb565,
1526 fb: &mut D,
1527 coverage: &mut [u8],
1528 width: usize,
1529) where
1530 D: DrawTarget<Color = Rgb565> + ReadPixel,
1531 <D as DrawTarget>::Error: Debug,
1532{
1533 let dx = (x1 - x0).abs();
1534 let dy = (y1 - y0).abs();
1535 let steep = dy > dx;
1536 let (x0, y0, x1, y1) = if steep {
1537 (y0, x0, y1, x1)
1538 } else {
1539 (x0, y0, x1, y1)
1540 };
1541 let (x0, y0, x1, y1) = if x0 > x1 {
1542 (x1, y1, x0, y0)
1543 } else {
1544 (x0, y0, x1, y1)
1545 };
1546 let dx = x1 - x0;
1547 let dy = y1 - y0;
1548 if dx == 0 {
1549 let (px, py) = if steep { (y0, x0) } else { (x0, y0) };
1550 plot_aa_cov(fb, px, py, color, coverage, width, 256);
1551 return;
1552 }
1553 let gradient: i32 = ((dy as i64) << 16) as i32 / dx;
1554 let mut intery: i32 = y0 << 16;
1555 for x in x0..=x1 {
1556 let y_int = intery >> 16;
1557 let frac_q16 = (intery & 0xFFFF) as u32;
1558 let cov_top = 256 - (frac_q16 >> 8);
1559 let cov_bot = frac_q16 >> 8;
1560 if steep {
1561 plot_aa_cov(fb, y_int, x, color, coverage, width, cov_top);
1562 plot_aa_cov(fb, y_int + 1, x, color, coverage, width, cov_bot);
1563 } else {
1564 plot_aa_cov(fb, x, y_int, color, coverage, width, cov_top);
1565 plot_aa_cov(fb, x, y_int + 1, color, coverage, width, cov_bot);
1566 }
1567 intery += gradient;
1568 }
1569}
1570
1571#[cfg(feature = "aa-coverage")]
1574#[inline(always)]
1575fn plot_aa_cov<D>(
1576 fb: &mut D,
1577 x: i32,
1578 y: i32,
1579 color: Rgb565,
1580 coverage: &mut [u8],
1581 width: usize,
1582 coverage_q8: u32,
1583) where
1584 D: DrawTarget<Color = Rgb565> + ReadPixel,
1585 <D as DrawTarget>::Error: Debug,
1586{
1587 if x < 0 || y < 0 || x >= width as i32 || coverage_q8 == 0 {
1588 return;
1589 }
1590 let idx = y as usize * width + x as usize;
1591 if idx >= coverage.len() {
1592 return;
1593 }
1594 let p = Point::new(x, y);
1595
1596 if coverage_q8 >= 256 {
1597 coverage[idx] = 255;
1598 fb.draw_iter([embedded_graphics_core::Pixel(p, color)])
1599 .unwrap();
1600 return;
1601 }
1602
1603 let prev_cov = coverage[idx] as u32;
1604
1605 if prev_cov == 0 {
1606 let claim_255 = (coverage_q8 * 255) >> 8;
1607 coverage[idx] = claim_255 as u8;
1608 fb.draw_iter([embedded_graphics_core::Pixel(p, color)])
1609 .unwrap();
1610 return;
1611 }
1612
1613 if prev_cov >= 255 {
1614 let existing = fb.read_pixel(p);
1615 let result = blend_q8(existing, color, coverage_q8);
1616 fb.draw_iter([embedded_graphics_core::Pixel(p, result)])
1617 .unwrap();
1618 return;
1619 }
1620
1621 let remaining = 255 - prev_cov;
1622 let claim_255 = ((coverage_q8 * 255) >> 8).min(remaining);
1623 if claim_255 == 0 {
1624 return;
1625 }
1626 let new_total = prev_cov + claim_255;
1627 let existing = fb.read_pixel(p);
1628 let blend_factor = (claim_255 * 256) / new_total;
1629 let result = blend_q8(existing, color, blend_factor);
1630 coverage[idx] = new_total as u8;
1631 fb.draw_iter([embedded_graphics_core::Pixel(p, result)])
1632 .unwrap();
1633}
1634
1635#[cfg(feature = "aa-coverage")]
1639pub fn composite_aa_background<D>(
1640 fb: &mut D,
1641 coverage: &[u8],
1642 bg: Rgb565,
1643 width: usize,
1644 height: usize,
1645) where
1646 D: DrawTarget<Color = Rgb565> + ReadPixel,
1647 <D as DrawTarget>::Error: Debug,
1648{
1649 for y in 0..height {
1650 for x in 0..width {
1651 let idx = y * width + x;
1652 let cov = coverage[idx];
1653 if cov == 255 {
1654 continue; }
1656 let p = Point::new(x as i32, y as i32);
1657 let final_color = if cov == 0 {
1658 bg
1659 } else {
1660 let tri_color = fb.read_pixel(p);
1664 let cov_q8 = ((cov as u32) * 256) / 255;
1666 blend_q8(bg, tri_color, cov_q8)
1667 };
1668 fb.draw_iter([embedded_graphics_core::Pixel(p, final_color)])
1669 .unwrap();
1670 }
1671 }
1672}
1673
1674#[cfg(feature = "aa-coverage")]
1689#[inline(always)]
1690fn aa_pixel_cov<D>(
1691 fb: &mut D,
1692 x: i32,
1693 y: i32,
1694 color: Rgb565,
1695 z: u32,
1696 zbuffer: &mut [u32],
1697 coverage: &mut [u8],
1698 width: usize,
1699 coverage_q8: u32,
1700) where
1701 D: DrawTarget<Color = Rgb565> + ReadPixel,
1702 <D as DrawTarget>::Error: Debug,
1703{
1704 if x < 0 || y < 0 || x >= width as i32 || coverage_q8 == 0 {
1705 return;
1706 }
1707 let idx = y as usize * width + x as usize;
1708 if idx >= zbuffer.len() {
1709 return;
1710 }
1711 if z >= zbuffer[idx].saturating_add(DEPTH_EPSILON) {
1712 return;
1713 }
1714
1715 let p = Point::new(x, y);
1716
1717 if coverage_q8 >= 256 {
1719 coverage[idx] = 255;
1720 zbuffer[idx] = z;
1721 fb.draw_iter([embedded_graphics_core::Pixel(p, color)])
1722 .unwrap();
1723 return;
1724 }
1725
1726 let prev_cov = coverage[idx] as u32;
1727
1728 if prev_cov == 0 {
1729 let claim_255 = (coverage_q8 * 255) >> 8;
1732 coverage[idx] = claim_255 as u8;
1733 zbuffer[idx] = z;
1734 fb.draw_iter([embedded_graphics_core::Pixel(p, color)])
1735 .unwrap();
1736 return;
1737 }
1738
1739 if prev_cov >= 255 {
1740 let existing = fb.read_pixel(p);
1745 let result = blend_q8(existing, color, coverage_q8);
1746 zbuffer[idx] = z;
1747 fb.draw_iter([embedded_graphics_core::Pixel(p, result)])
1748 .unwrap();
1749 return;
1750 }
1751
1752 let remaining = 255 - prev_cov;
1755 let claim_255 = ((coverage_q8 * 255) >> 8).min(remaining);
1756 if claim_255 == 0 {
1757 return;
1758 }
1759 let new_total = prev_cov + claim_255;
1760 let existing = fb.read_pixel(p);
1761 let blend_factor = (claim_255 * 256) / new_total;
1762 let result = blend_q8(existing, color, blend_factor);
1763 coverage[idx] = new_total as u8;
1764 zbuffer[idx] = z;
1765 fb.draw_iter([embedded_graphics_core::Pixel(p, result)])
1766 .unwrap();
1767}
1768
1769#[cfg(feature = "aa-coverage")]
1770#[inline(always)]
1771fn fill_triangle_zbuffered_aa_cov<D>(
1772 p1: nalgebra::Point2<i32>,
1773 p2: nalgebra::Point2<i32>,
1774 p3: nalgebra::Point2<i32>,
1775 z1: f32,
1776 z2: f32,
1777 z3: f32,
1778 color: Rgb565,
1779 fb: &mut D,
1780 zbuffer: &mut [u32],
1781 coverage: &mut [u8],
1782 width: usize,
1783) where
1784 D: DrawTarget<Color = Rgb565> + ReadPixel,
1785 <D as DrawTarget>::Error: Debug,
1786{
1787 let p1_eg = Point::new(p1.x, p1.y);
1788 let p2_eg = Point::new(p2.x, p2.y);
1789 let p3_eg = Point::new(p3.x, p3.y);
1790
1791 let z1_int = (z1 * 65536.0) as u32;
1792 let z2_int = (z2 * 65536.0) as u32;
1793 let z3_int = (z3 * 65536.0) as u32;
1794
1795 if p2_eg.y == p3_eg.y {
1796 fill_bottom_flat_aa_cov(
1797 p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, fb, zbuffer, coverage, width,
1798 );
1799 } else if p1_eg.y == p2_eg.y {
1800 fill_top_flat_aa_cov(
1801 p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, fb, zbuffer, coverage, width,
1802 );
1803 } else {
1804 let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
1805 let p4 = Point::new(
1806 (p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
1807 p2_eg.y,
1808 );
1809 let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
1810 fill_bottom_flat_aa_cov(
1811 p1_eg, p2_eg, p4, z1_int, z2_int, z4_int, color, fb, zbuffer, coverage, width,
1812 );
1813 fill_top_flat_aa_cov(
1814 p2_eg, p4, p3_eg, z2_int, z4_int, z3_int, color, fb, zbuffer, coverage, width,
1815 );
1816 }
1817}
1818
1819#[cfg(feature = "aa-coverage")]
1820#[inline(always)]
1821fn fill_bottom_flat_aa_cov<D>(
1822 p1: Point,
1823 p2: Point,
1824 p3: Point,
1825 z1: u32,
1826 z2: u32,
1827 z3: u32,
1828 color: Rgb565,
1829 fb: &mut D,
1830 zbuffer: &mut [u32],
1831 coverage: &mut [u8],
1832 width: usize,
1833) where
1834 D: DrawTarget<Color = Rgb565> + ReadPixel,
1835 <D as DrawTarget>::Error: Debug,
1836{
1837 let height = p2.y - p1.y;
1838 if height == 0 {
1839 return;
1840 }
1841 let invslope1 = ((p2.x - p1.x) << 16) / height;
1842 let invslope2 = ((p3.x - p1.x) << 16) / height;
1843
1844 let mut curx1 = p1.x << 16;
1845 let mut curx2 = p1.x << 16;
1846
1847 for scanline_y in p1.y..=p2.y {
1848 let dy = scanline_y - p1.y;
1849 let z_left = (z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1850 let z_right = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1851
1852 aa_scanline_cov(
1853 curx1, curx2, scanline_y, z_left, z_right, color, fb, zbuffer, coverage, width,
1854 );
1855
1856 curx1 += invslope1;
1857 curx2 += invslope2;
1858 }
1859}
1860
1861#[cfg(feature = "aa-coverage")]
1862#[inline(always)]
1863fn fill_top_flat_aa_cov<D>(
1864 p1: Point,
1865 p2: Point,
1866 p3: Point,
1867 z1: u32,
1868 z2: u32,
1869 z3: u32,
1870 color: Rgb565,
1871 fb: &mut D,
1872 zbuffer: &mut [u32],
1873 coverage: &mut [u8],
1874 width: usize,
1875) where
1876 D: DrawTarget<Color = Rgb565> + ReadPixel,
1877 <D as DrawTarget>::Error: Debug,
1878{
1879 let height = p3.y - p1.y;
1880 if height == 0 {
1881 return;
1882 }
1883 let invslope1 = ((p3.x - p1.x) << 16) / height;
1884 let invslope2 = ((p3.x - p2.x) << 16) / height;
1885
1886 let mut curx1 = p3.x << 16;
1887 let mut curx2 = p3.x << 16;
1888
1889 for scanline_y in (p1.y..=p3.y).rev() {
1890 let dy = scanline_y - p1.y;
1891 let z_left = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
1892 let z_right = (z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32;
1893
1894 aa_scanline_cov(
1895 curx1, curx2, scanline_y, z_left, z_right, color, fb, zbuffer, coverage, width,
1896 );
1897
1898 curx1 -= invslope1;
1899 curx2 -= invslope2;
1900 }
1901}
1902
1903#[cfg(feature = "aa-coverage")]
1904#[inline(always)]
1905fn aa_scanline_cov<D>(
1906 cx1: i32,
1907 cx2: i32,
1908 y: i32,
1909 z_left: u32,
1910 z_right: u32,
1911 color: Rgb565,
1912 fb: &mut D,
1913 zbuffer: &mut [u32],
1914 coverage: &mut [u8],
1915 width: usize,
1916) where
1917 D: DrawTarget<Color = Rgb565> + ReadPixel,
1918 <D as DrawTarget>::Error: Debug,
1919{
1920 let (left_fx, right_fx, z_l, z_r) = if cx1 <= cx2 {
1921 (cx1, cx2, z_left, z_right)
1922 } else {
1923 (cx2, cx1, z_right, z_left)
1924 };
1925
1926 let l_int = left_fx >> 16;
1927 let r_int = right_fx >> 16;
1928 let l_frac_q16 = (left_fx & 0xFFFF) as u32;
1929 let r_frac_q16 = (right_fx & 0xFFFF) as u32;
1930 let span = r_int - l_int;
1931
1932 if l_int == r_int {
1933 let cov_q16 = r_frac_q16.saturating_sub(l_frac_q16);
1934 aa_pixel_cov(
1935 fb,
1936 l_int,
1937 y,
1938 color,
1939 z_l,
1940 zbuffer,
1941 coverage,
1942 width,
1943 cov_q16 >> 8,
1944 );
1945 return;
1946 }
1947
1948 let left_cov_q8 = 256 - (l_frac_q16 >> 8);
1949 aa_pixel_cov(
1950 fb,
1951 l_int,
1952 y,
1953 color,
1954 z_l,
1955 zbuffer,
1956 coverage,
1957 width,
1958 left_cov_q8,
1959 );
1960
1961 if span > 1 {
1962 for x in (l_int + 1)..r_int {
1963 let t_num = (x - l_int) as i64;
1966 let t_den = span as i64;
1967 let z = (z_l as i64 + ((z_r as i64 - z_l as i64) * t_num / t_den)) as u32;
1968 aa_pixel_cov(fb, x, y, color, z, zbuffer, coverage, width, 256);
1969 }
1970 }
1971
1972 if r_frac_q16 > 0 {
1973 let right_cov_q8 = r_frac_q16 >> 8;
1974 aa_pixel_cov(
1975 fb,
1976 r_int,
1977 y,
1978 color,
1979 z_r,
1980 zbuffer,
1981 coverage,
1982 width,
1983 right_cov_q8,
1984 );
1985 }
1986}
1987
1988#[cfg(feature = "aa")]
1993pub fn draw_line_aa<D>(x0: i32, y0: i32, x1: i32, y1: i32, color: Rgb565, fb: &mut D)
1994where
1995 D: DrawTarget<Color = Rgb565> + ReadPixel,
1996 <D as DrawTarget>::Error: Debug,
1997{
1998 let dx = (x1 - x0).abs();
1999 let dy = (y1 - y0).abs();
2000 let steep = dy > dx;
2001 let (x0, y0, x1, y1) = if steep {
2002 (y0, x0, y1, x1)
2003 } else {
2004 (x0, y0, x1, y1)
2005 };
2006 let (x0, y0, x1, y1) = if x0 > x1 {
2007 (x1, y1, x0, y0)
2008 } else {
2009 (x0, y0, x1, y1)
2010 };
2011 let dx = x1 - x0;
2012 let dy = y1 - y0;
2013 if dx == 0 {
2014 let (px, py) = if steep { (y0, x0) } else { (x0, y0) };
2016 plot_aa(fb, px, py, color, 256);
2017 return;
2018 }
2019 let gradient: i32 = ((dy as i64) << 16) as i32 / dx;
2021 let mut intery: i32 = y0 << 16;
2023 for x in x0..=x1 {
2024 let y_int = intery >> 16;
2025 let frac_q16 = (intery & 0xFFFF) as u32;
2026 let cov_top = 256 - (frac_q16 >> 8); let cov_bot = frac_q16 >> 8; if steep {
2029 plot_aa(fb, y_int, x, color, cov_top);
2030 plot_aa(fb, y_int + 1, x, color, cov_bot);
2031 } else {
2032 plot_aa(fb, x, y_int, color, cov_top);
2033 plot_aa(fb, x, y_int + 1, color, cov_bot);
2034 }
2035 intery += gradient;
2036 }
2037}
2038
2039#[cfg(feature = "aa")]
2040#[inline(always)]
2041fn plot_aa<D>(fb: &mut D, x: i32, y: i32, color: Rgb565, coverage_q8: u32)
2042where
2043 D: DrawTarget<Color = Rgb565> + ReadPixel,
2044 <D as DrawTarget>::Error: Debug,
2045{
2046 if coverage_q8 == 0 {
2047 return;
2048 }
2049 let final_color = if coverage_q8 >= 256 {
2050 color
2051 } else {
2052 let bg = fb.read_pixel(Point::new(x, y));
2053 blend_q8(bg, color, coverage_q8)
2054 };
2055 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_color)])
2056 .unwrap();
2057}
2058
2059#[inline]
2062pub fn draw_zbuffered_with_effects<
2063 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
2064>(
2065 primitive: DrawPrimitive,
2066 fb: &mut D,
2067 zbuffer: &mut [u32],
2068 width: usize,
2069 fog_config: Option<&FogConfig>,
2070 dither_config: Option<&DitherConfig>,
2071) where
2072 <D as DrawTarget>::Error: Debug,
2073{
2074 match primitive {
2075 DrawPrimitive::ColoredTriangleWithDepth {
2076 mut points,
2077 mut depths,
2078 color,
2079 } => {
2080 if points[0].y > points[1].y {
2082 points.swap(0, 1);
2083 depths.swap(0, 1);
2084 }
2085 if points[0].y > points[2].y {
2086 points.swap(0, 2);
2087 depths.swap(0, 2);
2088 }
2089 if points[1].y > points[2].y {
2090 points.swap(1, 2);
2091 depths.swap(1, 2);
2092 }
2093
2094 let [p1, p2, p3] = points;
2095 let [z1, z2, z3] = depths;
2096
2097 let scr_w = width as i32;
2099 let scr_h = (zbuffer.len() / width) as i32;
2100 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
2101 return;
2102 }
2103 if p1.x >= scr_w && p2.x >= scr_w && p3.x >= scr_w {
2104 return;
2105 }
2106 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
2107 return;
2108 }
2109 if p1.y >= scr_h && p2.y >= scr_h && p3.y >= scr_h {
2110 return;
2111 }
2112
2113 fill_triangle_zbuffered(
2114 p1,
2115 p2,
2116 p3,
2117 z1,
2118 z2,
2119 z3,
2120 color,
2121 fb,
2122 zbuffer,
2123 width,
2124 fog_config,
2125 dither_config,
2126 );
2127 }
2128 DrawPrimitive::TranslucentTriangleWithDepth {
2129 mut points,
2130 mut depths,
2131 color,
2132 alpha,
2133 } => {
2134 if points[0].y > points[1].y {
2135 points.swap(0, 1);
2136 depths.swap(0, 1);
2137 }
2138 if points[0].y > points[2].y {
2139 points.swap(0, 2);
2140 depths.swap(0, 2);
2141 }
2142 if points[1].y > points[2].y {
2143 points.swap(1, 2);
2144 depths.swap(1, 2);
2145 }
2146
2147 let [p1, p2, p3] = points;
2148 let [z1, z2, z3] = depths;
2149
2150 let scr_w = width as i32;
2151 let scr_h = (zbuffer.len() / width) as i32;
2152 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
2153 return;
2154 }
2155 if p1.x >= scr_w && p2.x >= scr_w && p3.x >= scr_w {
2156 return;
2157 }
2158 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
2159 return;
2160 }
2161 if p1.y >= scr_h && p2.y >= scr_h && p3.y >= scr_h {
2162 return;
2163 }
2164
2165 fill_triangle_zbuffered_translucent(
2166 p1, p2, p3, z1, z2, z3, color, alpha, fb, zbuffer, width,
2167 );
2168 }
2169 DrawPrimitive::GouraudTriangleWithDepth {
2170 mut points,
2171 mut depths,
2172 mut colors,
2173 } => {
2174 if points[0].y > points[1].y {
2176 points.swap(0, 1);
2177 depths.swap(0, 1);
2178 colors.swap(0, 1);
2179 }
2180 if points[0].y > points[2].y {
2181 points.swap(0, 2);
2182 depths.swap(0, 2);
2183 colors.swap(0, 2);
2184 }
2185 if points[1].y > points[2].y {
2186 points.swap(1, 2);
2187 depths.swap(1, 2);
2188 colors.swap(1, 2);
2189 }
2190
2191 let [p1, p2, p3] = points;
2192 let [z1, z2, z3] = depths;
2193 let [c1, c2, c3] = colors;
2194
2195 let scr_w = width as i32;
2197 let scr_h = (zbuffer.len() / width) as i32;
2198 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
2199 return;
2200 }
2201 if p1.x >= scr_w && p2.x >= scr_w && p3.x >= scr_w {
2202 return;
2203 }
2204 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
2205 return;
2206 }
2207 if p1.y >= scr_h && p2.y >= scr_h && p3.y >= scr_h {
2208 return;
2209 }
2210
2211 fill_triangle_zbuffered_gouraud(
2212 p1,
2213 p2,
2214 p3,
2215 z1,
2216 z2,
2217 z3,
2218 c1,
2219 c2,
2220 c3,
2221 fb,
2222 zbuffer,
2223 width,
2224 fog_config,
2225 dither_config,
2226 );
2227 }
2228 DrawPrimitive::TexturedTriangle { .. }
2230 | DrawPrimitive::TexturedTriangleWithDepth { .. }
2231 | DrawPrimitive::LightmappedTriangle { .. } => {
2232 }
2234 _ => draw(primitive, fb),
2236 }
2237}
2238
2239#[inline(always)]
2240fn interpolate_uv(
2241 t: f32,
2242 w1: f32,
2243 w2: f32,
2244 uv1: [f32; 2],
2245 uv2: [f32; 2],
2246 texture_mapping: TextureMapping,
2247) -> [f32; 2] {
2248 match texture_mapping {
2249 TextureMapping::PerspectiveCorrect => {
2250 let ow1 = 1.0 / w1;
2251 let ow2 = 1.0 / w2;
2252 let one_over_w = ow1 + t * (ow2 - ow1);
2253 [
2254 (uv1[0] * ow1 + t * (uv2[0] * ow2 - uv1[0] * ow1)) / one_over_w,
2255 (uv1[1] * ow1 + t * (uv2[1] * ow2 - uv1[1] * ow1)) / one_over_w,
2256 ]
2257 }
2258 TextureMapping::Affine => [
2259 uv1[0] + t * (uv2[0] - uv1[0]),
2260 uv1[1] + t * (uv2[1] - uv1[1]),
2261 ],
2262 }
2263}
2264
2265#[inline(always)]
2266fn should_skip_stipple(x: i32, y: i32, stipple_mode: StippleMode) -> bool {
2267 matches!(stipple_mode, StippleMode::Checkerboard) && ((x ^ y) & 1) != 0
2268}
2269
2270#[inline]
2272pub fn draw_zbuffered_with_textures<
2273 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
2274 const N: usize,
2275>(
2276 primitive: DrawPrimitive,
2277 fb: &mut D,
2278 zbuffer: &mut [u32],
2279 width: usize,
2280 texture_manager: &crate::texture::TextureManager<N>,
2281 fog_config: Option<&FogConfig>,
2282 dither_config: Option<&DitherConfig>,
2283) where
2284 <D as DrawTarget>::Error: Debug,
2285{
2286 draw_zbuffered_with_textures_mapped(
2287 primitive,
2288 fb,
2289 zbuffer,
2290 width,
2291 texture_manager,
2292 fog_config,
2293 dither_config,
2294 TextureMapping::PerspectiveCorrect,
2295 StippleMode::Off,
2296 None,
2297 PaletteMode::Off,
2298 );
2299}
2300
2301#[inline]
2302pub fn draw_zbuffered_with_textures_mapped<
2303 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
2304 const N: usize,
2305>(
2306 primitive: DrawPrimitive,
2307 fb: &mut D,
2308 zbuffer: &mut [u32],
2309 width: usize,
2310 texture_manager: &crate::texture::TextureManager<N>,
2311 fog_config: Option<&FogConfig>,
2312 dither_config: Option<&DitherConfig>,
2313 texture_mapping: TextureMapping,
2314 stipple_mode: StippleMode,
2315 screen_tint: Option<ScreenTint>,
2316 palette_mode: PaletteMode,
2317) where
2318 <D as DrawTarget>::Error: Debug,
2319{
2320 match primitive {
2321 DrawPrimitive::TexturedTriangleWithDepth {
2322 mut points,
2323 mut depths,
2324 mut ws,
2325 mut uvs,
2326 texture_id,
2327 } => {
2328 if let Some(texture) = texture_manager.get(texture_id) {
2330 if points[0].y > points[1].y {
2332 points.swap(0, 1);
2333 depths.swap(0, 1);
2334 ws.swap(0, 1);
2335 uvs.swap(0, 1);
2336 }
2337 if points[0].y > points[2].y {
2338 points.swap(0, 2);
2339 depths.swap(0, 2);
2340 ws.swap(0, 2);
2341 uvs.swap(0, 2);
2342 }
2343 if points[1].y > points[2].y {
2344 points.swap(1, 2);
2345 depths.swap(1, 2);
2346 ws.swap(1, 2);
2347 uvs.swap(1, 2);
2348 }
2349
2350 let [p1, p2, p3] = points;
2351 let [z1, z2, z3] = depths;
2352 let [w1, w2, w3] = ws;
2353 let [uv1, uv2, uv3] = uvs;
2354
2355 let scr_w = width as i32;
2357 let scr_h = (zbuffer.len() / width) as i32;
2358 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
2359 return;
2360 }
2361 if p1.x >= scr_w && p2.x >= scr_w && p3.x >= scr_w {
2362 return;
2363 }
2364 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
2365 return;
2366 }
2367 if p1.y >= scr_h && p2.y >= scr_h && p3.y >= scr_h {
2368 return;
2369 }
2370
2371 fill_triangle_zbuffered_textured(
2372 p1,
2373 p2,
2374 p3,
2375 z1,
2376 z2,
2377 z3,
2378 w1,
2379 w2,
2380 w3,
2381 uv1,
2382 uv2,
2383 uv3,
2384 texture,
2385 fb,
2386 zbuffer,
2387 width,
2388 fog_config,
2389 dither_config,
2390 texture_mapping,
2391 stipple_mode,
2392 screen_tint,
2393 palette_mode,
2394 );
2395 }
2396 }
2397 _ => draw_zbuffered_with_effects(primitive, fb, zbuffer, width, fog_config, dither_config),
2399 }
2400}
2401
2402pub fn draw_zbuffered_lightmapped<
2416 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
2417 const N: usize,
2418>(
2419 points: [nalgebra::Point2<i32>; 3],
2420 depths: [f32; 3],
2421 ws: [f32; 3],
2422 surface_uvs: [[f32; 2]; 3],
2423 lm_uvs: [[f32; 2]; 3],
2424 texture_id: u32,
2425 lightmap_id: u32,
2426 brightness: u8,
2427 dynamic_tint: embedded_graphics_core::pixelcolor::Rgb565,
2428 fog_config: Option<&FogConfig>,
2429 texture_manager: &crate::texture::TextureManager<N>,
2430 fb: &mut D,
2431 zbuffer: &mut [u32],
2432 width: usize,
2433) where
2434 <D as DrawTarget>::Error: core::fmt::Debug,
2435{
2436 draw_zbuffered_lightmapped_mapped(
2437 points,
2438 depths,
2439 ws,
2440 surface_uvs,
2441 lm_uvs,
2442 texture_id,
2443 lightmap_id,
2444 brightness,
2445 dynamic_tint,
2446 fog_config,
2447 texture_manager,
2448 fb,
2449 zbuffer,
2450 width,
2451 TextureMapping::PerspectiveCorrect,
2452 StippleMode::Off,
2453 None,
2454 PaletteMode::Off,
2455 );
2456}
2457
2458pub fn draw_zbuffered_lightmapped_mapped<
2459 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
2460 const N: usize,
2461>(
2462 mut points: [nalgebra::Point2<i32>; 3],
2463 mut depths: [f32; 3],
2464 mut ws: [f32; 3],
2465 mut surface_uvs: [[f32; 2]; 3],
2466 mut lm_uvs: [[f32; 2]; 3],
2467 texture_id: u32,
2468 lightmap_id: u32,
2469 brightness: u8,
2470 dynamic_tint: embedded_graphics_core::pixelcolor::Rgb565,
2471 fog_config: Option<&FogConfig>,
2472 texture_manager: &crate::texture::TextureManager<N>,
2473 fb: &mut D,
2474 zbuffer: &mut [u32],
2475 width: usize,
2476 texture_mapping: TextureMapping,
2477 stipple_mode: StippleMode,
2478 screen_tint: Option<ScreenTint>,
2479 palette_mode: PaletteMode,
2480) where
2481 <D as DrawTarget>::Error: core::fmt::Debug,
2482{
2483 let surf = match texture_manager.get(texture_id) {
2484 Some(t) => t,
2485 None => return,
2486 };
2487 let lm = if lightmap_id == u32::MAX {
2488 None
2489 } else {
2490 texture_manager.get(lightmap_id)
2491 };
2492
2493 macro_rules! swap_all {
2495 ($i:expr, $j:expr) => {
2496 points.swap($i, $j);
2497 depths.swap($i, $j);
2498 ws.swap($i, $j);
2499 surface_uvs.swap($i, $j);
2500 lm_uvs.swap($i, $j);
2501 };
2502 }
2503 if points[0].y > points[1].y {
2504 swap_all!(0, 1);
2505 }
2506 if points[0].y > points[2].y {
2507 swap_all!(0, 2);
2508 }
2509 if points[1].y > points[2].y {
2510 swap_all!(1, 2);
2511 }
2512
2513 let [p1, p2, p3] = points;
2514 let [z1, z2, z3] = depths;
2515 let [w1, w2, w3] = ws;
2516 let [uv1, uv2, uv3] = surface_uvs;
2517 let [luv1, luv2, luv3] = lm_uvs;
2518
2519 let scr_w = width as i32;
2520 let scr_h = (zbuffer.len() / width) as i32;
2521 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
2522 return;
2523 }
2524 if p1.x >= scr_w && p2.x >= scr_w && p3.x >= scr_w {
2525 return;
2526 }
2527 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
2528 return;
2529 }
2530 if p1.y >= scr_h && p2.y >= scr_h && p3.y >= scr_h {
2531 return;
2532 }
2533
2534 let z1_int = (z1 * 65536.0) as u32;
2535 let z2_int = (z2 * 65536.0) as u32;
2536 let z3_int = (z3 * 65536.0) as u32;
2537
2538 if p2.y == p3.y {
2540 fill_lm_bottom_flat(
2541 p1,
2542 p2,
2543 p3,
2544 z1_int,
2545 z2_int,
2546 z3_int,
2547 w1,
2548 w2,
2549 w3,
2550 uv1,
2551 uv2,
2552 uv3,
2553 luv1,
2554 luv2,
2555 luv3,
2556 dynamic_tint,
2557 fog_config,
2558 surf,
2559 lm,
2560 fb,
2561 zbuffer,
2562 width,
2563 texture_mapping,
2564 stipple_mode,
2565 screen_tint,
2566 palette_mode,
2567 brightness,
2568 );
2569 } else if p1.y == p2.y {
2570 fill_lm_top_flat(
2571 p1,
2572 p2,
2573 p3,
2574 z1_int,
2575 z2_int,
2576 z3_int,
2577 w1,
2578 w2,
2579 w3,
2580 uv1,
2581 uv2,
2582 uv3,
2583 luv1,
2584 luv2,
2585 luv3,
2586 dynamic_tint,
2587 fog_config,
2588 surf,
2589 lm,
2590 fb,
2591 zbuffer,
2592 width,
2593 texture_mapping,
2594 stipple_mode,
2595 screen_tint,
2596 palette_mode,
2597 brightness,
2598 );
2599 } else {
2600 let dy31 = (p3.y - p1.y) as f32;
2602 let dy21 = (p2.y - p1.y) as f32;
2603 let t = dy21 / dy31;
2604 let p4x = p1.x + ((p3.x - p1.x) as f32 * t) as i32;
2605 let p4 = embedded_graphics_core::prelude::Point::new(p4x, p2.y);
2606 let z4_int = (z1_int as f32 + (z3_int as f32 - z1_int as f32) * t) as u32;
2607 let w4 = w1 + (w3 - w1) * t;
2608 let uv4 = [
2609 uv1[0] + (uv3[0] - uv1[0]) * t,
2610 uv1[1] + (uv3[1] - uv1[1]) * t,
2611 ];
2612 let luv4 = [
2613 luv1[0] + (luv3[0] - luv1[0]) * t,
2614 luv1[1] + (luv3[1] - luv1[1]) * t,
2615 ];
2616 let p4_2 = nalgebra::Point2::new(p4.x, p4.y);
2617 fill_lm_bottom_flat(
2618 p1,
2619 p2,
2620 p4_2,
2621 z1_int,
2622 z2_int,
2623 z4_int,
2624 w1,
2625 w2,
2626 w4,
2627 uv1,
2628 uv2,
2629 uv4,
2630 luv1,
2631 luv2,
2632 luv4,
2633 dynamic_tint,
2634 fog_config,
2635 surf,
2636 lm,
2637 fb,
2638 zbuffer,
2639 width,
2640 texture_mapping,
2641 stipple_mode,
2642 screen_tint,
2643 palette_mode,
2644 brightness,
2645 );
2646 fill_lm_top_flat(
2647 p2,
2648 p4_2,
2649 p3,
2650 z2_int,
2651 z4_int,
2652 z3_int,
2653 w2,
2654 w4,
2655 w3,
2656 uv2,
2657 uv4,
2658 uv3,
2659 luv2,
2660 luv4,
2661 luv3,
2662 dynamic_tint,
2663 fog_config,
2664 surf,
2665 lm,
2666 fb,
2667 zbuffer,
2668 width,
2669 texture_mapping,
2670 stipple_mode,
2671 screen_tint,
2672 palette_mode,
2673 brightness,
2674 );
2675 }
2676}
2677
2678#[inline(always)]
2679#[allow(clippy::too_many_arguments)]
2680fn fill_lm_bottom_flat<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
2681 p1: nalgebra::Point2<i32>,
2682 p2: nalgebra::Point2<i32>,
2683 p3: nalgebra::Point2<i32>,
2684 z1: u32,
2685 z2: u32,
2686 z3: u32,
2687 w1: f32,
2688 w2: f32,
2689 w3: f32,
2690 uv1: [f32; 2],
2691 uv2: [f32; 2],
2692 uv3: [f32; 2],
2693 luv1: [f32; 2],
2694 luv2: [f32; 2],
2695 luv3: [f32; 2],
2696 dynamic_tint: embedded_graphics_core::pixelcolor::Rgb565,
2697 fog_config: Option<&FogConfig>,
2698 surf: &crate::texture::Texture,
2699 lm: Option<&crate::texture::Texture>,
2700 fb: &mut D,
2701 zbuffer: &mut [u32],
2702 width: usize,
2703 texture_mapping: TextureMapping,
2704 stipple_mode: StippleMode,
2705 screen_tint: Option<ScreenTint>,
2706 palette_mode: PaletteMode,
2707 brightness: u8,
2708) where
2709 <D as DrawTarget>::Error: core::fmt::Debug,
2710{
2711 let height = p2.y - p1.y;
2712 if height == 0 {
2713 return;
2714 }
2715 let invslope1 = ((p2.x - p1.x) << 16) / height;
2716 let invslope2 = ((p3.x - p1.x) << 16) / height;
2717 let mut curx1 = p1.x << 16;
2718 let mut curx2 = p1.x << 16;
2719 for scanline_y in p1.y..=p2.y {
2720 let dy = scanline_y - p1.y;
2721 let t = dy as f32 / height as f32;
2722 let z_l = (z1 as i64 + (z2 as i64 - z1 as i64) * dy as i64 / height as i64) as u32;
2723 let z_r = (z1 as i64 + (z3 as i64 - z1 as i64) * dy as i64 / height as i64) as u32;
2724 let wl = w1 + t * (w2 - w1);
2725 let wr = w1 + t * (w3 - w1);
2726 let uvl = [
2727 uv1[0] + t * (uv2[0] - uv1[0]),
2728 uv1[1] + t * (uv2[1] - uv1[1]),
2729 ];
2730 let uvr = [
2731 uv1[0] + t * (uv3[0] - uv1[0]),
2732 uv1[1] + t * (uv3[1] - uv1[1]),
2733 ];
2734 let luvl = [
2735 luv1[0] + t * (luv2[0] - luv1[0]),
2736 luv1[1] + t * (luv2[1] - luv1[1]),
2737 ];
2738 let luvr = [
2739 luv1[0] + t * (luv3[0] - luv1[0]),
2740 luv1[1] + t * (luv3[1] - luv1[1]),
2741 ];
2742 draw_scanline_lm(
2743 curx1 >> 16,
2744 curx2 >> 16,
2745 scanline_y,
2746 z_l,
2747 z_r,
2748 wl,
2749 wr,
2750 uvl,
2751 uvr,
2752 luvl,
2753 luvr,
2754 dynamic_tint,
2755 fog_config,
2756 surf,
2757 lm,
2758 fb,
2759 zbuffer,
2760 width,
2761 texture_mapping,
2762 stipple_mode,
2763 screen_tint,
2764 palette_mode,
2765 brightness,
2766 );
2767 curx1 += invslope1;
2768 curx2 += invslope2;
2769 }
2770}
2771
2772#[inline(always)]
2773#[allow(clippy::too_many_arguments)]
2774fn fill_lm_top_flat<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
2775 p1: nalgebra::Point2<i32>,
2776 p2: nalgebra::Point2<i32>,
2777 p3: nalgebra::Point2<i32>,
2778 z1: u32,
2779 z2: u32,
2780 z3: u32,
2781 w1: f32,
2782 w2: f32,
2783 w3: f32,
2784 uv1: [f32; 2],
2785 uv2: [f32; 2],
2786 uv3: [f32; 2],
2787 luv1: [f32; 2],
2788 luv2: [f32; 2],
2789 luv3: [f32; 2],
2790 dynamic_tint: embedded_graphics_core::pixelcolor::Rgb565,
2791 fog_config: Option<&FogConfig>,
2792 surf: &crate::texture::Texture,
2793 lm: Option<&crate::texture::Texture>,
2794 fb: &mut D,
2795 zbuffer: &mut [u32],
2796 width: usize,
2797 texture_mapping: TextureMapping,
2798 stipple_mode: StippleMode,
2799 screen_tint: Option<ScreenTint>,
2800 palette_mode: PaletteMode,
2801 brightness: u8,
2802) where
2803 <D as DrawTarget>::Error: core::fmt::Debug,
2804{
2805 let height = p3.y - p1.y;
2806 if height == 0 {
2807 return;
2808 }
2809 let invslope1 = ((p3.x - p1.x) << 16) / height;
2810 let invslope2 = ((p3.x - p2.x) << 16) / height;
2811 let mut curx1 = p3.x << 16;
2812 let mut curx2 = p3.x << 16;
2813 for scanline_y in (p1.y..=p3.y).rev() {
2814 let dy = scanline_y - p1.y;
2815 let t = dy as f32 / height as f32;
2816 let z_l = (z1 as i64 + (z3 as i64 - z1 as i64) * dy as i64 / height as i64) as u32;
2817 let z_r = (z2 as i64 + (z3 as i64 - z2 as i64) * dy as i64 / height as i64) as u32;
2818 let wl = w1 + t * (w3 - w1);
2819 let wr = w2 + t * (w3 - w2);
2820 let uvl = [
2821 uv1[0] + t * (uv3[0] - uv1[0]),
2822 uv1[1] + t * (uv3[1] - uv1[1]),
2823 ];
2824 let uvr = [
2825 uv2[0] + t * (uv3[0] - uv2[0]),
2826 uv2[1] + t * (uv3[1] - uv2[1]),
2827 ];
2828 let luvl = [
2829 luv1[0] + t * (luv3[0] - luv1[0]),
2830 luv1[1] + t * (luv3[1] - luv1[1]),
2831 ];
2832 let luvr = [
2833 luv2[0] + t * (luv3[0] - luv2[0]),
2834 luv2[1] + t * (luv3[1] - luv2[1]),
2835 ];
2836 draw_scanline_lm(
2837 curx1 >> 16,
2838 curx2 >> 16,
2839 scanline_y,
2840 z_l,
2841 z_r,
2842 wl,
2843 wr,
2844 uvl,
2845 uvr,
2846 luvl,
2847 luvr,
2848 dynamic_tint,
2849 fog_config,
2850 surf,
2851 lm,
2852 fb,
2853 zbuffer,
2854 width,
2855 texture_mapping,
2856 stipple_mode,
2857 screen_tint,
2858 palette_mode,
2859 brightness,
2860 );
2861 curx1 -= invslope1;
2862 curx2 -= invslope2;
2863 }
2864}
2865
2866#[inline(always)]
2867#[allow(clippy::too_many_arguments)]
2868fn draw_scanline_lm<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
2869 x1: i32,
2870 x2: i32,
2871 y: i32,
2872 z1: u32,
2873 z2: u32,
2874 w1: f32,
2875 w2: f32,
2876 uv1: [f32; 2],
2877 uv2: [f32; 2],
2878 luv1: [f32; 2],
2879 luv2: [f32; 2],
2880 dynamic_tint: embedded_graphics_core::pixelcolor::Rgb565,
2881 fog_config: Option<&FogConfig>,
2882 surf: &crate::texture::Texture,
2883 lm: Option<&crate::texture::Texture>,
2884 fb: &mut D,
2885 zbuffer: &mut [u32],
2886 width: usize,
2887 texture_mapping: TextureMapping,
2888 stipple_mode: StippleMode,
2889 screen_tint: Option<ScreenTint>,
2890 palette_mode: PaletteMode,
2891 brightness: u8,
2892) where
2893 <D as DrawTarget>::Error: core::fmt::Debug,
2894{
2895 use embedded_graphics_core::pixelcolor::RgbColor;
2896 use embedded_graphics_core::prelude::Point;
2897
2898 if y < 0 {
2899 return;
2900 }
2901 let height = zbuffer.len() / width;
2902 if y as usize >= height {
2903 return;
2904 }
2905
2906 let (left_x, right_x, z_left, z_right, w_left, w_right, uv_left, uv_right, luv_left, luv_right) =
2907 if x1 <= x2 {
2908 (x1, x2, z1, z2, w1, w2, uv1, uv2, luv1, luv2)
2909 } else {
2910 (x2, x1, z2, z1, w2, w1, uv2, uv1, luv2, luv1)
2911 };
2912
2913 let start_x = left_x.max(0);
2914 let end_x = right_x.min(width as i32 - 1);
2915 if start_x > end_x {
2916 return;
2917 }
2918
2919 let span = right_x - left_x;
2920 let inv_span = if span > 0 { 1.0 / span as f32 } else { 0.0 };
2921 let z_step = if span > 0 {
2922 (((z_right as i64 - z_left as i64) << 16) / span as i64) as i32
2923 } else {
2924 0
2925 };
2926
2927 let left_clip = start_x - left_x;
2928 let mut z_curr = ((z_left as i64) << 16) + (left_clip as i64 * z_step as i64);
2929 let mut zbuf_idx = y as usize * width + start_x as usize;
2930
2931 for x in start_x..=end_x {
2932 if should_skip_stipple(x, y, stipple_mode) {
2933 z_curr += z_step as i64;
2934 zbuf_idx += 1;
2935 continue;
2936 }
2937
2938 let z = (z_curr >> 16) as u32;
2939 z_curr += z_step as i64;
2940
2941 if z >= zbuffer[zbuf_idx].saturating_add(DEPTH_EPSILON) {
2942 zbuf_idx += 1;
2943 continue;
2944 }
2945 zbuffer[zbuf_idx] = z;
2946
2947 let t = (x - left_x) as f32 * inv_span;
2948 let [su, sv] = interpolate_uv(t, w_left, w_right, uv_left, uv_right, texture_mapping);
2949 let surf_c = surf.sample(su, sv);
2950
2951 let lit_c = if let Some(lm_tex) = lm {
2952 let [lu, lv] = interpolate_uv(t, w_left, w_right, luv_left, luv_right, texture_mapping);
2953 let lm_c = lm_tex.sample(lu, lv);
2954 let r = ((surf_c.r() as u32 * lm_c.r() as u32) / 31).min(31) as u8;
2955 let g = ((surf_c.g() as u32 * lm_c.g() as u32) / 63).min(63) as u8;
2956 let b = ((surf_c.b() as u32 * lm_c.b() as u32) / 31).min(31) as u8;
2957 embedded_graphics_core::pixelcolor::Rgb565::new(r, g, b)
2958 } else {
2959 surf_c
2960 };
2961
2962 let lit_c = if brightness < 255 {
2963 let scale = brightness as u32;
2964 let r = ((lit_c.r() as u32 * scale) / 255) as u8;
2965 let g = ((lit_c.g() as u32 * scale) / 255) as u8;
2966 let b = ((lit_c.b() as u32 * scale) / 255) as u8;
2967 embedded_graphics_core::pixelcolor::Rgb565::new(r, g, b)
2968 } else {
2969 lit_c
2970 };
2971
2972 let tinted_c = embedded_graphics_core::pixelcolor::Rgb565::new(
2973 (lit_c.r() as u16 + dynamic_tint.r() as u16).min(31) as u8,
2974 (lit_c.g() as u16 + dynamic_tint.g() as u16).min(63) as u8,
2975 (lit_c.b() as u16 + dynamic_tint.b() as u16).min(31) as u8,
2976 );
2977
2978 let mut final_c = if let Some(fog) = fog_config {
2979 fog.apply(tinted_c, z)
2980 } else {
2981 tinted_c
2982 };
2983
2984 if let Some(tint) = screen_tint {
2985 final_c = tint.apply(final_c);
2986 }
2987 final_c = palette_mode.apply(final_c);
2988
2989 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_c)])
2990 .unwrap();
2991 }
2992}
2993
2994pub fn draw_bsp_coverage<
3004 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
3005 const N: usize,
3006>(
3007 mut points: [nalgebra::Point2<i32>; 3],
3008 mut ws: [f32; 3],
3009 mut uvs: [[f32; 2]; 3],
3010 texture_id: u32,
3011 texture_manager: &crate::texture::TextureManager<N>,
3012 fb: &mut D,
3013 coverage: &mut crate::bsp::coverage::CoverageBuffer<'_>,
3014 texture_mapping: TextureMapping,
3015 stipple_mode: StippleMode,
3016 screen_tint: Option<ScreenTint>,
3017 palette_mode: PaletteMode,
3018) where
3019 <D as DrawTarget>::Error: core::fmt::Debug,
3020{
3021 let tex = match texture_manager.get(texture_id) {
3022 Some(t) => t,
3023 None => return,
3024 };
3025
3026 if points[0].y > points[1].y {
3028 points.swap(0, 1);
3029 ws.swap(0, 1);
3030 uvs.swap(0, 1);
3031 }
3032 if points[0].y > points[2].y {
3033 points.swap(0, 2);
3034 ws.swap(0, 2);
3035 uvs.swap(0, 2);
3036 }
3037 if points[1].y > points[2].y {
3038 points.swap(1, 2);
3039 ws.swap(1, 2);
3040 uvs.swap(1, 2);
3041 }
3042
3043 let [p1, p2, p3] = points;
3044 let [w1, w2, w3] = ws;
3045 let [uv1, uv2, uv3] = uvs;
3046
3047 let w = coverage.width as i32;
3048 let h = coverage.height as i32;
3049 if p1.x < 0 && p2.x < 0 && p3.x < 0 {
3050 return;
3051 }
3052 if p1.x >= w && p2.x >= w && p3.x >= w {
3053 return;
3054 }
3055 if p1.y < 0 && p2.y < 0 && p3.y < 0 {
3056 return;
3057 }
3058 if p1.y >= h && p2.y >= h && p3.y >= h {
3059 return;
3060 }
3061
3062 let rasterize_span =
3063 |x1: i32,
3064 x2: i32,
3065 y: i32,
3066 wl: f32,
3067 wr: f32,
3068 uvl: [f32; 2],
3069 uvr: [f32; 2],
3070 fb: &mut D,
3071 coverage: &mut crate::bsp::coverage::CoverageBuffer<'_>| {
3072 let start = x1.min(x2);
3073 let end = x1.max(x2);
3074 let span = end - start;
3075 for x in start..=end {
3076 if x < 0 || y < 0 || x >= w || y >= h {
3077 continue;
3078 }
3079 if coverage.is_covered(x as usize, y as usize) {
3080 continue;
3081 }
3082 if should_skip_stipple(x, y, stipple_mode) {
3083 continue;
3084 }
3085 let t = if span > 0 {
3086 (x - start) as f32 / span as f32
3087 } else {
3088 0.0
3089 };
3090 let [su, sv] = interpolate_uv(t, wl, wr, uvl, uvr, texture_mapping);
3091 let mut color = tex.sample(su, sv);
3092 if let Some(tint) = screen_tint {
3093 color = tint.apply(color);
3094 }
3095 color = palette_mode.apply(color);
3096 coverage.mark_covered(x as usize, y as usize);
3097 fb.draw_iter([embedded_graphics_core::Pixel(
3098 embedded_graphics_core::prelude::Point::new(x, y),
3099 color,
3100 )])
3101 .unwrap();
3102 }
3103 };
3104
3105 let draw_flat_bottom =
3107 |p1: nalgebra::Point2<i32>,
3108 p2: nalgebra::Point2<i32>,
3109 p3: nalgebra::Point2<i32>,
3110 w1: f32,
3111 w2: f32,
3112 w3: f32,
3113 uv1: [f32; 2],
3114 uv2: [f32; 2],
3115 uv3: [f32; 2],
3116 fb: &mut D,
3117 coverage: &mut crate::bsp::coverage::CoverageBuffer<'_>| {
3118 let height = p2.y - p1.y;
3119 if height == 0 {
3120 return;
3121 }
3122 let invslope1 = ((p2.x - p1.x) << 16) / height;
3123 let invslope2 = ((p3.x - p1.x) << 16) / height;
3124 let mut cx1 = p1.x << 16;
3125 let mut cx2 = p1.x << 16;
3126 for sy in p1.y..=p2.y {
3127 let dy = sy - p1.y;
3128 let t = dy as f32 / height as f32;
3129 let wl = w1 + t * (w2 - w1);
3130 let wr = w1 + t * (w3 - w1);
3131 let uvl = [
3132 uv1[0] + t * (uv2[0] - uv1[0]),
3133 uv1[1] + t * (uv2[1] - uv1[1]),
3134 ];
3135 let uvr = [
3136 uv1[0] + t * (uv3[0] - uv1[0]),
3137 uv1[1] + t * (uv3[1] - uv1[1]),
3138 ];
3139 rasterize_span(cx1 >> 16, cx2 >> 16, sy, wl, wr, uvl, uvr, fb, coverage);
3140 cx1 += invslope1;
3141 cx2 += invslope2;
3142 }
3143 };
3144
3145 let draw_flat_top =
3146 |p1: nalgebra::Point2<i32>,
3147 p2: nalgebra::Point2<i32>,
3148 p3: nalgebra::Point2<i32>,
3149 w1: f32,
3150 w2: f32,
3151 w3: f32,
3152 uv1: [f32; 2],
3153 uv2: [f32; 2],
3154 uv3: [f32; 2],
3155 fb: &mut D,
3156 coverage: &mut crate::bsp::coverage::CoverageBuffer<'_>| {
3157 let height = p3.y - p1.y;
3158 if height == 0 {
3159 return;
3160 }
3161 let invslope1 = ((p3.x - p1.x) << 16) / height;
3162 let invslope2 = ((p3.x - p2.x) << 16) / height;
3163 let mut cx1 = p3.x << 16;
3164 let mut cx2 = p3.x << 16;
3165 for sy in (p1.y..=p3.y).rev() {
3166 let dy = sy - p1.y;
3167 let t = dy as f32 / height as f32;
3168 let wl = w1 + t * (w3 - w1);
3169 let wr = w2 + t * (w3 - w2);
3170 let uvl = [
3171 uv1[0] + t * (uv3[0] - uv1[0]),
3172 uv1[1] + t * (uv3[1] - uv1[1]),
3173 ];
3174 let uvr = [
3175 uv2[0] + t * (uv3[0] - uv2[0]),
3176 uv2[1] + t * (uv3[1] - uv2[1]),
3177 ];
3178 rasterize_span(cx1 >> 16, cx2 >> 16, sy, wl, wr, uvl, uvr, fb, coverage);
3179 cx1 -= invslope1;
3180 cx2 -= invslope2;
3181 }
3182 };
3183
3184 if p2.y == p3.y {
3185 draw_flat_bottom(p1, p2, p3, w1, w2, w3, uv1, uv2, uv3, fb, coverage);
3186 } else if p1.y == p2.y {
3187 draw_flat_top(p1, p2, p3, w1, w2, w3, uv1, uv2, uv3, fb, coverage);
3188 } else {
3189 let dy31 = (p3.y - p1.y) as f32;
3190 let dy21 = (p2.y - p1.y) as f32;
3191 let t = dy21 / dy31;
3192 let p4x = p1.x + ((p3.x - p1.x) as f32 * t) as i32;
3193 let p4 = nalgebra::Point2::new(p4x, p2.y);
3194 let w4 = w1 + (w3 - w1) * t;
3195 let uv4 = [
3196 uv1[0] + (uv3[0] - uv1[0]) * t,
3197 uv1[1] + (uv3[1] - uv1[1]) * t,
3198 ];
3199 draw_flat_bottom(p1, p2, p4, w1, w2, w4, uv1, uv2, uv4, fb, coverage);
3200 draw_flat_top(p2, p4, p3, w2, w4, w3, uv2, uv4, uv3, fb, coverage);
3201 }
3202}
3203
3204#[inline(always)]
3205fn fill_triangle_zbuffered<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
3206 p1: nalgebra::Point2<i32>,
3207 p2: nalgebra::Point2<i32>,
3208 p3: nalgebra::Point2<i32>,
3209 z1: f32,
3210 z2: f32,
3211 z3: f32,
3212 color: embedded_graphics_core::pixelcolor::Rgb565,
3213 fb: &mut D,
3214 zbuffer: &mut [u32],
3215 width: usize,
3216 fog_config: Option<&FogConfig>,
3217 dither_config: Option<&DitherConfig>,
3218) where
3219 <D as DrawTarget>::Error: Debug,
3220{
3221 let p1_eg = Point::new(p1.x, p1.y);
3223 let p2_eg = Point::new(p2.x, p2.y);
3224 let p3_eg = Point::new(p3.x, p3.y);
3225
3226 let z1_int = (z1 * 65536.0) as u32;
3229 let z2_int = (z2 * 65536.0) as u32;
3230 let z3_int = (z3 * 65536.0) as u32;
3231
3232 if p2_eg.y == p3_eg.y {
3234 fill_bottom_flat_triangle_zbuffered(
3235 p1_eg,
3236 p2_eg,
3237 p3_eg,
3238 z1_int,
3239 z2_int,
3240 z3_int,
3241 color,
3242 fb,
3243 zbuffer,
3244 width,
3245 fog_config,
3246 dither_config,
3247 );
3248 } else if p1_eg.y == p2_eg.y {
3249 fill_top_flat_triangle_zbuffered(
3250 p1_eg,
3251 p2_eg,
3252 p3_eg,
3253 z1_int,
3254 z2_int,
3255 z3_int,
3256 color,
3257 fb,
3258 zbuffer,
3259 width,
3260 fog_config,
3261 dither_config,
3262 );
3263 } else {
3264 let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
3266 let p4 = Point::new(
3267 (p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
3268 p2_eg.y,
3269 );
3270 let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
3271
3272 fill_bottom_flat_triangle_zbuffered(
3273 p1_eg,
3274 p2_eg,
3275 p4,
3276 z1_int,
3277 z2_int,
3278 z4_int,
3279 color,
3280 fb,
3281 zbuffer,
3282 width,
3283 fog_config,
3284 dither_config,
3285 );
3286 fill_top_flat_triangle_zbuffered(
3287 p2_eg,
3288 p4,
3289 p3_eg,
3290 z2_int,
3291 z4_int,
3292 z3_int,
3293 color,
3294 fb,
3295 zbuffer,
3296 width,
3297 fog_config,
3298 dither_config,
3299 );
3300 }
3301}
3302
3303#[inline(always)]
3305fn fill_triangle_zbuffered_gouraud<
3306 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
3307>(
3308 p1: nalgebra::Point2<i32>,
3309 p2: nalgebra::Point2<i32>,
3310 p3: nalgebra::Point2<i32>,
3311 z1: f32,
3312 z2: f32,
3313 z3: f32,
3314 c1: embedded_graphics_core::pixelcolor::Rgb565,
3315 c2: embedded_graphics_core::pixelcolor::Rgb565,
3316 c3: embedded_graphics_core::pixelcolor::Rgb565,
3317 fb: &mut D,
3318 zbuffer: &mut [u32],
3319 width: usize,
3320 fog_config: Option<&FogConfig>,
3321 dither_config: Option<&DitherConfig>,
3322) where
3323 <D as DrawTarget>::Error: Debug,
3324{
3325 let p1_eg = Point::new(p1.x, p1.y);
3327 let p2_eg = Point::new(p2.x, p2.y);
3328 let p3_eg = Point::new(p3.x, p3.y);
3329
3330 let z1_int = (z1 * 65536.0) as u32;
3332 let z2_int = (z2 * 65536.0) as u32;
3333 let z3_int = (z3 * 65536.0) as u32;
3334
3335 if p2_eg.y == p3_eg.y {
3337 fill_bottom_flat_triangle_zbuffered_gouraud(
3338 p1_eg,
3339 p2_eg,
3340 p3_eg,
3341 z1_int,
3342 z2_int,
3343 z3_int,
3344 c1,
3345 c2,
3346 c3,
3347 fb,
3348 zbuffer,
3349 width,
3350 fog_config,
3351 dither_config,
3352 );
3353 } else if p1_eg.y == p2_eg.y {
3354 fill_top_flat_triangle_zbuffered_gouraud(
3355 p1_eg,
3356 p2_eg,
3357 p3_eg,
3358 z1_int,
3359 z2_int,
3360 z3_int,
3361 c1,
3362 c2,
3363 c3,
3364 fb,
3365 zbuffer,
3366 width,
3367 fog_config,
3368 dither_config,
3369 );
3370 } else {
3371 let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
3373 let p4 = Point::new(
3374 (p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
3375 p2_eg.y,
3376 );
3377 let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
3378 let c4 = interpolate_color(c1, c3, t);
3379
3380 fill_bottom_flat_triangle_zbuffered_gouraud(
3381 p1_eg,
3382 p2_eg,
3383 p4,
3384 z1_int,
3385 z2_int,
3386 z4_int,
3387 c1,
3388 c2,
3389 c4,
3390 fb,
3391 zbuffer,
3392 width,
3393 fog_config,
3394 dither_config,
3395 );
3396 fill_top_flat_triangle_zbuffered_gouraud(
3397 p2_eg,
3398 p4,
3399 p3_eg,
3400 z2_int,
3401 z4_int,
3402 z3_int,
3403 c2,
3404 c4,
3405 c3,
3406 fb,
3407 zbuffer,
3408 width,
3409 fog_config,
3410 dither_config,
3411 );
3412 }
3413}
3414
3415#[inline(always)]
3416fn fill_bottom_flat_triangle_zbuffered<
3417 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
3418>(
3419 p1: Point,
3420 p2: Point,
3421 p3: Point,
3422 z1: u32,
3423 z2: u32,
3424 z3: u32,
3425 color: embedded_graphics_core::pixelcolor::Rgb565,
3426 fb: &mut D,
3427 zbuffer: &mut [u32],
3428 width: usize,
3429 fog_config: Option<&FogConfig>,
3430 dither_config: Option<&DitherConfig>,
3431) where
3432 <D as DrawTarget>::Error: Debug,
3433{
3434 let height = p2.y - p1.y;
3435 if height == 0 {
3436 return;
3437 }
3438
3439 let invslope1 = ((p2.x - p1.x) << 16) / height;
3442 let invslope2 = ((p3.x - p1.x) << 16) / height;
3443
3444 let mut curx1 = p1.x << 16; let mut curx2 = p1.x << 16; let scr_h = (zbuffer.len() / width) as i32;
3450 let y_skip = (0_i32 - p1.y).max(0);
3451 curx1 = curx1.wrapping_add(invslope1.wrapping_mul(y_skip));
3452 curx2 = curx2.wrapping_add(invslope2.wrapping_mul(y_skip));
3453 let y_start = p1.y.max(0);
3454 let y_end = p2.y.min(scr_h - 1);
3455
3456 for scanline_y in y_start..=y_end {
3457 let dy = scanline_y - p1.y;
3458 let z_left = if height > 0 {
3460 (z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
3461 } else {
3462 z1
3463 };
3464 let z_right = if height > 0 {
3465 (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
3466 } else {
3467 z1
3468 };
3469
3470 draw_scanline_zbuffered(
3471 curx1 >> 16, curx2 >> 16, scanline_y,
3474 z_left,
3475 z_right,
3476 color,
3477 fb,
3478 zbuffer,
3479 width,
3480 fog_config,
3481 dither_config,
3482 );
3483
3484 curx1 = curx1.wrapping_add(invslope1);
3485 curx2 = curx2.wrapping_add(invslope2);
3486 }
3487}
3488
3489#[inline(always)]
3490fn fill_top_flat_triangle_zbuffered<
3491 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
3492>(
3493 p1: Point,
3494 p2: Point,
3495 p3: Point,
3496 z1: u32,
3497 z2: u32,
3498 z3: u32,
3499 color: embedded_graphics_core::pixelcolor::Rgb565,
3500 fb: &mut D,
3501 zbuffer: &mut [u32],
3502 width: usize,
3503 fog_config: Option<&FogConfig>,
3504 dither_config: Option<&DitherConfig>,
3505) where
3506 <D as DrawTarget>::Error: Debug,
3507{
3508 let height = p3.y - p1.y;
3509 if height == 0 {
3510 return;
3511 }
3512
3513 let invslope1 = ((p3.x - p1.x) << 16) / height;
3515 let invslope2 = ((p3.x - p2.x) << 16) / height;
3516
3517 let mut curx1 = p3.x << 16; let mut curx2 = p3.x << 16; let scr_h = (zbuffer.len() / width) as i32;
3526 let y_skip_bot = (p3.y - (scr_h - 1)).max(0);
3527 curx1 = curx1.wrapping_sub(invslope1.wrapping_mul(y_skip_bot));
3528 curx2 = curx2.wrapping_sub(invslope2.wrapping_mul(y_skip_bot));
3529 let y_start = p1.y.max(0);
3530 let y_end = p3.y.min(scr_h - 1);
3531
3532 for scanline_y in (y_start..=y_end).rev() {
3533 let dy = scanline_y - p1.y;
3534 let z_left = if height > 0 {
3536 (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
3537 } else {
3538 z1
3539 };
3540 let z_right = if height > 0 {
3541 (z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32
3542 } else {
3543 z2
3544 };
3545
3546 draw_scanline_zbuffered(
3547 curx1 >> 16, curx2 >> 16, scanline_y,
3550 z_left,
3551 z_right,
3552 color,
3553 fb,
3554 zbuffer,
3555 width,
3556 fog_config,
3557 dither_config,
3558 );
3559
3560 curx1 = curx1.wrapping_sub(invslope1);
3561 curx2 = curx2.wrapping_sub(invslope2);
3562 }
3563}
3564
3565#[inline(always)]
3567fn fill_bottom_flat_triangle_zbuffered_gouraud<
3568 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
3569>(
3570 p1: Point,
3571 p2: Point,
3572 p3: Point,
3573 z1: u32,
3574 z2: u32,
3575 z3: u32,
3576 c1: embedded_graphics_core::pixelcolor::Rgb565,
3577 c2: embedded_graphics_core::pixelcolor::Rgb565,
3578 c3: embedded_graphics_core::pixelcolor::Rgb565,
3579 fb: &mut D,
3580 zbuffer: &mut [u32],
3581 width: usize,
3582 fog_config: Option<&FogConfig>,
3583 dither_config: Option<&DitherConfig>,
3584) where
3585 <D as DrawTarget>::Error: Debug,
3586{
3587 let height = p2.y - p1.y;
3588 if height == 0 {
3589 return;
3590 }
3591
3592 let invslope1 = ((p2.x - p1.x) << 16) / height;
3593 let invslope2 = ((p3.x - p1.x) << 16) / height;
3594
3595 let mut curx1 = p1.x << 16;
3596 let mut curx2 = p1.x << 16;
3597
3598 for scanline_y in p1.y..=p2.y {
3599 let dy = scanline_y - p1.y;
3600 let t = dy as f32 / height as f32;
3601
3602 let z_left = if height > 0 {
3604 (z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
3605 } else {
3606 z1
3607 };
3608 let z_right = if height > 0 {
3609 (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
3610 } else {
3611 z1
3612 };
3613
3614 let color_left = interpolate_color(c1, c2, t);
3616 let color_right = interpolate_color(c1, c3, t);
3617
3618 draw_scanline_zbuffered_gouraud(
3619 curx1 >> 16,
3620 curx2 >> 16,
3621 scanline_y,
3622 z_left,
3623 z_right,
3624 color_left,
3625 color_right,
3626 fb,
3627 zbuffer,
3628 width,
3629 fog_config,
3630 dither_config,
3631 );
3632
3633 curx1 += invslope1;
3634 curx2 += invslope2;
3635 }
3636}
3637
3638#[inline(always)]
3640fn fill_top_flat_triangle_zbuffered_gouraud<
3641 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
3642>(
3643 p1: Point,
3644 p2: Point,
3645 p3: Point,
3646 z1: u32,
3647 z2: u32,
3648 z3: u32,
3649 c1: embedded_graphics_core::pixelcolor::Rgb565,
3650 c2: embedded_graphics_core::pixelcolor::Rgb565,
3651 c3: embedded_graphics_core::pixelcolor::Rgb565,
3652 fb: &mut D,
3653 zbuffer: &mut [u32],
3654 width: usize,
3655 fog_config: Option<&FogConfig>,
3656 dither_config: Option<&DitherConfig>,
3657) where
3658 <D as DrawTarget>::Error: Debug,
3659{
3660 let height = p3.y - p1.y;
3661 if height == 0 {
3662 return;
3663 }
3664
3665 let invslope1 = ((p3.x - p1.x) << 16) / height;
3666 let invslope2 = ((p3.x - p2.x) << 16) / height;
3667
3668 let mut curx1 = p3.x << 16;
3669 let mut curx2 = p3.x << 16;
3670
3671 for scanline_y in (p1.y..=p3.y).rev() {
3672 let dy = scanline_y - p1.y;
3673 let t = dy as f32 / height as f32;
3674
3675 let z_left = if height > 0 {
3677 (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
3678 } else {
3679 z1
3680 };
3681 let z_right = if height > 0 {
3682 (z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32
3683 } else {
3684 z2
3685 };
3686
3687 let color_left = interpolate_color(c1, c3, t);
3689 let color_right = interpolate_color(c2, c3, t);
3690
3691 draw_scanline_zbuffered_gouraud(
3692 curx1 >> 16,
3693 curx2 >> 16,
3694 scanline_y,
3695 z_left,
3696 z_right,
3697 color_left,
3698 color_right,
3699 fb,
3700 zbuffer,
3701 width,
3702 fog_config,
3703 dither_config,
3704 );
3705
3706 curx1 -= invslope1;
3707 curx2 -= invslope2;
3708 }
3709}
3710
3711#[inline(always)]
3713fn draw_scanline_zbuffered_gouraud<
3714 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
3715>(
3716 x1: i32,
3717 x2: i32,
3718 y: i32,
3719 z1: u32,
3720 z2: u32,
3721 color1: embedded_graphics_core::pixelcolor::Rgb565,
3722 color2: embedded_graphics_core::pixelcolor::Rgb565,
3723 fb: &mut D,
3724 zbuffer: &mut [u32],
3725 width: usize,
3726 fog_config: Option<&FogConfig>,
3727 dither_config: Option<&DitherConfig>,
3728) where
3729 <D as DrawTarget>::Error: Debug,
3730{
3731 if y < 0 {
3732 return;
3733 }
3734 let height = zbuffer.len() / width;
3735 if y as usize >= height {
3736 return;
3737 }
3738
3739 let (left_x, right_x, z_left, z_right, c_left, c_right) = if x1 <= x2 {
3740 (x1, x2, z1, z2, color1, color2)
3741 } else {
3742 (x2, x1, z2, z1, color2, color1)
3743 };
3744
3745 let start_x = left_x.max(0);
3746 let end_x = right_x.min(width as i32 - 1);
3747 if start_x > end_x {
3748 return;
3749 }
3750
3751 let span = right_x - left_x;
3752 let inv_span = if span > 0 { 1.0 / span as f32 } else { 0.0 };
3753 let z_step = if span > 0 {
3754 (((z_right as i64 - z_left as i64) << 16) / span as i64) as i32
3755 } else {
3756 0
3757 };
3758
3759 let left_clip = start_x - left_x;
3760 let mut z_curr = ((z_left as i64) << 16) + (left_clip as i64 * z_step as i64);
3761 let mut zbuf_idx = y as usize * width + start_x as usize;
3762
3763 for x in start_x..=end_x {
3764 let z = (z_curr >> 16) as u32;
3765 z_curr += z_step as i64;
3766
3767 if z < zbuffer[zbuf_idx].saturating_add(DEPTH_EPSILON) {
3768 zbuffer[zbuf_idx] = z;
3769
3770 let t = (x - left_x) as f32 * inv_span;
3771 let mut final_color = interpolate_color(c_left, c_right, t);
3772
3773 if let Some(fog) = fog_config {
3774 final_color = fog.apply(final_color, z);
3775 }
3776
3777 if let Some(dither) = dither_config {
3778 final_color = dither.apply(final_color, x, y);
3779 }
3780
3781 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_color)])
3782 .unwrap();
3783 }
3784 zbuf_idx += 1;
3785 }
3786}
3787
3788#[inline(always)]
3789fn draw_scanline_zbuffered<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
3790 x1: i32,
3791 x2: i32,
3792 y: i32,
3793 z1: u32,
3794 z2: u32,
3795 color: embedded_graphics_core::pixelcolor::Rgb565,
3796 fb: &mut D,
3797 zbuffer: &mut [u32],
3798 width: usize,
3799 fog_config: Option<&FogConfig>,
3800 dither_config: Option<&DitherConfig>,
3801) where
3802 <D as DrawTarget>::Error: Debug,
3803{
3804 if y < 0 {
3805 return;
3806 }
3807 let height = zbuffer.len() / width;
3808 if y as usize >= height {
3809 return;
3810 }
3811
3812 let (left_x, right_x, z_left, z_right) = if x1 <= x2 {
3813 (x1, x2, z1, z2)
3814 } else {
3815 (x2, x1, z2, z1)
3816 };
3817
3818 let start_x = left_x.max(0);
3819 let end_x = right_x.min(width as i32 - 1);
3820 if start_x > end_x {
3821 return;
3822 }
3823
3824 let span = right_x - left_x;
3825 let z_step = if span > 0 {
3826 (((z_right as i64 - z_left as i64) << 16) / span as i64) as i32
3827 } else {
3828 0
3829 };
3830
3831 let left_clip = start_x - left_x;
3832 let mut z_curr = ((z_left as i64) << 16) + (left_clip as i64 * z_step as i64);
3833 let mut zbuf_idx = y as usize * width + start_x as usize;
3834
3835 for x in start_x..=end_x {
3836 let z = (z_curr >> 16) as u32;
3837 z_curr += z_step as i64;
3838
3839 if z < zbuffer[zbuf_idx].saturating_add(DEPTH_EPSILON) {
3840 zbuffer[zbuf_idx] = z;
3841
3842 let mut final_color = color;
3843
3844 if let Some(fog) = fog_config {
3845 final_color = fog.apply(final_color, z);
3846 }
3847
3848 if let Some(dither) = dither_config {
3849 final_color = dither.apply(final_color, x, y);
3850 }
3851
3852 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_color)])
3853 .unwrap();
3854 }
3855 zbuf_idx += 1;
3856 }
3857}
3858
3859#[inline(always)]
3861fn fill_triangle_zbuffered_textured<
3862 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
3863>(
3864 p1: nalgebra::Point2<i32>,
3865 p2: nalgebra::Point2<i32>,
3866 p3: nalgebra::Point2<i32>,
3867 z1: f32,
3868 z2: f32,
3869 z3: f32,
3870 w1: f32,
3871 w2: f32,
3872 w3: f32,
3873 uv1: [f32; 2],
3874 uv2: [f32; 2],
3875 uv3: [f32; 2],
3876 texture: &crate::texture::Texture,
3877 fb: &mut D,
3878 zbuffer: &mut [u32],
3879 width: usize,
3880 fog_config: Option<&FogConfig>,
3881 dither_config: Option<&DitherConfig>,
3882 texture_mapping: TextureMapping,
3883 stipple_mode: StippleMode,
3884 screen_tint: Option<ScreenTint>,
3885 palette_mode: PaletteMode,
3886) where
3887 <D as DrawTarget>::Error: Debug,
3888{
3889 let p1_eg = Point::new(p1.x, p1.y);
3891 let p2_eg = Point::new(p2.x, p2.y);
3892 let p3_eg = Point::new(p3.x, p3.y);
3893
3894 let z1_int = (z1 * 65536.0) as u32;
3896 let z2_int = (z2 * 65536.0) as u32;
3897 let z3_int = (z3 * 65536.0) as u32;
3898
3899 if p2_eg.y == p3_eg.y {
3901 fill_bottom_flat_triangle_zbuffered_textured(
3902 p1_eg,
3903 p2_eg,
3904 p3_eg,
3905 z1_int,
3906 z2_int,
3907 z3_int,
3908 w1,
3909 w2,
3910 w3,
3911 uv1,
3912 uv2,
3913 uv3,
3914 texture,
3915 fb,
3916 zbuffer,
3917 width,
3918 fog_config,
3919 dither_config,
3920 texture_mapping,
3921 stipple_mode,
3922 screen_tint,
3923 palette_mode,
3924 );
3925 } else if p1_eg.y == p2_eg.y {
3926 fill_top_flat_triangle_zbuffered_textured(
3927 p1_eg,
3928 p2_eg,
3929 p3_eg,
3930 z1_int,
3931 z2_int,
3932 z3_int,
3933 w1,
3934 w2,
3935 w3,
3936 uv1,
3937 uv2,
3938 uv3,
3939 texture,
3940 fb,
3941 zbuffer,
3942 width,
3943 fog_config,
3944 dither_config,
3945 texture_mapping,
3946 stipple_mode,
3947 screen_tint,
3948 palette_mode,
3949 );
3950 } else {
3951 let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
3953 let p4 = Point::new(
3954 (p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
3955 p2_eg.y,
3956 );
3957 let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
3958 let w4 = w1 + t * (w3 - w1);
3960 let uv4 = [
3962 uv1[0] + t * (uv3[0] - uv1[0]),
3963 uv1[1] + t * (uv3[1] - uv1[1]),
3964 ];
3965
3966 fill_bottom_flat_triangle_zbuffered_textured(
3967 p1_eg,
3968 p2_eg,
3969 p4,
3970 z1_int,
3971 z2_int,
3972 z4_int,
3973 w1,
3974 w2,
3975 w4,
3976 uv1,
3977 uv2,
3978 uv4,
3979 texture,
3980 fb,
3981 zbuffer,
3982 width,
3983 fog_config,
3984 dither_config,
3985 texture_mapping,
3986 stipple_mode,
3987 screen_tint,
3988 palette_mode,
3989 );
3990 fill_top_flat_triangle_zbuffered_textured(
3991 p2_eg,
3992 p4,
3993 p3_eg,
3994 z2_int,
3995 z4_int,
3996 z3_int,
3997 w2,
3998 w4,
3999 w3,
4000 uv2,
4001 uv4,
4002 uv3,
4003 texture,
4004 fb,
4005 zbuffer,
4006 width,
4007 fog_config,
4008 dither_config,
4009 texture_mapping,
4010 stipple_mode,
4011 screen_tint,
4012 palette_mode,
4013 );
4014 }
4015}
4016
4017#[inline(always)]
4019fn fill_bottom_flat_triangle_zbuffered_textured<
4020 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
4021>(
4022 p1: Point,
4023 p2: Point,
4024 p3: Point,
4025 z1: u32,
4026 z2: u32,
4027 z3: u32,
4028 w1: f32,
4029 w2: f32,
4030 w3: f32,
4031 uv1: [f32; 2],
4032 uv2: [f32; 2],
4033 uv3: [f32; 2],
4034 texture: &crate::texture::Texture,
4035 fb: &mut D,
4036 zbuffer: &mut [u32],
4037 width: usize,
4038 fog_config: Option<&FogConfig>,
4039 dither_config: Option<&DitherConfig>,
4040 texture_mapping: TextureMapping,
4041 stipple_mode: StippleMode,
4042 screen_tint: Option<ScreenTint>,
4043 palette_mode: PaletteMode,
4044) where
4045 <D as DrawTarget>::Error: Debug,
4046{
4047 let height = p2.y - p1.y;
4048 if height == 0 {
4049 return;
4050 }
4051
4052 let invslope1 = ((p2.x - p1.x) << 16) / height;
4053 let invslope2 = ((p3.x - p1.x) << 16) / height;
4054
4055 let mut curx1 = p1.x << 16;
4056 let mut curx2 = p1.x << 16;
4057
4058 for scanline_y in p1.y..=p2.y {
4059 let dy = scanline_y - p1.y;
4060 let t = dy as f32 / height as f32;
4061
4062 let z_left = if height > 0 {
4064 (z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
4065 } else {
4066 z1
4067 };
4068 let z_right = if height > 0 {
4069 (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
4070 } else {
4071 z1
4072 };
4073
4074 let w_left = w1 + t * (w2 - w1);
4076 let w_right = w1 + t * (w3 - w1);
4077
4078 let uv_left = [
4080 uv1[0] + t * (uv2[0] - uv1[0]),
4081 uv1[1] + t * (uv2[1] - uv1[1]),
4082 ];
4083 let uv_right = [
4084 uv1[0] + t * (uv3[0] - uv1[0]),
4085 uv1[1] + t * (uv3[1] - uv1[1]),
4086 ];
4087
4088 draw_scanline_zbuffered_textured(
4089 curx1 >> 16,
4090 curx2 >> 16,
4091 scanline_y,
4092 z_left,
4093 z_right,
4094 w_left,
4095 w_right,
4096 uv_left,
4097 uv_right,
4098 texture,
4099 fb,
4100 zbuffer,
4101 width,
4102 fog_config,
4103 dither_config,
4104 texture_mapping,
4105 stipple_mode,
4106 screen_tint,
4107 palette_mode,
4108 );
4109
4110 curx1 += invslope1;
4111 curx2 += invslope2;
4112 }
4113}
4114
4115#[inline(always)]
4117fn fill_top_flat_triangle_zbuffered_textured<
4118 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
4119>(
4120 p1: Point,
4121 p2: Point,
4122 p3: Point,
4123 z1: u32,
4124 z2: u32,
4125 z3: u32,
4126 w1: f32,
4127 w2: f32,
4128 w3: f32,
4129 uv1: [f32; 2],
4130 uv2: [f32; 2],
4131 uv3: [f32; 2],
4132 texture: &crate::texture::Texture,
4133 fb: &mut D,
4134 zbuffer: &mut [u32],
4135 width: usize,
4136 fog_config: Option<&FogConfig>,
4137 dither_config: Option<&DitherConfig>,
4138 texture_mapping: TextureMapping,
4139 stipple_mode: StippleMode,
4140 screen_tint: Option<ScreenTint>,
4141 palette_mode: PaletteMode,
4142) where
4143 <D as DrawTarget>::Error: Debug,
4144{
4145 let height = p3.y - p1.y;
4146 if height == 0 {
4147 return;
4148 }
4149
4150 let invslope1 = ((p3.x - p1.x) << 16) / height;
4151 let invslope2 = ((p3.x - p2.x) << 16) / height;
4152
4153 let mut curx1 = p3.x << 16;
4154 let mut curx2 = p3.x << 16;
4155
4156 for scanline_y in (p1.y..=p3.y).rev() {
4157 let dy = scanline_y - p1.y;
4158 let t = dy as f32 / height as f32;
4159
4160 let z_left = if height > 0 {
4162 (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
4163 } else {
4164 z1
4165 };
4166 let z_right = if height > 0 {
4167 (z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32
4168 } else {
4169 z2
4170 };
4171
4172 let w_left = w1 + t * (w3 - w1);
4174 let w_right = w2 + t * (w3 - w2);
4175
4176 let uv_left = [
4178 uv1[0] + t * (uv3[0] - uv1[0]),
4179 uv1[1] + t * (uv3[1] - uv1[1]),
4180 ];
4181 let uv_right = [
4182 uv2[0] + t * (uv3[0] - uv2[0]),
4183 uv2[1] + t * (uv3[1] - uv2[1]),
4184 ];
4185
4186 draw_scanline_zbuffered_textured(
4187 curx1 >> 16,
4188 curx2 >> 16,
4189 scanline_y,
4190 z_left,
4191 z_right,
4192 w_left,
4193 w_right,
4194 uv_left,
4195 uv_right,
4196 texture,
4197 fb,
4198 zbuffer,
4199 width,
4200 fog_config,
4201 dither_config,
4202 texture_mapping,
4203 stipple_mode,
4204 screen_tint,
4205 palette_mode,
4206 );
4207
4208 curx1 -= invslope1;
4209 curx2 -= invslope2;
4210 }
4211}
4212
4213#[inline(always)]
4215fn draw_scanline_zbuffered_textured<
4216 D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
4217>(
4218 x1: i32,
4219 x2: i32,
4220 y: i32,
4221 z1: u32,
4222 z2: u32,
4223 w1: f32,
4224 w2: f32,
4225 uv1: [f32; 2],
4226 uv2: [f32; 2],
4227 texture: &crate::texture::Texture,
4228 fb: &mut D,
4229 zbuffer: &mut [u32],
4230 width: usize,
4231 fog_config: Option<&FogConfig>,
4232 dither_config: Option<&DitherConfig>,
4233 texture_mapping: TextureMapping,
4234 stipple_mode: StippleMode,
4235 screen_tint: Option<ScreenTint>,
4236 palette_mode: PaletteMode,
4237) where
4238 <D as DrawTarget>::Error: Debug,
4239{
4240 if y < 0 {
4241 return;
4242 }
4243 let height = zbuffer.len() / width;
4244 if y as usize >= height {
4245 return;
4246 }
4247
4248 let (left_x, right_x, z_left, z_right, w_left, w_right, uv_left, uv_right) = if x1 <= x2 {
4249 (x1, x2, z1, z2, w1, w2, uv1, uv2)
4250 } else {
4251 (x2, x1, z2, z1, w2, w1, uv2, uv1)
4252 };
4253
4254 let start_x = left_x.max(0);
4255 let end_x = right_x.min(width as i32 - 1);
4256 if start_x > end_x {
4257 return;
4258 }
4259
4260 let span = right_x - left_x;
4261 let inv_span = if span > 0 { 1.0 / span as f32 } else { 0.0 };
4262 let z_step = if span > 0 {
4263 (((z_right as i64 - z_left as i64) << 16) / span as i64) as i32
4264 } else {
4265 0
4266 };
4267
4268 let left_clip = start_x - left_x;
4269 let mut z_curr = ((z_left as i64) << 16) + (left_clip as i64 * z_step as i64);
4270 let mut zbuf_idx = y as usize * width + start_x as usize;
4271
4272 const SUB_SPAN_SIZE: i32 = 16;
4275
4276 let mut span_x = start_x;
4277 while span_x <= end_x {
4278 let next_span_x = (span_x + SUB_SPAN_SIZE).min(end_x + 1);
4279 let span_len = next_span_x - span_x;
4280
4281 let t_start = (span_x - left_x) as f32 * inv_span;
4282 let t_end = (next_span_x - 1 - left_x) as f32 * inv_span;
4283
4284 let [u_start, v_start] =
4285 interpolate_uv(t_start, w_left, w_right, uv_left, uv_right, texture_mapping);
4286 let [u_end, v_end] =
4287 interpolate_uv(t_end, w_left, w_right, uv_left, uv_right, texture_mapping);
4288
4289 let inv_sub = if span_len > 1 {
4290 1.0 / (span_len - 1) as f32
4291 } else {
4292 0.0
4293 };
4294 let du = (u_end - u_start) * inv_sub;
4295 let dv = (v_end - v_start) * inv_sub;
4296
4297 let mut curr_u = u_start;
4298 let mut curr_v = v_start;
4299
4300 for x in span_x..next_span_x {
4301 if should_skip_stipple(x, y, stipple_mode) {
4302 z_curr += z_step as i64;
4303 zbuf_idx += 1;
4304 curr_u += du;
4305 curr_v += dv;
4306 continue;
4307 }
4308
4309 let z = (z_curr >> 16) as u32;
4310 z_curr += z_step as i64;
4311
4312 if z < zbuffer[zbuf_idx].saturating_add(DEPTH_EPSILON) {
4313 zbuffer[zbuf_idx] = z;
4314
4315 let mut final_color = texture.sample(curr_u, curr_v);
4317
4318 if let Some(fog) = fog_config {
4320 final_color = fog.apply(final_color, z);
4321 }
4322
4323 if let Some(dither) = dither_config {
4324 final_color = dither.apply(final_color, x, y);
4325 }
4326 if let Some(tint) = screen_tint {
4327 final_color = tint.apply(final_color);
4328 }
4329 final_color = palette_mode.apply(final_color);
4330
4331 fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_color)])
4332 .unwrap();
4333 }
4334 zbuf_idx += 1;
4335 curr_u += du;
4336 curr_v += dv;
4337 }
4338
4339 span_x = next_span_x;
4340 }
4341}
4342
4343#[inline(always)]
4344fn fill_triangle_zbuffered_translucent<D: DrawTarget<Color = Rgb565>>(
4345 p1: nalgebra::Point2<i32>,
4346 p2: nalgebra::Point2<i32>,
4347 p3: nalgebra::Point2<i32>,
4348 z1: f32,
4349 z2: f32,
4350 z3: f32,
4351 color: Rgb565,
4352 alpha: u8,
4353 fb: &mut D,
4354 zbuffer: &mut [u32],
4355 width: usize,
4356) where
4357 <D as DrawTarget>::Error: Debug,
4358{
4359 let p1_eg = Point::new(p1.x, p1.y);
4360 let p2_eg = Point::new(p2.x, p2.y);
4361 let p3_eg = Point::new(p3.x, p3.y);
4362
4363 let z1_int = (z1 * 65536.0) as u32;
4364 let z2_int = (z2 * 65536.0) as u32;
4365 let z3_int = (z3 * 65536.0) as u32;
4366
4367 if p2_eg.y == p3_eg.y {
4368 fill_bottom_flat_translucent(
4369 p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, alpha, fb, zbuffer, width,
4370 );
4371 } else if p1_eg.y == p2_eg.y {
4372 fill_top_flat_translucent(
4373 p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, alpha, fb, zbuffer, width,
4374 );
4375 } else {
4376 let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
4377 let p4 = Point::new(
4378 (p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
4379 p2_eg.y,
4380 );
4381 let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
4382 fill_bottom_flat_translucent(
4383 p1_eg, p2_eg, p4, z1_int, z2_int, z4_int, color, alpha, fb, zbuffer, width,
4384 );
4385 fill_top_flat_translucent(
4386 p2_eg, p4, p3_eg, z2_int, z4_int, z3_int, color, alpha, fb, zbuffer, width,
4387 );
4388 }
4389}
4390
4391#[inline(always)]
4392fn fill_bottom_flat_translucent<D: DrawTarget<Color = Rgb565>>(
4393 p1: Point,
4394 p2: Point,
4395 p3: Point,
4396 z1: u32,
4397 z2: u32,
4398 z3: u32,
4399 color: Rgb565,
4400 alpha: u8,
4401 fb: &mut D,
4402 zbuffer: &mut [u32],
4403 width: usize,
4404) where
4405 <D as DrawTarget>::Error: Debug,
4406{
4407 let height = p2.y - p1.y;
4408 if height == 0 {
4409 return;
4410 }
4411 let invslope1 = ((p2.x - p1.x) << 16) / height;
4412 let invslope2 = ((p3.x - p1.x) << 16) / height;
4413
4414 let mut curx1 = p1.x << 16;
4415 let mut curx2 = p1.x << 16;
4416
4417 for scanline_y in p1.y..=p2.y {
4418 let dy = scanline_y - p1.y;
4419 let z_left = (z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
4420 let z_right = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
4421
4422 let (left_x, right_x, z_l, z_r) = if curx1 <= curx2 {
4423 (curx1 >> 16, curx2 >> 16, z_left, z_right)
4424 } else {
4425 (curx2 >> 16, curx1 >> 16, z_right, z_left)
4426 };
4427
4428 let span = right_x - left_x;
4429 for x in left_x..=right_x {
4430 if x < 0 || scanline_y < 0 || x >= width as i32 {
4431 continue;
4432 }
4433 let idx = (scanline_y as usize) * width + (x as usize);
4434 if idx >= zbuffer.len() {
4435 continue;
4436 }
4437
4438 let z = if span > 0 {
4439 let t = (x - left_x) as f32 / span as f32;
4440 (z_l as f32 + t * (z_r as f32 - z_l as f32)) as u32
4441 } else {
4442 z_l
4443 };
4444
4445 if z < zbuffer[idx] {
4446 zbuffer[idx] = z;
4447 let draw_color = fast_blend_rgb565(Rgb565::BLACK, color, alpha);
4448 let _ = fb.draw_iter([embedded_graphics_core::Pixel(
4449 Point::new(x, scanline_y),
4450 draw_color,
4451 )]);
4452 }
4453 }
4454
4455 curx1 += invslope1;
4456 curx2 += invslope2;
4457 }
4458}
4459
4460#[inline(always)]
4461fn fill_top_flat_translucent<D: DrawTarget<Color = Rgb565>>(
4462 p1: Point,
4463 p2: Point,
4464 p3: Point,
4465 z1: u32,
4466 z2: u32,
4467 z3: u32,
4468 color: Rgb565,
4469 alpha: u8,
4470 fb: &mut D,
4471 zbuffer: &mut [u32],
4472 width: usize,
4473) where
4474 <D as DrawTarget>::Error: Debug,
4475{
4476 let height = p3.y - p1.y;
4477 if height == 0 {
4478 return;
4479 }
4480 let invslope1 = ((p3.x - p1.x) << 16) / height;
4481 let invslope2 = ((p3.x - p2.x) << 16) / height;
4482
4483 let mut curx1 = p3.x << 16;
4484 let mut curx2 = p3.x << 16;
4485
4486 for scanline_y in (p1.y..=p3.y).rev() {
4487 let dy = scanline_y - p1.y;
4488 let z_left = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
4489 let z_right = (z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32;
4490
4491 let (left_x, right_x, z_l, z_r) = if curx1 <= curx2 {
4492 (curx1 >> 16, curx2 >> 16, z_left, z_right)
4493 } else {
4494 (curx2 >> 16, curx1 >> 16, z_right, z_left)
4495 };
4496
4497 let span = right_x - left_x;
4498 for x in left_x..=right_x {
4499 if x < 0 || scanline_y < 0 || x >= width as i32 {
4500 continue;
4501 }
4502 let idx = (scanline_y as usize) * width + (x as usize);
4503 if idx >= zbuffer.len() {
4504 continue;
4505 }
4506
4507 let z = if span > 0 {
4508 let t = (x - left_x) as f32 / span as f32;
4509 (z_l as f32 + t * (z_r as f32 - z_l as f32)) as u32
4510 } else {
4511 z_l
4512 };
4513
4514 if z < zbuffer[idx] {
4515 zbuffer[idx] = z;
4516 let draw_color = fast_blend_rgb565(Rgb565::BLACK, color, alpha);
4517 let _ = fb.draw_iter([embedded_graphics_core::Pixel(
4518 Point::new(x, scanline_y),
4519 draw_color,
4520 )]);
4521 }
4522 }
4523
4524 curx1 -= invslope1;
4525 curx2 -= invslope2;
4526 }
4527}
4528
4529#[cfg(test)]
4530mod tests {
4531 extern crate std;
4532 use super::*;
4533 use embedded_graphics_core::pixelcolor::Rgb565;
4534 use embedded_graphics_core::prelude::*;
4535 use nalgebra::Point2;
4536
4537 struct MockFramebuffer {
4539 pixels: std::vec::Vec<(i32, i32, Rgb565)>,
4540 }
4541
4542 impl MockFramebuffer {
4543 fn new() -> Self {
4544 Self {
4545 pixels: std::vec::Vec::new(),
4546 }
4547 }
4548
4549 fn contains_pixel(&self, x: i32, y: i32) -> bool {
4550 self.pixels.iter().any(|(px, py, _)| *px == x && *py == y)
4551 }
4552
4553 fn pixel_count(&self) -> usize {
4554 self.pixels.len()
4555 }
4556 }
4557
4558 impl DrawTarget for MockFramebuffer {
4559 type Color = Rgb565;
4560 type Error = core::convert::Infallible;
4561
4562 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
4563 where
4564 I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>,
4565 {
4566 for pixel in pixels {
4567 self.pixels.push((pixel.0.x, pixel.0.y, pixel.1));
4568 }
4569 Ok(())
4570 }
4571 }
4572
4573 impl OriginDimensions for MockFramebuffer {
4574 fn size(&self) -> Size {
4575 Size::new(640, 480)
4576 }
4577 }
4578
4579 #[test]
4580 fn test_draw_point() {
4581 let mut fb = MockFramebuffer::new();
4582 let point = Point2::new(10, 20);
4583 let color = Rgb565::CSS_RED;
4584
4585 draw(DrawPrimitive::ColoredPoint(point, color), &mut fb);
4586
4587 assert_eq!(fb.pixel_count(), 1);
4588 assert!(fb.contains_pixel(10, 20));
4589 }
4590
4591 #[test]
4592 fn test_draw_line_horizontal() {
4593 let mut fb = MockFramebuffer::new();
4594 let p1 = Point2::new(10, 20);
4595 let p2 = Point2::new(20, 20);
4596 let color = Rgb565::CSS_GREEN;
4597
4598 draw(DrawPrimitive::Line([p1, p2], color), &mut fb);
4599
4600 assert!(fb.pixel_count() >= 10); assert!(fb.contains_pixel(10, 20));
4603 assert!(fb.contains_pixel(20, 20));
4604 }
4605
4606 #[test]
4607 fn test_draw_line_vertical() {
4608 let mut fb = MockFramebuffer::new();
4609 let p1 = Point2::new(10, 10);
4610 let p2 = Point2::new(10, 20);
4611 let color = Rgb565::CSS_BLUE;
4612
4613 draw(DrawPrimitive::Line([p1, p2], color), &mut fb);
4614
4615 assert!(fb.pixel_count() >= 10);
4617 assert!(fb.contains_pixel(10, 10));
4618 assert!(fb.contains_pixel(10, 20));
4619 }
4620
4621 #[test]
4622 fn test_draw_line_diagonal() {
4623 let mut fb = MockFramebuffer::new();
4624 let p1 = Point2::new(0, 0);
4625 let p2 = Point2::new(10, 10);
4626 let color = Rgb565::CSS_WHITE;
4627
4628 draw(DrawPrimitive::Line([p1, p2], color), &mut fb);
4629
4630 assert!(fb.pixel_count() >= 10);
4632 assert!(fb.contains_pixel(0, 0));
4633 assert!(fb.contains_pixel(10, 10));
4634 }
4635
4636 #[test]
4637 fn test_draw_triangle_flat_bottom() {
4638 let mut fb = MockFramebuffer::new();
4639 let vertices = [
4640 Point2::new(50, 10), Point2::new(30, 30), Point2::new(70, 30), ];
4644 let color = Rgb565::CSS_YELLOW;
4645
4646 draw(DrawPrimitive::ColoredTriangle(vertices, color), &mut fb);
4647
4648 let count = fb.pixel_count();
4650 assert!(count > 0, "Expected pixels to be drawn, got {}", count);
4651 assert!(fb.contains_pixel(50, 10));
4653 }
4654
4655 #[test]
4656 fn test_draw_triangle_flat_top() {
4657 let mut fb = MockFramebuffer::new();
4658 let vertices = [
4659 Point2::new(30, 10), Point2::new(70, 10), Point2::new(50, 30), ];
4663 let color = Rgb565::CSS_CYAN;
4664
4665 draw(DrawPrimitive::ColoredTriangle(vertices, color), &mut fb);
4666
4667 assert!(fb.pixel_count() > 20);
4669 assert!(fb.contains_pixel(50, 30));
4670 }
4671
4672 #[test]
4673 fn test_draw_triangle_general() {
4674 let mut fb = MockFramebuffer::new();
4675 let vertices = [
4676 Point2::new(50, 10),
4677 Point2::new(30, 30),
4678 Point2::new(80, 40),
4679 ];
4680 let color = Rgb565::CSS_MAGENTA;
4681
4682 draw(DrawPrimitive::ColoredTriangle(vertices, color), &mut fb);
4683
4684 assert!(fb.pixel_count() > 30);
4686 }
4687
4688 #[test]
4689 fn test_triangle_vertex_sorting() {
4690 let mut fb = MockFramebuffer::new();
4691 let vertices = [
4693 Point2::new(50, 30), Point2::new(30, 10), Point2::new(70, 20), ];
4697 let color = Rgb565::CSS_WHITE;
4698
4699 draw(DrawPrimitive::ColoredTriangle(vertices, color), &mut fb);
4701
4702 assert!(fb.pixel_count() > 10);
4703 }
4704
4705 #[test]
4706 fn test_draw_multiple_primitives() {
4707 let mut fb = MockFramebuffer::new();
4708
4709 draw(
4710 DrawPrimitive::ColoredPoint(Point2::new(5, 5), Rgb565::CSS_RED),
4711 &mut fb,
4712 );
4713 draw(
4714 DrawPrimitive::Line(
4715 [Point2::new(10, 10), Point2::new(20, 20)],
4716 Rgb565::CSS_GREEN,
4717 ),
4718 &mut fb,
4719 );
4720
4721 assert!(fb.pixel_count() > 11); assert!(fb.contains_pixel(5, 5));
4724 }
4725
4726 #[test]
4727 fn test_scanline_z_linear_interpolation_correctness() {
4728 let width = 100;
4729 let mut zbuffer = std::vec![u32::MAX; width * 10];
4730 let mut fb = MockFramebuffer::new();
4731
4732 let x1 = 10;
4733 let x2 = 90;
4734 let y = 5;
4735 let z1 = 10000u32;
4736 let z2 = 90000u32;
4737
4738 draw_scanline_zbuffered(
4739 x1,
4740 x2,
4741 y,
4742 z1,
4743 z2,
4744 Rgb565::CSS_BLUE,
4745 &mut fb,
4746 &mut zbuffer,
4747 width,
4748 None,
4749 None,
4750 );
4751
4752 let span = (x2 - x1) as f64;
4754 for x in x1..=x2 {
4755 let idx = y as usize * width + x as usize;
4756 let actual_z = zbuffer[idx];
4757 let expected_z = (z1 as f64 + (x - x1) as f64 * (z2 - z1) as f64 / span) as u32;
4758
4759 let diff = (actual_z as i64 - expected_z as i64).abs();
4760 assert!(
4761 diff <= 2,
4762 "Z interpolation error at x={}: actual={}, expected={}, diff={}",
4763 x,
4764 actual_z,
4765 expected_z,
4766 diff
4767 );
4768 }
4769 }
4770
4771 #[test]
4772 fn test_zbuffer_depth_occlusion_correctness() {
4773 let width = 50;
4774 let mut zbuffer = std::vec![u32::MAX; width * 5];
4775 let mut fb = MockFramebuffer::new();
4776
4777 draw_scanline_zbuffered(
4779 10,
4780 20,
4781 2,
4782 50000,
4783 50000,
4784 Rgb565::CSS_RED,
4785 &mut fb,
4786 &mut zbuffer,
4787 width,
4788 None,
4789 None,
4790 );
4791
4792 draw_scanline_zbuffered(
4794 10,
4795 20,
4796 2,
4797 20000,
4798 20000,
4799 Rgb565::CSS_GREEN,
4800 &mut fb,
4801 &mut zbuffer,
4802 width,
4803 None,
4804 None,
4805 );
4806
4807 for x in 10..=20 {
4809 let idx = 2 * width + x;
4810 assert_eq!(zbuffer[idx], 20000);
4811 }
4812
4813 draw_scanline_zbuffered(
4815 10,
4816 20,
4817 2,
4818 80000,
4819 80000,
4820 Rgb565::CSS_BLUE,
4821 &mut fb,
4822 &mut zbuffer,
4823 width,
4824 None,
4825 None,
4826 );
4827
4828 for x in 10..=20 {
4830 let idx = 2 * width + x;
4831 assert_eq!(zbuffer[idx], 20000);
4832 }
4833 }
4834
4835 #[test]
4836 fn test_sub_span_textured_scanline_correctness() {
4837 let width = 100;
4838 let mut zbuffer = std::vec![u32::MAX; width * 10];
4839 let mut fb = MockFramebuffer::new();
4840
4841 static TEX_DATA: [Rgb565; 4] = [
4843 Rgb565::CSS_RED,
4844 Rgb565::CSS_GREEN,
4845 Rgb565::CSS_BLUE,
4846 Rgb565::CSS_YELLOW,
4847 ];
4848 let texture = crate::texture::Texture::new(&TEX_DATA, 2, 2);
4849
4850 draw_scanline_zbuffered_textured(
4852 10,
4853 73,
4854 4,
4855 1000,
4856 1000,
4857 1.0,
4858 1.0,
4859 [0.0, 0.0],
4860 [1.0, 1.0],
4861 &texture,
4862 &mut fb,
4863 &mut zbuffer,
4864 width,
4865 None,
4866 None,
4867 TextureMapping::Affine,
4868 StippleMode::Off,
4869 None,
4870 PaletteMode::Off,
4871 );
4872
4873 for x in 10..=73 {
4875 let idx = 4 * width + x as usize;
4876 assert_eq!(zbuffer[idx], 1000);
4877 }
4878 assert!(fb.pixel_count() >= 64);
4879 }
4880
4881 #[test]
4882 fn test_fast_blend_rgb565() {
4883 let bg = Rgb565::BLACK;
4884 let fg = Rgb565::WHITE;
4885 assert_eq!(fast_blend_rgb565(bg, fg, 0), bg);
4886 assert_eq!(fast_blend_rgb565(bg, fg, 255), fg);
4887
4888 let blended = fast_blend_rgb565(bg, fg, 128);
4889 assert!(blended.r() > 0 && blended.r() < 31);
4890 }
4891
4892 #[test]
4893 fn test_fast_blend_rgba8888() {
4894 let bg = [0, 0, 0, 255];
4895 let fg = [255, 255, 255, 128];
4896 let out = fast_blend_rgba8888(bg, fg);
4897 assert!(out[0] > 0 && out[0] < 255);
4898 }
4899
4900 #[test]
4901 fn test_fast_blend_rgba8888_to_rgb565() {
4902 let bg = Rgb565::BLACK;
4903 let fg = [255, 0, 0, 255];
4904 let out = fast_blend_rgba8888_to_rgb565(bg, fg);
4905 assert_eq!(out, Rgb565::CSS_RED);
4906 }
4907}