use embedded_3dgfx::DrawPrimitive;
use embedded_3dgfx::K3dengine;
use embedded_3dgfx::command_buffer::{CommandBuffer, RenderCommand};
use embedded_3dgfx::draw::draw;
use embedded_3dgfx::mesh::{Geometry, K3dMesh, RenderMode};
#[cfg(feature = "perfcounter")]
use embedded_3dgfx::perfcounter::PerformanceCounter;
use embedded_3dgfx::renderer::FrameCtx;
use embedded_graphics::mono_font::{MonoTextStyle, ascii::FONT_6X10};
use embedded_graphics::text::Text;
use embedded_graphics_core::pixelcolor::{Rgb565, RgbColor, WebColors};
use embedded_graphics_core::prelude::*;
use embedded_graphics_simulator::{
OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, sdl2::Keycode,
};
use nalgebra::Point3;
use std::thread;
use std::time::Duration;
#[derive(Clone)]
struct SortedPrimitive {
primitive: DrawPrimitive,
depth_key: i32,
original_order: usize,
}
fn collect_sorted_primitives<const MAX: usize>(
commands: &CommandBuffer<MAX>,
out: &mut Vec<SortedPrimitive>,
) {
out.clear();
for (idx, cmd) in commands.iter().enumerate() {
let RenderCommand::Draw(prim) = cmd else {
continue;
};
if let Some(p) = as_painter_primitive(prim) {
out.push(SortedPrimitive {
primitive: p,
depth_key: depth_key_for_primitive(prim),
original_order: idx,
});
}
}
out.sort_by(|a, b| {
b.depth_key
.cmp(&a.depth_key)
.then(a.original_order.cmp(&b.original_order))
});
}
fn depth_key_for_primitive(prim: &DrawPrimitive) -> i32 {
let depth = match prim {
DrawPrimitive::ColoredTriangleWithDepth { depths, .. }
| DrawPrimitive::GouraudTriangleWithDepth { depths, .. }
| DrawPrimitive::TexturedTriangleWithDepth { depths, .. }
| DrawPrimitive::LightmappedTriangle { depths, .. } => {
(depths[0] + depths[1] + depths[2]) / 3.0
}
_ => 0.0,
};
if depth.is_finite() {
(depth * 64.0).round() as i32
} else {
0
}
}
fn as_painter_primitive(prim: &DrawPrimitive) -> Option<DrawPrimitive> {
match prim {
DrawPrimitive::ColoredTriangleWithDepth { points, color, .. } => {
Some(DrawPrimitive::ColoredTriangle(*points, *color))
}
DrawPrimitive::GouraudTriangleWithDepth { points, colors, .. } => {
Some(DrawPrimitive::GouraudTriangle {
points: *points,
colors: *colors,
})
}
DrawPrimitive::ColoredTriangle(..)
| DrawPrimitive::GouraudTriangle { .. }
| DrawPrimitive::Line(..)
| DrawPrimitive::ColoredPoint(..) => Some(prim.clone()),
DrawPrimitive::TexturedTriangle { .. }
| DrawPrimitive::TexturedTriangleWithDepth { .. }
| DrawPrimitive::TranslucentTriangleWithDepth { .. }
| DrawPrimitive::LightmappedTriangle { .. } => None,
}
}
fn main() {
const WIDTH: usize = 800;
const HEIGHT: usize = 600;
let mut display = SimulatorDisplay::<Rgb565>::new(Size::new(800, 600));
let mut commands = CommandBuffer::<8192>::new();
let mut cmd_mesh_0 = CommandBuffer::<4096>::new();
let mut cmd_mesh_1 = CommandBuffer::<4096>::new();
let mut cmd_mesh_2 = CommandBuffer::<4096>::new();
let output_settings = OutputSettingsBuilder::new().scale(1).build();
let mut window = Window::new("Painter's Algorithm Demo - NO Z-BUFFER!", &output_settings);
let mut engine = K3dengine::new(800, 600);
engine.clear_caps();
engine.camera.set_position(Point3::new(0.0, 3.0, 12.0));
engine.camera.set_target(Point3::new(0.0, 0.0, 0.0));
let mut perf = PerformanceCounter::new();
perf.only_fps(true);
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::CSS_WHITE);
let cube_vertices = [
[-1.0, -1.0, -1.0],
[1.0, -1.0, -1.0],
[1.0, 1.0, -1.0],
[-1.0, 1.0, -1.0],
[-1.0, -1.0, 1.0],
[1.0, -1.0, 1.0],
[1.0, 1.0, 1.0],
[-1.0, 1.0, 1.0],
];
let cube_faces = [
[0, 1, 2],
[0, 2, 3], [1, 5, 6],
[1, 6, 2], [5, 4, 7],
[5, 7, 6], [4, 0, 3],
[4, 3, 7], [3, 2, 6],
[3, 6, 7], [4, 5, 1],
[4, 1, 0], ];
let cube_normals = [
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0], [-1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0], [0.0, 0.0, -1.0],
[0.0, 0.0, -1.0], [1.0, 0.0, 0.0],
[1.0, 0.0, 0.0], [0.0, -1.0, 0.0],
[0.0, -1.0, 0.0], [0.0, 1.0, 0.0],
[0.0, 1.0, 0.0], ];
let cube_geom = Geometry {
vertices: &cube_vertices,
faces: &cube_faces,
colors: &[],
lines: &[],
normals: &cube_normals,
vertex_normals: &[],
uvs: &[],
texture_id: None,
};
let mut cube1 = K3dMesh::new(cube_geom);
cube1.set_position(-4.2, 0.0, -1.2);
cube1.set_scale(1.1);
cube1.set_color(Rgb565::new(31, 0, 0)); cube1.set_render_mode(RenderMode::Solid);
let mut cube2 = K3dMesh::new(cube_geom);
cube2.set_position(0.0, 0.0, -3.6);
cube2.set_scale(1.1);
cube2.set_color(Rgb565::new(0, 63, 0)); cube2.set_render_mode(RenderMode::Solid);
let mut cube3 = K3dMesh::new(cube_geom);
cube3.set_position(4.2, 0.0, -6.0);
cube3.set_scale(1.1);
cube3.set_color(Rgb565::new(0, 0, 31)); cube3.set_render_mode(RenderMode::Solid);
let mut sorted: Vec<SortedPrimitive> = Vec::new();
let mut sorted_m0: Vec<SortedPrimitive> = Vec::new();
let mut sorted_m1: Vec<SortedPrimitive> = Vec::new();
let mut sorted_m2: Vec<SortedPrimitive> = Vec::new();
let mut use_painters = true;
let mut rotation = 0.0f32;
let mut auto_rotate = true;
println!("Painter's Algorithm Demo");
println!("========================");
println!("Memory Savings: ~1.92MB (no Z-buffer needed!)");
println!();
println!("Controls:");
println!(" SPACE - Toggle Painter's Algorithm / Z-buffer mode");
println!(" R - Toggle auto-rotation");
println!(" ESC - Exit");
println!("\nStarting render loop...");
display.clear(Rgb565::BLACK).unwrap();
window.update(&display);
'running: loop {
perf.start_of_frame();
for event in window.events() {
match event {
SimulatorEvent::KeyDown { keycode, .. } => match keycode {
Keycode::Escape => break 'running,
Keycode::Space => {
use_painters = !use_painters;
println!(
"Rendering mode: {}",
if use_painters {
"PAINTER'S ALGORITHM (No Z-buffer)"
} else {
"Z-BUFFER (1.92MB RAM)"
}
);
}
Keycode::R => {
auto_rotate = !auto_rotate;
println!("Auto-rotate: {}", if auto_rotate { "ON" } else { "OFF" });
}
_ => {}
},
SimulatorEvent::Quit => break 'running,
_ => {}
}
}
if auto_rotate {
rotation += 0.02;
}
cube1.set_attitude(0.18, rotation, 0.12);
cube2.set_attitude(0.18, rotation + 0.35, 0.12);
cube3.set_attitude(0.18, rotation + 0.7, 0.12);
display.clear(Rgb565::BLACK).unwrap();
let meshes = [&cube1, &cube2, &cube3];
let triangle_count;
engine
.record(meshes.iter().copied(), &mut commands, None)
.unwrap();
if use_painters {
engine
.record(std::iter::once(&cube1), &mut cmd_mesh_0, None)
.unwrap();
engine
.record(std::iter::once(&cube2), &mut cmd_mesh_1, None)
.unwrap();
engine
.record(std::iter::once(&cube3), &mut cmd_mesh_2, None)
.unwrap();
collect_sorted_primitives(&cmd_mesh_0, &mut sorted_m0);
collect_sorted_primitives(&cmd_mesh_1, &mut sorted_m1);
collect_sorted_primitives(&cmd_mesh_2, &mut sorted_m2);
sorted.clear();
sorted.extend(sorted_m0.iter().cloned());
sorted.extend(sorted_m1.iter().cloned());
sorted.extend(sorted_m2.iter().cloned());
let mut mesh_order = [
(
0usize,
(cube1.get_position() - engine.camera.position).norm_squared(),
),
(
1usize,
(cube2.get_position() - engine.camera.position).norm_squared(),
),
(
2usize,
(cube3.get_position() - engine.camera.position).norm_squared(),
),
];
mesh_order.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal));
for (idx, _) in mesh_order {
let bucket = match idx {
0 => &sorted_m0,
1 => &sorted_m1,
_ => &sorted_m2,
};
for item in bucket {
draw(item.primitive.clone(), &mut display);
}
}
triangle_count = sorted.len();
} else {
let mut zbuffer = vec![u32::MAX; WIDTH * HEIGHT];
let mut frame = FrameCtx {
zbuffer: &mut zbuffer,
width: WIDTH,
height: HEIGHT,
};
engine
.execute::<_, 8192>(&mut display, &mut frame, &commands, None)
.unwrap();
triangle_count = commands
.iter()
.filter(|c| matches!(c, embedded_3dgfx::command_buffer::RenderCommand::Draw(_)))
.count();
}
perf.print();
let zbuffer_size_mb = (800 * 600 * 4) as f32 / (1024.0 * 1024.0);
let mode_str = if use_painters {
"PAINTER'S ALGORITHM"
} else {
"Z-BUFFER MODE"
};
let info_text = format!(
"{}\nMode: {}\nTriangles: {}\nZ-Buffer: {} ({:.2} MB)\nMemory Saved: {:.2} MB",
perf.get_text(),
mode_str,
triangle_count,
if use_painters { "NONE" } else { "ACTIVE" },
if use_painters { 0.0 } else { zbuffer_size_mb },
if use_painters { zbuffer_size_mb } else { 0.0 }
);
Text::new(&info_text, Point::new(10, 20), text_style)
.draw(&mut display)
.unwrap();
let help_text = "SPACE: Toggle mode | R: Rotate | ESC: Exit";
Text::new(help_text, Point::new(10, 580), text_style)
.draw(&mut display)
.unwrap();
window.update(&display);
thread::sleep(Duration::from_millis(16)); }
println!("\nExiting...");
}