use core::fmt::Debug;
use embedded_graphics_core::draw_target::DrawTarget;
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_core::prelude::Point;
use crate::ZDepth;
use crate::raster::scanline::EdgeStepper;
use crate::shader::FragmentShader;
#[inline(always)]
pub fn tri_area2(a: Point, b: Point, c: Point) -> i32 {
(b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
}
pub fn draw_triangle_zbuffered<D, S>(
points: [Point; 3],
depths: [f32; 3],
shader: &S,
fb: &mut D,
zbuffer: &mut [ZDepth],
) where
D: DrawTarget<Color = Rgb565>,
<D as DrawTarget>::Error: Debug,
S: FragmentShader<Interpolants = ()>,
{
let mut indices = [0, 1, 2];
if points[indices[0]].y > points[indices[1]].y {
indices.swap(0, 1);
}
if points[indices[1]].y > points[indices[2]].y {
indices.swap(1, 2);
}
if points[indices[0]].y > points[indices[1]].y {
indices.swap(0, 1);
}
let p1 = points[indices[0]];
let p2 = points[indices[1]];
let p3 = points[indices[2]];
let z1 = depths[indices[0]];
let z2 = depths[indices[1]];
let z3 = depths[indices[2]];
let total_height = p3.y - p1.y;
if total_height == 0 {
return;
}
let area = tri_area2(p1, p2, p3);
if area == 0 {
return;
}
let target_width = fb.bounding_box().size.width as i32;
let target_height = fb.bounding_box().size.height as i32;
for y in p1.y..=p3.y {
if y < 0 || y >= target_height {
continue;
}
let second_half = y > p2.y || p2.y == p1.y;
let segment_height = if second_half {
p3.y - p2.y
} else {
p2.y - p1.y
};
if segment_height == 0 {
continue;
}
let edge1 = EdgeStepper::new(p1, p3, y);
let edge2 = if second_half {
EdgeStepper::new(p2, p3, y)
} else {
EdgeStepper::new(p1, p2, y)
};
let mut x1 = edge1.current_x();
let mut x2 = edge2.current_x();
if x1 > x2 {
core::mem::swap(&mut x1, &mut x2);
}
let min_x = x1.max(0);
let max_x = x2.min(target_width - 1);
for x in min_x..=max_x {
let w1 = tri_area2(p2, p3, Point::new(x, y));
let w2 = tri_area2(p3, p1, Point::new(x, y));
let w3 = tri_area2(p1, p2, Point::new(x, y));
if area > 0 && (w1 < 0 || w2 < 0 || w3 < 0) {
continue;
}
if area < 0 && (w1 > 0 || w2 > 0 || w3 > 0) {
continue;
}
let inv_area = 1.0 / (area as f32);
let alpha = (w1 as f32) * inv_area;
let beta = (w2 as f32) * inv_area;
let gamma = (w3 as f32) * inv_area;
let interp_z = alpha * z1 + beta * z2 + gamma * z3;
if interp_z < 0.0 {
continue;
}
let z_int = crate::to_zdepth((interp_z * 65536.0) as u32);
let zb_idx = (y as usize) * (target_width as usize) + (x as usize);
if zb_idx < zbuffer.len() && z_int < zbuffer[zb_idx] {
zbuffer[zb_idx] = z_int;
let color = shader.shade(x, y, z_int, ());
fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), color)])
.unwrap();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shader::FlatColorShader;
use embedded_graphics_core::Pixel;
use embedded_graphics_core::geometry::{OriginDimensions, Size};
use embedded_graphics_core::pixelcolor::RgbColor;
struct TinyFb {
pixels: [Rgb565; 100],
}
impl OriginDimensions for TinyFb {
fn size(&self) -> Size {
Size::new(10, 10)
}
}
impl DrawTarget for TinyFb {
type Color = Rgb565;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels {
if point.x >= 0 && point.x < 10 && point.y >= 0 && point.y < 10 {
let idx = (point.y * 10 + point.x) as usize;
self.pixels[idx] = color;
}
}
Ok(())
}
}
#[test]
fn test_tri_area2() {
let p1 = Point::new(0, 0);
let p2 = Point::new(10, 0);
let p3 = Point::new(0, 10);
assert_eq!(tri_area2(p1, p2, p3), 100);
let p4 = Point::new(5, 0);
assert_eq!(tri_area2(p1, p4, p2), 0);
}
#[test]
fn test_draw_triangle_zbuffered() {
let mut fb = TinyFb {
pixels: [Rgb565::BLACK; 100],
};
let mut zbuffer = [ZDepth::MAX; 100];
let shader = FlatColorShader { color: Rgb565::RED };
let points = [Point::new(1, 1), Point::new(8, 1), Point::new(4, 8)];
let depths = [1.0, 1.0, 1.0];
draw_triangle_zbuffered(points, depths, &shader, &mut fb, &mut zbuffer);
let center_idx = 4 * 10 + 4;
assert_eq!(fb.pixels[center_idx], Rgb565::RED);
assert!(zbuffer[center_idx] < ZDepth::MAX);
}
#[test]
fn test_draw_triangle_zero_area_ignored() {
let mut fb = TinyFb {
pixels: [Rgb565::BLACK; 100],
};
let mut zbuffer = [ZDepth::MAX; 100];
let shader = FlatColorShader { color: Rgb565::RED };
let collinear = [Point::new(0, 0), Point::new(5, 5), Point::new(9, 9)];
draw_triangle_zbuffered(collinear, [1.0, 1.0, 1.0], &shader, &mut fb, &mut zbuffer);
assert_eq!(fb.pixels[0], Rgb565::BLACK);
}
}