#[cfg(feature = "row_width_320")]
const MAX_ROW_WIDTH: usize = 320;
#[cfg(all(feature = "row_width_240", not(feature = "row_width_320")))]
const MAX_ROW_WIDTH: usize = 240;
#[cfg(all(
feature = "row_width_160",
not(feature = "row_width_240"),
not(feature = "row_width_320")
))]
const MAX_ROW_WIDTH: usize = 160;
#[cfg(not(any(
feature = "row_width_320",
feature = "row_width_240",
feature = "row_width_160"
)))]
const MAX_ROW_WIDTH: usize = 100;
use core::fmt::Debug;
use embedded_graphics_core::draw_target::DrawTarget;
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_core::pixelcolor::RgbColor;
use embedded_graphics_core::prelude::Point;
use heapless::Vec;
use crate::DrawPrimitive;
pub trait ReadPixel {
fn read_pixel(&self, point: Point) -> Rgb565;
}
#[inline(always)]
fn blend_q8(bg: Rgb565, fg: Rgb565, coverage_q8: u32) -> Rgb565 {
let inv = 256 - coverage_q8;
let r = (bg.r() as u32 * inv + fg.r() as u32 * coverage_q8) >> 8;
let g = (bg.g() as u32 * inv + fg.g() as u32 * coverage_q8) >> 8;
let b = (bg.b() as u32 * inv + fg.b() as u32 * coverage_q8) >> 8;
Rgb565::new(r as u8, g as u8, b as u8)
}
#[inline(always)]
fn aa_pixel<D>(
fb: &mut D,
x: i32,
y: i32,
color: Rgb565,
z: u32,
zbuffer: &mut [u32],
width: usize,
coverage_q8: u32,
) where
D: DrawTarget<Color = Rgb565> + ReadPixel,
<D as DrawTarget>::Error: Debug,
{
if x < 0 || y < 0 || coverage_q8 == 0 {
return;
}
let idx = y as usize * width + x as usize;
if idx >= zbuffer.len() {
return;
}
if z >= zbuffer[idx].saturating_add(DEPTH_EPSILON) {
return;
}
let final_color = if coverage_q8 >= 256 {
zbuffer[idx] = z;
color
} else {
let bg = fb.read_pixel(Point::new(x, y));
blend_q8(bg, color, coverage_q8)
};
fb.draw_iter([embedded_graphics_core::Pixel(
Point::new(x, y),
final_color,
)])
.unwrap();
}
const DEPTH_EPSILON: u32 = 128;
#[derive(Debug, Clone, Copy)]
pub struct FogConfig {
pub color: embedded_graphics_core::pixelcolor::Rgb565,
pub near: u32,
pub far: u32,
}
impl FogConfig {
pub fn new(color: embedded_graphics_core::pixelcolor::Rgb565, near: f32, far: f32) -> Self {
Self {
color,
near: (near * 65536.0) as u32,
far: (far * 65536.0) as u32,
}
}
#[inline]
pub fn apply(
&self,
base_color: embedded_graphics_core::pixelcolor::Rgb565,
depth: u32,
) -> embedded_graphics_core::pixelcolor::Rgb565 {
let fog_factor = if depth <= self.near {
0u32
} else if depth >= self.far {
65536u32 } else {
let numerator = (depth - self.near) as u64;
let denominator = (self.far - self.near) as u64;
((numerator * 65536) / denominator) as u32
};
let base_r = base_color.r() as u32;
let base_g = base_color.g() as u32;
let base_b = base_color.b() as u32;
let fog_r = self.color.r() as u32;
let fog_g = self.color.g() as u32;
let fog_b = self.color.b() as u32;
let r = ((base_r * (65536 - fog_factor) + fog_r * fog_factor) / 65536) as u8;
let g = ((base_g * (65536 - fog_factor) + fog_g * fog_factor) / 65536) as u8;
let b = ((base_b * (65536 - fog_factor) + fog_b * fog_factor) / 65536) as u8;
embedded_graphics_core::pixelcolor::Rgb565::new(r, g, b)
}
}
#[derive(Debug, Clone, Copy)]
pub struct DitherConfig {
pub intensity: u8,
}
impl DitherConfig {
const BAYER_MATRIX: [[u8; 4]; 4] =
[[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];
pub fn new(intensity: u8) -> Self {
Self { intensity }
}
#[inline]
pub fn apply(
&self,
color: embedded_graphics_core::pixelcolor::Rgb565,
x: i32,
y: i32,
) -> embedded_graphics_core::pixelcolor::Rgb565 {
if self.intensity == 0 {
return color;
}
let matrix_x = (x & 3) as usize;
let matrix_y = (y & 3) as usize;
let threshold = Self::BAYER_MATRIX[matrix_y][matrix_x];
let scaled_threshold = ((threshold as u16 * self.intensity as u16) / 15) as u8;
let r = color.r();
let g = color.g();
let b = color.b();
let r = if r > scaled_threshold {
r.saturating_sub(scaled_threshold / 2)
} else {
r.saturating_add(scaled_threshold / 2)
};
let g = if g > scaled_threshold {
g.saturating_sub(scaled_threshold / 2)
} else {
g.saturating_add(scaled_threshold / 2)
};
let b = if b > scaled_threshold {
b.saturating_sub(scaled_threshold / 2)
} else {
b.saturating_add(scaled_threshold / 2)
};
embedded_graphics_core::pixelcolor::Rgb565::new(r, g, b)
}
}
struct Interpolator {
x: i32,
dx: i32,
dy: i32,
error: i32,
}
impl Interpolator {
fn new(p_start: Point, p_end: Point) -> Self {
Self {
x: p_start.x,
dx: p_end.x - p_start.x,
dy: p_end.y - p_start.y,
error: 0,
}
}
fn next(&mut self) -> i32 {
self.x += self.dx / self.dy;
self.error += self.dx % self.dy;
if self.error >= self.dy {
self.x += 1;
self.error -= self.dy;
}
self.x
}
}
#[inline(always)]
fn fill_triangle<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
p1: Point,
p2: Point,
p3: Point,
color: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
) where
<D as DrawTarget>::Error: Debug,
{
let area = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
if area == 0 {
return;
}
let bounds = fb.bounding_box();
let min_x = bounds.top_left.x;
let max_x = bounds.bottom_right().unwrap().x;
let mut pixel_row: [embedded_graphics_core::Pixel<embedded_graphics_core::pixelcolor::Rgb565>;
MAX_ROW_WIDTH] = [embedded_graphics_core::Pixel(
Point::new(0, 0),
embedded_graphics_core::pixelcolor::RgbColor::BLACK,
); MAX_ROW_WIDTH];
if p2.y - p1.y > 0 {
let mut a = Interpolator::new(p1, p2);
let mut b = Interpolator::new(p1, p3);
for y in p1.y..p2.y {
let ax = a.next();
let bx = b.next();
let (start_x, end_x) = if ax < bx { (ax, bx) } else { (bx, ax) };
let start_x = start_x.clamp(min_x, max_x);
let end_x = end_x.clamp(min_x, max_x);
let mut i = 0;
for x in start_x..=end_x {
pixel_row[i] = embedded_graphics_core::Pixel(Point::new(x, y), color);
i += 1;
}
fb.draw_iter(pixel_row[..(end_x - start_x + 1) as usize].iter().copied())
.unwrap();
}
}
if p3.y - p2.y > 0 {
let mut a = Interpolator::new(p2, p3);
let mut b = Interpolator::new(p1, p3);
for y in p2.y..=p3.y {
let ax = a.next();
let bx = b.next();
let (start_x, end_x) = if ax < bx { (ax, bx) } else { (bx, ax) };
let start_x = start_x.clamp(min_x, max_x);
let end_x = end_x.clamp(min_x, max_x);
let mut i = 0;
for x in start_x..=end_x {
pixel_row[i] = embedded_graphics_core::Pixel(Point::new(x, y), color);
i += 1;
}
fb.draw_iter(pixel_row[..(end_x - start_x + 1) as usize].iter().copied())
.unwrap();
}
}
}
fn fill_bottom_flat_triangle<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
p1: Point,
p2: Point,
p3: Point,
color: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
) where
<D as DrawTarget>::Error: Debug,
{
let invslope1 = (p2.x - p1.x) as f32 / (p2.y - p1.y) as f32;
let invslope2 = (p3.x - p1.x) as f32 / (p3.y - p1.y) as f32;
let mut curx1 = p1.x as f32;
let mut curx2 = p1.x as f32;
for scanline_y in p1.y..=p2.y {
draw_horizontal_line(
Point::new(curx1 as i32, scanline_y),
Point::new(curx2 as i32, scanline_y),
color,
fb,
);
curx1 += invslope1;
curx2 += invslope2;
}
}
fn fill_top_flat_triangle<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
p1: Point,
p2: Point,
p3: Point,
color: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
) where
<D as DrawTarget>::Error: Debug,
{
let invslope1 = (p3.x - p1.x) as f32 / (p3.y - p1.y) as f32;
let invslope2 = (p3.x - p2.x) as f32 / (p3.y - p2.y) as f32;
let mut curx1 = p3.x as f32;
let mut curx2 = p3.x as f32;
for scanline_y in (p1.y..=p3.y).rev() {
draw_horizontal_line(
Point::new(curx1 as i32, scanline_y),
Point::new(curx2 as i32, scanline_y),
color,
fb,
);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
fn draw_horizontal_line<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
p1: Point,
p2: Point,
color: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
) where
<D as DrawTarget>::Error: Debug,
{
let start = p1.x.min(p2.x);
let end = p1.x.max(p2.x);
for x in start..=end {
fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, p1.y), color)])
.unwrap();
}
}
#[inline]
pub fn draw<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
primitive: DrawPrimitive,
fb: &mut D,
) where
<D as DrawTarget>::Error: Debug,
{
match primitive {
DrawPrimitive::Line([p1, p2], color) => {
fb.draw_iter(
line_drawing::Bresenham::new((p1.x, p1.y), (p2.x, p2.y))
.map(|(x, y)| embedded_graphics_core::Pixel(Point::new(x, y), color)),
)
.unwrap();
}
DrawPrimitive::ColoredPoint(p, c) => {
let p = embedded_graphics_core::geometry::Point::new(p.x, p.y);
fb.draw_iter([embedded_graphics_core::Pixel(p, c)]).unwrap();
}
DrawPrimitive::ColoredTriangle(mut vertices, color) => {
vertices
.as_mut_slice()
.sort_unstable_by(|a, b| a.y.cmp(&b.y));
let [p1, p2, p3] = [
Point::new(vertices[0].x, vertices[0].y),
Point::new(vertices[1].x, vertices[1].y),
Point::new(vertices[2].x, vertices[2].y),
];
fill_triangle(p1, p2, p3, color, fb);
}
DrawPrimitive::ColoredTriangleWithDepth {
points,
depths: _,
color,
} => {
let mut vertices = points;
if vertices[0].y > vertices[1].y {
vertices.swap(0, 1);
}
if vertices[0].y > vertices[2].y {
vertices.swap(0, 2);
}
if vertices[1].y > vertices[2].y {
vertices.swap(1, 2);
}
let mut buf: Vec<_, 3> = Vec::new();
for p in vertices.iter() {
buf.push(embedded_graphics_core::geometry::Point::new(p.x, p.y))
.unwrap();
}
let [p1, p2, p3] = buf.into_array().unwrap();
if p2.y == p3.y {
fill_bottom_flat_triangle(p1, p2, p3, color, fb);
} else if p1.y == p2.y {
fill_top_flat_triangle(p1, p2, p3, color, fb);
} else {
let p4 = Point::new(
(p1.x as f32
+ ((p2.y - p1.y) as f32 / (p3.y - p1.y) as f32) * (p3.x - p1.x) as f32)
as i32,
p2.y,
);
fill_bottom_flat_triangle(p1, p2, p4, color, fb);
fill_top_flat_triangle(p2, p4, p3, color, fb);
}
}
DrawPrimitive::GouraudTriangle {
mut points,
mut colors,
} => {
if points[0].y > points[1].y {
points.swap(0, 1);
colors.swap(0, 1);
}
if points[0].y > points[2].y {
points.swap(0, 2);
colors.swap(0, 2);
}
if points[1].y > points[2].y {
points.swap(1, 2);
colors.swap(1, 2);
}
let mut buf: Vec<_, 3> = Vec::new();
for p in points.iter() {
buf.push(embedded_graphics_core::geometry::Point::new(p.x, p.y))
.unwrap();
}
let [p1, p2, p3] = buf.into_array().unwrap();
let [c1, c2, c3] = colors;
if p2.y == p3.y {
fill_bottom_flat_gouraud(p1, p2, p3, c1, c2, c3, fb);
} else if p1.y == p2.y {
fill_top_flat_gouraud(p1, p2, p3, c1, c2, c3, fb);
} else {
let t = (p2.y - p1.y) as f32 / (p3.y - p1.y) as f32;
let p4 = Point::new((p1.x as f32 + t * (p3.x - p1.x) as f32) as i32, p2.y);
let c4 = interpolate_color(c1, c3, t);
fill_bottom_flat_gouraud(p1, p2, p4, c1, c2, c4, fb);
fill_top_flat_gouraud(p2, p4, p3, c2, c4, c3, fb);
}
}
DrawPrimitive::GouraudTriangleWithDepth {
points,
depths: _,
colors,
} => {
let prim = DrawPrimitive::GouraudTriangle { points, colors };
draw(prim, fb);
}
DrawPrimitive::TexturedTriangle { .. }
| DrawPrimitive::TexturedTriangleWithDepth { .. } => {
}
}
}
#[inline]
fn interpolate_color(
c1: embedded_graphics_core::pixelcolor::Rgb565,
c2: embedded_graphics_core::pixelcolor::Rgb565,
t: f32,
) -> embedded_graphics_core::pixelcolor::Rgb565 {
let r1 = c1.r() as f32;
let g1 = c1.g() as f32;
let b1 = c1.b() as f32;
let r2 = c2.r() as f32;
let g2 = c2.g() as f32;
let b2 = c2.b() as f32;
let r = (r1 + t * (r2 - r1)) as u8;
let g = (g1 + t * (g2 - g1)) as u8;
let b = (b1 + t * (b2 - b1)) as u8;
embedded_graphics_core::pixelcolor::Rgb565::new(r, g, b)
}
fn fill_bottom_flat_gouraud<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
p1: Point,
p2: Point,
p3: Point,
c1: embedded_graphics_core::pixelcolor::Rgb565,
c2: embedded_graphics_core::pixelcolor::Rgb565,
c3: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
) where
<D as DrawTarget>::Error: Debug,
{
let height = (p2.y - p1.y) as f32;
if height == 0.0 {
return;
}
let invslope1 = (p2.x - p1.x) as f32 / height;
let invslope2 = (p3.x - p1.x) as f32 / height;
let mut curx1 = p1.x as f32;
let mut curx2 = p1.x as f32;
for scanline_y in p1.y..=p2.y {
let t = (scanline_y - p1.y) as f32 / height;
let color_left = interpolate_color(c1, c2, t);
let color_right = interpolate_color(c1, c3, t);
draw_horizontal_line_gouraud(
Point::new(curx1 as i32, scanline_y),
Point::new(curx2 as i32, scanline_y),
color_left,
color_right,
fb,
);
curx1 += invslope1;
curx2 += invslope2;
}
}
fn fill_top_flat_gouraud<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
p1: Point,
p2: Point,
p3: Point,
c1: embedded_graphics_core::pixelcolor::Rgb565,
c2: embedded_graphics_core::pixelcolor::Rgb565,
c3: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
) where
<D as DrawTarget>::Error: Debug,
{
let height = (p3.y - p1.y) as f32;
if height == 0.0 {
return;
}
let invslope1 = (p3.x - p1.x) as f32 / height;
let invslope2 = (p3.x - p2.x) as f32 / height;
let mut curx1 = p3.x as f32;
let mut curx2 = p3.x as f32;
for scanline_y in (p1.y..=p3.y).rev() {
let t = (scanline_y - p1.y) as f32 / height;
let color_left = interpolate_color(c1, c3, t);
let color_right = interpolate_color(c2, c3, t);
draw_horizontal_line_gouraud(
Point::new(curx1 as i32, scanline_y),
Point::new(curx2 as i32, scanline_y),
color_left,
color_right,
fb,
);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
fn draw_horizontal_line_gouraud<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
p1: Point,
p2: Point,
color1: embedded_graphics_core::pixelcolor::Rgb565,
color2: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
) where
<D as DrawTarget>::Error: Debug,
{
let start = p1.x.min(p2.x);
let end = p1.x.max(p2.x);
let width = (end - start) as f32;
if width == 0.0 {
fb.draw_iter([embedded_graphics_core::Pixel(
Point::new(start, p1.y),
color1,
)])
.unwrap();
return;
}
for x in start..=end {
let t = (x - start) as f32 / width;
let color = interpolate_color(color1, color2, t);
fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, p1.y), color)])
.unwrap();
}
}
#[inline]
pub fn draw_zbuffered<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
primitive: DrawPrimitive,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
) where
<D as DrawTarget>::Error: Debug,
{
draw_zbuffered_with_effects(primitive, fb, zbuffer, width, None, None);
}
#[inline]
pub fn draw_zbuffered_aa<D>(
primitive: DrawPrimitive,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
) where
D: DrawTarget<Color = Rgb565> + ReadPixel,
<D as DrawTarget>::Error: Debug,
{
match primitive {
DrawPrimitive::ColoredTriangleWithDepth {
mut points,
mut depths,
color,
} => {
if points[0].y > points[1].y {
points.swap(0, 1);
depths.swap(0, 1);
}
if points[0].y > points[2].y {
points.swap(0, 2);
depths.swap(0, 2);
}
if points[1].y > points[2].y {
points.swap(1, 2);
depths.swap(1, 2);
}
let [p1, p2, p3] = points;
let [z1, z2, z3] = depths;
fill_triangle_zbuffered_aa(p1, p2, p3, z1, z2, z3, color, fb, zbuffer, width);
}
DrawPrimitive::Line([p1, p2], color) => {
draw_line_aa(p1.x, p1.y, p2.x, p2.y, color, fb);
}
other => draw_zbuffered(other, fb, zbuffer, width),
}
}
#[inline(always)]
fn fill_triangle_zbuffered_aa<D>(
p1: nalgebra::Point2<i32>,
p2: nalgebra::Point2<i32>,
p3: nalgebra::Point2<i32>,
z1: f32,
z2: f32,
z3: f32,
color: Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
) where
D: DrawTarget<Color = Rgb565> + ReadPixel,
<D as DrawTarget>::Error: Debug,
{
let p1_eg = Point::new(p1.x, p1.y);
let p2_eg = Point::new(p2.x, p2.y);
let p3_eg = Point::new(p3.x, p3.y);
let z1_int = (z1 * 65536.0) as u32;
let z2_int = (z2 * 65536.0) as u32;
let z3_int = (z3 * 65536.0) as u32;
if p2_eg.y == p3_eg.y {
fill_bottom_flat_aa(
p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, fb, zbuffer, width,
);
} else if p1_eg.y == p2_eg.y {
fill_top_flat_aa(
p1_eg, p2_eg, p3_eg, z1_int, z2_int, z3_int, color, fb, zbuffer, width,
);
} else {
let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
let p4 = Point::new(
(p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
p2_eg.y,
);
let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
fill_bottom_flat_aa(
p1_eg, p2_eg, p4, z1_int, z2_int, z4_int, color, fb, zbuffer, width,
);
fill_top_flat_aa(
p2_eg, p4, p3_eg, z2_int, z4_int, z3_int, color, fb, zbuffer, width,
);
}
}
#[inline(always)]
fn fill_bottom_flat_aa<D>(
p1: Point,
p2: Point,
p3: Point,
z1: u32,
z2: u32,
z3: u32,
color: Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
) where
D: DrawTarget<Color = Rgb565> + ReadPixel,
<D as DrawTarget>::Error: Debug,
{
let height = p2.y - p1.y;
if height == 0 {
return;
}
let invslope1 = ((p2.x - p1.x) << 16) / height;
let invslope2 = ((p3.x - p1.x) << 16) / height;
let mut curx1 = p1.x << 16;
let mut curx2 = p1.x << 16;
for scanline_y in p1.y..=p2.y {
let dy = scanline_y - p1.y;
let z_left = (z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
let z_right = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
aa_scanline(curx1, curx2, scanline_y, z_left, z_right, color, fb, zbuffer, width);
curx1 += invslope1;
curx2 += invslope2;
}
}
#[inline(always)]
fn fill_top_flat_aa<D>(
p1: Point,
p2: Point,
p3: Point,
z1: u32,
z2: u32,
z3: u32,
color: Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
) where
D: DrawTarget<Color = Rgb565> + ReadPixel,
<D as DrawTarget>::Error: Debug,
{
let height = p3.y - p1.y;
if height == 0 {
return;
}
let invslope1 = ((p3.x - p1.x) << 16) / height;
let invslope2 = ((p3.x - p2.x) << 16) / height;
let mut curx1 = p3.x << 16;
let mut curx2 = p3.x << 16;
for scanline_y in (p1.y..=p3.y).rev() {
let dy = scanline_y - p1.y;
let z_left = (z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32;
let z_right = (z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32;
aa_scanline(curx1, curx2, scanline_y, z_left, z_right, color, fb, zbuffer, width);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
#[inline(always)]
fn aa_scanline<D>(
cx1: i32,
cx2: i32,
y: i32,
z_left: u32,
z_right: u32,
color: Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
) where
D: DrawTarget<Color = Rgb565> + ReadPixel,
<D as DrawTarget>::Error: Debug,
{
let (left_fx, right_fx, z_l, z_r) = if cx1 <= cx2 {
(cx1, cx2, z_left, z_right)
} else {
(cx2, cx1, z_right, z_left)
};
let l_int = left_fx >> 16;
let r_int = right_fx >> 16;
let l_frac_q16 = (left_fx & 0xFFFF) as u32;
let r_frac_q16 = (right_fx & 0xFFFF) as u32;
let span = r_int - l_int;
if l_int == r_int {
let cov_q16 = r_frac_q16.saturating_sub(l_frac_q16);
aa_pixel(fb, l_int, y, color, z_l, zbuffer, width, cov_q16 >> 8);
return;
}
let left_cov_q8 = 256 - (l_frac_q16 >> 8);
aa_pixel(fb, l_int, y, color, z_l, zbuffer, width, left_cov_q8);
if span > 1 {
for x in (l_int + 1)..r_int {
if x < 0 {
continue;
}
let idx = y as usize * width + x as usize;
if idx >= zbuffer.len() {
continue;
}
let t_num = (x - l_int) as i64;
let t_den = span as i64;
let z = (z_l as i64 + ((z_r as i64 - z_l as i64) * t_num / t_den)) as u32;
if z < zbuffer[idx].saturating_add(DEPTH_EPSILON) {
zbuffer[idx] = z;
fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), color)])
.unwrap();
}
}
}
if r_frac_q16 > 0 {
let right_cov_q8 = r_frac_q16 >> 8;
aa_pixel(fb, r_int, y, color, z_r, zbuffer, width, right_cov_q8);
}
}
pub fn draw_line_aa<D>(x0: i32, y0: i32, x1: i32, y1: i32, color: Rgb565, fb: &mut D)
where
D: DrawTarget<Color = Rgb565> + ReadPixel,
<D as DrawTarget>::Error: Debug,
{
let dx = (x1 - x0).abs();
let dy = (y1 - y0).abs();
let steep = dy > dx;
let (x0, y0, x1, y1) = if steep {
(y0, x0, y1, x1)
} else {
(x0, y0, x1, y1)
};
let (x0, y0, x1, y1) = if x0 > x1 {
(x1, y1, x0, y0)
} else {
(x0, y0, x1, y1)
};
let dx = x1 - x0;
let dy = y1 - y0;
if dx == 0 {
let (px, py) = if steep { (y0, x0) } else { (x0, y0) };
plot_aa(fb, px, py, color, 256);
return;
}
let gradient: i32 = ((dy as i64) << 16) as i32 / dx;
let mut intery: i32 = y0 << 16;
for x in x0..=x1 {
let y_int = intery >> 16;
let frac_q16 = (intery & 0xFFFF) as u32;
let cov_top = 256 - (frac_q16 >> 8); let cov_bot = frac_q16 >> 8; if steep {
plot_aa(fb, y_int, x, color, cov_top);
plot_aa(fb, y_int + 1, x, color, cov_bot);
} else {
plot_aa(fb, x, y_int, color, cov_top);
plot_aa(fb, x, y_int + 1, color, cov_bot);
}
intery += gradient;
}
}
#[inline(always)]
fn plot_aa<D>(fb: &mut D, x: i32, y: i32, color: Rgb565, coverage_q8: u32)
where
D: DrawTarget<Color = Rgb565> + ReadPixel,
<D as DrawTarget>::Error: Debug,
{
if coverage_q8 == 0 {
return;
}
let final_color = if coverage_q8 >= 256 {
color
} else {
let bg = fb.read_pixel(Point::new(x, y));
blend_q8(bg, color, coverage_q8)
};
fb.draw_iter([embedded_graphics_core::Pixel(
Point::new(x, y),
final_color,
)])
.unwrap();
}
#[inline]
pub fn draw_zbuffered_with_effects<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
primitive: DrawPrimitive,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
match primitive {
DrawPrimitive::ColoredTriangleWithDepth {
mut points,
mut depths,
color,
} => {
if points[0].y > points[1].y {
points.swap(0, 1);
depths.swap(0, 1);
}
if points[0].y > points[2].y {
points.swap(0, 2);
depths.swap(0, 2);
}
if points[1].y > points[2].y {
points.swap(1, 2);
depths.swap(1, 2);
}
let [p1, p2, p3] = points;
let [z1, z2, z3] = depths;
fill_triangle_zbuffered(
p1,
p2,
p3,
z1,
z2,
z3,
color,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
}
DrawPrimitive::GouraudTriangleWithDepth {
mut points,
mut depths,
mut colors,
} => {
if points[0].y > points[1].y {
points.swap(0, 1);
depths.swap(0, 1);
colors.swap(0, 1);
}
if points[0].y > points[2].y {
points.swap(0, 2);
depths.swap(0, 2);
colors.swap(0, 2);
}
if points[1].y > points[2].y {
points.swap(1, 2);
depths.swap(1, 2);
colors.swap(1, 2);
}
let [p1, p2, p3] = points;
let [z1, z2, z3] = depths;
let [c1, c2, c3] = colors;
fill_triangle_zbuffered_gouraud(
p1,
p2,
p3,
z1,
z2,
z3,
c1,
c2,
c3,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
}
DrawPrimitive::TexturedTriangle { .. }
| DrawPrimitive::TexturedTriangleWithDepth { .. } => {
}
_ => draw(primitive, fb),
}
}
#[inline]
pub fn draw_zbuffered_with_textures<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
const N: usize,
>(
primitive: DrawPrimitive,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
texture_manager: &crate::texture::TextureManager<N>,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
match primitive {
DrawPrimitive::TexturedTriangleWithDepth {
mut points,
mut depths,
mut uvs,
texture_id,
} => {
if let Some(texture) = texture_manager.get(texture_id) {
if points[0].y > points[1].y {
points.swap(0, 1);
depths.swap(0, 1);
uvs.swap(0, 1);
}
if points[0].y > points[2].y {
points.swap(0, 2);
depths.swap(0, 2);
uvs.swap(0, 2);
}
if points[1].y > points[2].y {
points.swap(1, 2);
depths.swap(1, 2);
uvs.swap(1, 2);
}
let [p1, p2, p3] = points;
let [z1, z2, z3] = depths;
let [uv1, uv2, uv3] = uvs;
fill_triangle_zbuffered_textured(
p1,
p2,
p3,
z1,
z2,
z3,
uv1,
uv2,
uv3,
texture,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
}
}
_ => draw_zbuffered_with_effects(primitive, fb, zbuffer, width, fog_config, dither_config),
}
}
#[inline(always)]
fn fill_triangle_zbuffered<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
p1: nalgebra::Point2<i32>,
p2: nalgebra::Point2<i32>,
p3: nalgebra::Point2<i32>,
z1: f32,
z2: f32,
z3: f32,
color: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let p1_eg = Point::new(p1.x, p1.y);
let p2_eg = Point::new(p2.x, p2.y);
let p3_eg = Point::new(p3.x, p3.y);
let z1_int = (z1 * 65536.0) as u32;
let z2_int = (z2 * 65536.0) as u32;
let z3_int = (z3 * 65536.0) as u32;
if p2_eg.y == p3_eg.y {
fill_bottom_flat_triangle_zbuffered(
p1_eg,
p2_eg,
p3_eg,
z1_int,
z2_int,
z3_int,
color,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
} else if p1_eg.y == p2_eg.y {
fill_top_flat_triangle_zbuffered(
p1_eg,
p2_eg,
p3_eg,
z1_int,
z2_int,
z3_int,
color,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
} else {
let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
let p4 = Point::new(
(p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
p2_eg.y,
);
let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
fill_bottom_flat_triangle_zbuffered(
p1_eg,
p2_eg,
p4,
z1_int,
z2_int,
z4_int,
color,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
fill_top_flat_triangle_zbuffered(
p2_eg,
p4,
p3_eg,
z2_int,
z4_int,
z3_int,
color,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
}
}
#[inline(always)]
fn fill_triangle_zbuffered_gouraud<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
p1: nalgebra::Point2<i32>,
p2: nalgebra::Point2<i32>,
p3: nalgebra::Point2<i32>,
z1: f32,
z2: f32,
z3: f32,
c1: embedded_graphics_core::pixelcolor::Rgb565,
c2: embedded_graphics_core::pixelcolor::Rgb565,
c3: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let p1_eg = Point::new(p1.x, p1.y);
let p2_eg = Point::new(p2.x, p2.y);
let p3_eg = Point::new(p3.x, p3.y);
let z1_int = (z1 * 65536.0) as u32;
let z2_int = (z2 * 65536.0) as u32;
let z3_int = (z3 * 65536.0) as u32;
if p2_eg.y == p3_eg.y {
fill_bottom_flat_triangle_zbuffered_gouraud(
p1_eg,
p2_eg,
p3_eg,
z1_int,
z2_int,
z3_int,
c1,
c2,
c3,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
} else if p1_eg.y == p2_eg.y {
fill_top_flat_triangle_zbuffered_gouraud(
p1_eg,
p2_eg,
p3_eg,
z1_int,
z2_int,
z3_int,
c1,
c2,
c3,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
} else {
let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
let p4 = Point::new(
(p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
p2_eg.y,
);
let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
let c4 = interpolate_color(c1, c3, t);
fill_bottom_flat_triangle_zbuffered_gouraud(
p1_eg,
p2_eg,
p4,
z1_int,
z2_int,
z4_int,
c1,
c2,
c4,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
fill_top_flat_triangle_zbuffered_gouraud(
p2_eg,
p4,
p3_eg,
z2_int,
z4_int,
z3_int,
c2,
c4,
c3,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
}
}
#[inline(always)]
fn fill_bottom_flat_triangle_zbuffered<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
p1: Point,
p2: Point,
p3: Point,
z1: u32,
z2: u32,
z3: u32,
color: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let height = p2.y - p1.y;
if height == 0 {
return;
}
let invslope1 = ((p2.x - p1.x) << 16) / height;
let invslope2 = ((p3.x - p1.x) << 16) / height;
let mut curx1 = p1.x << 16; let mut curx2 = p1.x << 16;
for scanline_y in p1.y..=p2.y {
let dy = scanline_y - p1.y;
let z_left = if height > 0 {
(z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
let z_right = if height > 0 {
(z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
draw_scanline_zbuffered(
curx1 >> 16, curx2 >> 16, scanline_y,
z_left,
z_right,
color,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
curx1 += invslope1;
curx2 += invslope2;
}
}
#[inline(always)]
fn fill_top_flat_triangle_zbuffered<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
p1: Point,
p2: Point,
p3: Point,
z1: u32,
z2: u32,
z3: u32,
color: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let height = p3.y - p1.y;
if height == 0 {
return;
}
let invslope1 = ((p3.x - p1.x) << 16) / height;
let invslope2 = ((p3.x - p2.x) << 16) / height;
let mut curx1 = p3.x << 16; let mut curx2 = p3.x << 16;
for scanline_y in (p1.y..=p3.y).rev() {
let dy = scanline_y - p1.y;
let z_left = if height > 0 {
(z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
let z_right = if height > 0 {
(z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32
} else {
z2
};
draw_scanline_zbuffered(
curx1 >> 16, curx2 >> 16, scanline_y,
z_left,
z_right,
color,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
#[inline(always)]
fn fill_bottom_flat_triangle_zbuffered_gouraud<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
p1: Point,
p2: Point,
p3: Point,
z1: u32,
z2: u32,
z3: u32,
c1: embedded_graphics_core::pixelcolor::Rgb565,
c2: embedded_graphics_core::pixelcolor::Rgb565,
c3: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let height = p2.y - p1.y;
if height == 0 {
return;
}
let invslope1 = ((p2.x - p1.x) << 16) / height;
let invslope2 = ((p3.x - p1.x) << 16) / height;
let mut curx1 = p1.x << 16;
let mut curx2 = p1.x << 16;
for scanline_y in p1.y..=p2.y {
let dy = scanline_y - p1.y;
let t = dy as f32 / height as f32;
let z_left = if height > 0 {
(z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
let z_right = if height > 0 {
(z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
let color_left = interpolate_color(c1, c2, t);
let color_right = interpolate_color(c1, c3, t);
draw_scanline_zbuffered_gouraud(
curx1 >> 16,
curx2 >> 16,
scanline_y,
z_left,
z_right,
color_left,
color_right,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
curx1 += invslope1;
curx2 += invslope2;
}
}
#[inline(always)]
fn fill_top_flat_triangle_zbuffered_gouraud<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
p1: Point,
p2: Point,
p3: Point,
z1: u32,
z2: u32,
z3: u32,
c1: embedded_graphics_core::pixelcolor::Rgb565,
c2: embedded_graphics_core::pixelcolor::Rgb565,
c3: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let height = p3.y - p1.y;
if height == 0 {
return;
}
let invslope1 = ((p3.x - p1.x) << 16) / height;
let invslope2 = ((p3.x - p2.x) << 16) / height;
let mut curx1 = p3.x << 16;
let mut curx2 = p3.x << 16;
for scanline_y in (p1.y..=p3.y).rev() {
let dy = scanline_y - p1.y;
let t = dy as f32 / height as f32;
let z_left = if height > 0 {
(z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
let z_right = if height > 0 {
(z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32
} else {
z2
};
let color_left = interpolate_color(c1, c3, t);
let color_right = interpolate_color(c2, c3, t);
draw_scanline_zbuffered_gouraud(
curx1 >> 16,
curx2 >> 16,
scanline_y,
z_left,
z_right,
color_left,
color_right,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
#[inline(always)]
fn draw_scanline_zbuffered_gouraud<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
x1: i32,
x2: i32,
y: i32,
z1: u32,
z2: u32,
color1: embedded_graphics_core::pixelcolor::Rgb565,
color2: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let start = x1.min(x2);
let end = x1.max(x2);
let span_width = end - start;
for x in start..=end {
if x < 0 || y < 0 {
continue;
}
let zbuffer_idx = y as usize * width + x as usize;
if zbuffer_idx >= zbuffer.len() {
continue;
}
let t = if span_width > 0 {
(x - start) as f32 / span_width as f32
} else {
0.0
};
let z = if span_width > 0 {
(z1 as i64 + ((z2 as i64 - z1 as i64) * (x - start) as i64 / span_width as i64)) as u32
} else {
z1
};
if z < zbuffer[zbuffer_idx].saturating_add(DEPTH_EPSILON) {
zbuffer[zbuffer_idx] = z;
let mut final_color = interpolate_color(color1, color2, t);
if let Some(fog) = fog_config {
final_color = fog.apply(final_color, z);
}
if let Some(dither) = dither_config {
final_color = dither.apply(final_color, x, y);
}
fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_color)])
.unwrap();
}
}
}
#[inline(always)]
fn draw_scanline_zbuffered<D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>(
x1: i32,
x2: i32,
y: i32,
z1: u32,
z2: u32,
color: embedded_graphics_core::pixelcolor::Rgb565,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let start = x1.min(x2);
let end = x1.max(x2);
for x in start..=end {
if x < 0 || y < 0 {
continue;
}
let zbuffer_idx = y as usize * width + x as usize;
if zbuffer_idx >= zbuffer.len() {
continue;
}
let span = end - start;
let z = if span == 0 {
z1
} else {
let t_num = (x - start) as i64;
let t_den = span as i64;
let z_diff = z2 as i64 - z1 as i64;
(z1 as i64 + (t_num * z_diff / t_den)) as u32
};
if z < zbuffer[zbuffer_idx].saturating_add(DEPTH_EPSILON) {
zbuffer[zbuffer_idx] = z;
let mut final_color = color;
if let Some(fog) = fog_config {
final_color = fog.apply(final_color, z);
}
if let Some(dither) = dither_config {
final_color = dither.apply(final_color, x, y);
}
fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_color)])
.unwrap();
}
}
}
#[inline(always)]
fn fill_triangle_zbuffered_textured<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
p1: nalgebra::Point2<i32>,
p2: nalgebra::Point2<i32>,
p3: nalgebra::Point2<i32>,
z1: f32,
z2: f32,
z3: f32,
uv1: [f32; 2],
uv2: [f32; 2],
uv3: [f32; 2],
texture: &crate::texture::Texture,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let p1_eg = Point::new(p1.x, p1.y);
let p2_eg = Point::new(p2.x, p2.y);
let p3_eg = Point::new(p3.x, p3.y);
let z1_int = (z1 * 65536.0) as u32;
let z2_int = (z2 * 65536.0) as u32;
let z3_int = (z3 * 65536.0) as u32;
if p2_eg.y == p3_eg.y {
fill_bottom_flat_triangle_zbuffered_textured(
p1_eg,
p2_eg,
p3_eg,
z1_int,
z2_int,
z3_int,
uv1,
uv2,
uv3,
texture,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
} else if p1_eg.y == p2_eg.y {
fill_top_flat_triangle_zbuffered_textured(
p1_eg,
p2_eg,
p3_eg,
z1_int,
z2_int,
z3_int,
uv1,
uv2,
uv3,
texture,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
} else {
let t = (p2_eg.y - p1_eg.y) as f32 / (p3_eg.y - p1_eg.y) as f32;
let p4 = Point::new(
(p1_eg.x as f32 + t * (p3_eg.x - p1_eg.x) as f32) as i32,
p2_eg.y,
);
let z4_int = (z1_int as i64 + (t * (z3_int as i64 - z1_int as i64) as f32) as i64) as u32;
let uv4 = [
uv1[0] + t * (uv3[0] - uv1[0]),
uv1[1] + t * (uv3[1] - uv1[1]),
];
fill_bottom_flat_triangle_zbuffered_textured(
p1_eg,
p2_eg,
p4,
z1_int,
z2_int,
z4_int,
uv1,
uv2,
uv4,
texture,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
fill_top_flat_triangle_zbuffered_textured(
p2_eg,
p4,
p3_eg,
z2_int,
z4_int,
z3_int,
uv2,
uv4,
uv3,
texture,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
}
}
#[inline(always)]
fn fill_bottom_flat_triangle_zbuffered_textured<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
p1: Point,
p2: Point,
p3: Point,
z1: u32,
z2: u32,
z3: u32,
uv1: [f32; 2],
uv2: [f32; 2],
uv3: [f32; 2],
texture: &crate::texture::Texture,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let height = p2.y - p1.y;
if height == 0 {
return;
}
let invslope1 = ((p2.x - p1.x) << 16) / height;
let invslope2 = ((p3.x - p1.x) << 16) / height;
let mut curx1 = p1.x << 16;
let mut curx2 = p1.x << 16;
for scanline_y in p1.y..=p2.y {
let dy = scanline_y - p1.y;
let t = dy as f32 / height as f32;
let z_left = if height > 0 {
(z1 as i64 + ((z2 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
let z_right = if height > 0 {
(z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
let uv_left = [
uv1[0] + t * (uv2[0] - uv1[0]),
uv1[1] + t * (uv2[1] - uv1[1]),
];
let uv_right = [
uv1[0] + t * (uv3[0] - uv1[0]),
uv1[1] + t * (uv3[1] - uv1[1]),
];
draw_scanline_zbuffered_textured(
curx1 >> 16,
curx2 >> 16,
scanline_y,
z_left,
z_right,
uv_left,
uv_right,
texture,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
curx1 += invslope1;
curx2 += invslope2;
}
}
#[inline(always)]
fn fill_top_flat_triangle_zbuffered_textured<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
p1: Point,
p2: Point,
p3: Point,
z1: u32,
z2: u32,
z3: u32,
uv1: [f32; 2],
uv2: [f32; 2],
uv3: [f32; 2],
texture: &crate::texture::Texture,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let height = p3.y - p1.y;
if height == 0 {
return;
}
let invslope1 = ((p3.x - p1.x) << 16) / height;
let invslope2 = ((p3.x - p2.x) << 16) / height;
let mut curx1 = p3.x << 16;
let mut curx2 = p3.x << 16;
for scanline_y in (p1.y..=p3.y).rev() {
let dy = scanline_y - p1.y;
let t = dy as f32 / height as f32;
let z_left = if height > 0 {
(z1 as i64 + ((z3 as i64 - z1 as i64) * dy as i64 / height as i64)) as u32
} else {
z1
};
let z_right = if height > 0 {
(z2 as i64 + ((z3 as i64 - z2 as i64) * dy as i64 / height as i64)) as u32
} else {
z2
};
let uv_left = [
uv1[0] + t * (uv3[0] - uv1[0]),
uv1[1] + t * (uv3[1] - uv1[1]),
];
let uv_right = [
uv2[0] + t * (uv3[0] - uv2[0]),
uv2[1] + t * (uv3[1] - uv2[1]),
];
draw_scanline_zbuffered_textured(
curx1 >> 16,
curx2 >> 16,
scanline_y,
z_left,
z_right,
uv_left,
uv_right,
texture,
fb,
zbuffer,
width,
fog_config,
dither_config,
);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
#[inline(always)]
fn draw_scanline_zbuffered_textured<
D: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>,
>(
x1: i32,
x2: i32,
y: i32,
z1: u32,
z2: u32,
uv1: [f32; 2],
uv2: [f32; 2],
texture: &crate::texture::Texture,
fb: &mut D,
zbuffer: &mut [u32],
width: usize,
fog_config: Option<&FogConfig>,
dither_config: Option<&DitherConfig>,
) where
<D as DrawTarget>::Error: Debug,
{
let start = x1.min(x2);
let end = x1.max(x2);
let span_width = end - start;
for x in start..=end {
if x < 0 || y < 0 {
continue;
}
let zbuffer_idx = y as usize * width + x as usize;
if zbuffer_idx >= zbuffer.len() {
continue;
}
let t = if span_width > 0 {
(x - start) as f32 / span_width as f32
} else {
0.0
};
let z = if span_width > 0 {
(z1 as i64 + ((z2 as i64 - z1 as i64) * (x - start) as i64 / span_width as i64)) as u32
} else {
z1
};
if z < zbuffer[zbuffer_idx].saturating_add(DEPTH_EPSILON) {
zbuffer[zbuffer_idx] = z;
let u = uv1[0] + t * (uv2[0] - uv1[0]);
let v = uv1[1] + t * (uv2[1] - uv1[1]);
let mut final_color = texture.sample(u, v);
if let Some(fog) = fog_config {
final_color = fog.apply(final_color, z);
}
if let Some(dither) = dither_config {
final_color = dither.apply(final_color, x, y);
}
fb.draw_iter([embedded_graphics_core::Pixel(Point::new(x, y), final_color)])
.unwrap();
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_core::prelude::*;
use nalgebra::Point2;
struct MockFramebuffer {
pixels: std::vec::Vec<(i32, i32, Rgb565)>,
}
impl MockFramebuffer {
fn new() -> Self {
Self {
pixels: std::vec::Vec::new(),
}
}
fn contains_pixel(&self, x: i32, y: i32) -> bool {
self.pixels.iter().any(|(px, py, _)| *px == x && *py == y)
}
fn pixel_count(&self) -> usize {
self.pixels.len()
}
}
impl DrawTarget for MockFramebuffer {
type Color = Rgb565;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>,
{
for pixel in pixels {
self.pixels.push((pixel.0.x, pixel.0.y, pixel.1));
}
Ok(())
}
}
impl OriginDimensions for MockFramebuffer {
fn size(&self) -> Size {
Size::new(640, 480)
}
}
#[test]
fn test_draw_point() {
let mut fb = MockFramebuffer::new();
let point = Point2::new(10, 20);
let color = Rgb565::CSS_RED;
draw(DrawPrimitive::ColoredPoint(point, color), &mut fb);
assert_eq!(fb.pixel_count(), 1);
assert!(fb.contains_pixel(10, 20));
}
#[test]
fn test_draw_line_horizontal() {
let mut fb = MockFramebuffer::new();
let p1 = Point2::new(10, 20);
let p2 = Point2::new(20, 20);
let color = Rgb565::CSS_GREEN;
draw(DrawPrimitive::Line([p1, p2], color), &mut fb);
assert!(fb.pixel_count() >= 10); assert!(fb.contains_pixel(10, 20));
assert!(fb.contains_pixel(20, 20));
}
#[test]
fn test_draw_line_vertical() {
let mut fb = MockFramebuffer::new();
let p1 = Point2::new(10, 10);
let p2 = Point2::new(10, 20);
let color = Rgb565::CSS_BLUE;
draw(DrawPrimitive::Line([p1, p2], color), &mut fb);
assert!(fb.pixel_count() >= 10);
assert!(fb.contains_pixel(10, 10));
assert!(fb.contains_pixel(10, 20));
}
#[test]
fn test_draw_line_diagonal() {
let mut fb = MockFramebuffer::new();
let p1 = Point2::new(0, 0);
let p2 = Point2::new(10, 10);
let color = Rgb565::CSS_WHITE;
draw(DrawPrimitive::Line([p1, p2], color), &mut fb);
assert!(fb.pixel_count() >= 10);
assert!(fb.contains_pixel(0, 0));
assert!(fb.contains_pixel(10, 10));
}
#[test]
fn test_draw_triangle_flat_bottom() {
let mut fb = MockFramebuffer::new();
let vertices = [
Point2::new(50, 10), Point2::new(30, 30), Point2::new(70, 30), ];
let color = Rgb565::CSS_YELLOW;
draw(DrawPrimitive::ColoredTriangle(vertices, color), &mut fb);
let count = fb.pixel_count();
assert!(count > 0, "Expected pixels to be drawn, got {}", count);
assert!(fb.contains_pixel(50, 10));
}
#[test]
fn test_draw_triangle_flat_top() {
let mut fb = MockFramebuffer::new();
let vertices = [
Point2::new(30, 10), Point2::new(70, 10), Point2::new(50, 30), ];
let color = Rgb565::CSS_CYAN;
draw(DrawPrimitive::ColoredTriangle(vertices, color), &mut fb);
assert!(fb.pixel_count() > 20);
assert!(fb.contains_pixel(50, 30));
}
#[test]
fn test_draw_triangle_general() {
let mut fb = MockFramebuffer::new();
let vertices = [
Point2::new(50, 10),
Point2::new(30, 30),
Point2::new(80, 40),
];
let color = Rgb565::CSS_MAGENTA;
draw(DrawPrimitive::ColoredTriangle(vertices, color), &mut fb);
assert!(fb.pixel_count() > 30);
}
#[test]
fn test_triangle_vertex_sorting() {
let mut fb = MockFramebuffer::new();
let vertices = [
Point2::new(50, 30), Point2::new(30, 10), Point2::new(70, 20), ];
let color = Rgb565::CSS_WHITE;
draw(DrawPrimitive::ColoredTriangle(vertices, color), &mut fb);
assert!(fb.pixel_count() > 10);
}
#[test]
fn test_draw_multiple_primitives() {
let mut fb = MockFramebuffer::new();
draw(
DrawPrimitive::ColoredPoint(Point2::new(5, 5), Rgb565::CSS_RED),
&mut fb,
);
draw(
DrawPrimitive::Line(
[Point2::new(10, 10), Point2::new(20, 20)],
Rgb565::CSS_GREEN,
),
&mut fb,
);
assert!(fb.pixel_count() > 11); assert!(fb.contains_pixel(5, 5));
}
}