1use crate::mesh::{K3dMesh, RenderMode};
20use crate::{DrawPrimitive, K3dengine};
21use core::cmp::Ordering;
22use embedded_graphics_core::pixelcolor::{Rgb565, RgbColor};
23use nalgebra::{Vector3, Vector4};
24
25#[derive(Debug, Clone)]
27pub struct DepthSortedTriangle {
28 pub primitive: DrawPrimitive,
29 pub avg_depth: f32,
30}
31
32impl DepthSortedTriangle {
33 pub const DUMMY: Self = Self {
34 primitive: DrawPrimitive::ColoredPoint(nalgebra::Point2::new(0, 0), Rgb565::BLACK),
35 avg_depth: 0.0,
36 };
37
38 pub fn new(primitive: DrawPrimitive, avg_depth: f32) -> Self {
40 Self {
41 primitive,
42 avg_depth,
43 }
44 }
45}
46
47impl Default for DepthSortedTriangle {
48 fn default() -> Self {
49 Self::DUMMY
50 }
51}
52
53impl K3dengine {
54 pub fn render_painters_algorithm<'a, MS, F>(
67 &self,
68 meshes: MS,
69 triangles: &mut [DepthSortedTriangle],
70 mut callback: F,
71 ) -> usize
72 where
73 MS: IntoIterator<Item = &'a K3dMesh<'a>>,
74 F: FnMut(DrawPrimitive),
75 {
76 let mut count = 0;
77
78 for mesh in meshes {
80 if mesh.geometry.vertices.is_empty() {
81 continue;
82 }
83
84 if self.should_cull_mesh(mesh) {
86 continue;
87 }
88
89 let mesh_pos = mesh.get_position();
91 let distance = (mesh_pos - self.camera.position).norm();
92 let geometry = mesh.select_lod(distance);
93
94 let transform_matrix = self.camera.vp_matrix * mesh.model_matrix;
95 let render_mode = self.resolve_render_mode(&mesh.render_mode);
96
97 match render_mode {
99 RenderMode::Solid
100 | RenderMode::SolidLightDir(_)
101 | RenderMode::BlinnPhong { .. }
102 | RenderMode::SectorBright(_) => {
103 for (face_idx, face) in geometry.faces.iter().enumerate() {
104 let v = geometry.vertices;
105 if face[0] >= v.len() || face[1] >= v.len() || face[2] >= v.len() {
106 continue;
107 }
108
109 let v0_world = mesh.model_matrix.transform_point(&nalgebra::Point3::new(
110 v[face[0]][0],
111 v[face[0]][1],
112 v[face[0]][2],
113 ));
114
115 let mut normal_world_opt: Option<Vector3<f32>> = None;
125 if !geometry.normals.is_empty() && face_idx < geometry.normals.len() {
126 let n = geometry.normals[face_idx];
127 let n_world = mesh
128 .model_matrix
129 .transform_vector(&Vector3::new(n[0], n[1], n[2]));
130 if n_world.norm_squared() > 1e-8 {
131 let n_world = n_world.normalize();
132 if self.is_backface(
133 face,
134 geometry.vertices,
135 mesh.model_matrix,
136 &n_world,
137 ) {
138 continue;
139 }
140 normal_world_opt = Some(n_world);
141 }
142 }
143
144 let v0 = Vector3::new(v[face[0]][0], v[face[0]][1], v[face[0]][2]);
146 let v1 = Vector3::new(v[face[1]][0], v[face[1]][1], v[face[1]][2]);
147 let v2 = Vector3::new(v[face[2]][0], v[face[2]][1], v[face[2]][2]);
148 let normal_world = if let Some(n) = normal_world_opt {
149 n
150 } else {
151 let normal_model = (v1 - v0).cross(&(v2 - v0));
152 if normal_model.norm_squared() <= 1e-8 {
153 continue;
154 }
155 let mut n = mesh
156 .model_matrix
157 .transform_vector(&normal_model)
158 .normalize();
159 if (self.camera.position - v0_world).dot(&n) < 0.0 {
162 n = -n;
163 }
164 n
165 };
166
167 let mut color = match render_mode {
169 RenderMode::SolidLightDir(light_dir) => {
170 let adjusted_dir =
171 Vector3::new(light_dir.x, light_dir.y, -light_dir.z)
172 .normalize();
173 let intensity = normal_world.dot(&adjusted_dir).max(0.0);
174 let ambient = 0.1;
175 let final_intensity =
176 (ambient + (1.0 - ambient) * intensity).clamp(0.0, 1.0);
177 Rgb565::new(
178 (mesh.color.r() as f32 * final_intensity) as u8,
179 (mesh.color.g() as f32 * final_intensity) as u8,
180 (mesh.color.b() as f32 * final_intensity) as u8,
181 )
182 }
183 RenderMode::SectorBright(brightness) => {
184 let wc = Self::face_world_center(
185 face,
186 geometry.vertices,
187 mesh.model_matrix,
188 );
189 self.sector_shaded_color(mesh.color, brightness, wc)
190 }
191 RenderMode::BlinnPhong { .. } | RenderMode::Solid => mesh.color,
193 _ => mesh.color,
194 };
195
196 if !self.point_lights.is_empty() {
197 let wc =
198 Self::face_world_center(face, geometry.vertices, mesh.model_matrix);
199 color = Self::add_tint(color, self.light_tint_at(wc));
200 }
201
202 let clip = [
203 transform_matrix
204 * Vector4::new(v[face[0]][0], v[face[0]][1], v[face[0]][2], 1.0),
205 transform_matrix
206 * Vector4::new(v[face[1]][0], v[face[1]][1], v[face[1]][2], 1.0),
207 transform_matrix
208 * Vector4::new(v[face[2]][0], v[face[2]][1], v[face[2]][2], 1.0),
209 ];
210 let face_sort_depth = {
213 let d = (clip[0].w + clip[1].w + clip[2].w) / 3.0;
214 if d.is_finite() { d } else { 0.0 }
215 };
216
217 self.emit_clipped(clip, color, &mut |prim| {
218 if let DrawPrimitive::ColoredTriangleWithDepth {
219 points,
220 depths: _,
221 color,
222 } = prim
223 {
224 if count < triangles.len() {
225 triangles[count] = DepthSortedTriangle::new(
226 DrawPrimitive::ColoredTriangle(points, color),
227 face_sort_depth,
228 );
229 count += 1;
230 }
231 }
232 });
233 }
234 }
235 _ => {}
237 }
238 }
239
240 triangles[0..count].sort_unstable_by(|a, b| {
242 b.avg_depth
243 .partial_cmp(&a.avg_depth)
244 .unwrap_or(Ordering::Equal)
245 });
246
247 for triangle in triangles[0..count].iter() {
249 callback(triangle.primitive.clone());
250 }
251
252 count
253 }
254
255 #[allow(dead_code)]
257 fn calculate_lit_color(
258 &self,
259 face: &[usize; 3],
260 vertices: &[[f32; 3]],
261 normals: &[[f32; 3]],
262 base_color: Rgb565,
263 light_dir: nalgebra::Vector3<f32>,
264 ) -> Rgb565 {
265 let normal = if !normals.is_empty() && face[0] < normals.len() {
267 nalgebra::Vector3::new(
268 normals[face[0]][0],
269 normals[face[0]][1],
270 normals[face[0]][2],
271 )
272 } else {
273 let v0 = &vertices[face[0]];
275 let v1 = &vertices[face[1]];
276 let v2 = &vertices[face[2]];
277
278 let edge1 = nalgebra::Vector3::new(v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]);
279 let edge2 = nalgebra::Vector3::new(v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]);
280
281 let normal = edge1.cross(&edge2);
282 if normal.norm() > 0.0 {
283 normal.normalize()
284 } else {
285 nalgebra::Vector3::new(0.0, 1.0, 0.0)
286 }
287 };
288
289 let light_intensity = normal.dot(&light_dir.normalize()).max(0.0);
291 let ambient = 0.3;
292 let final_intensity = (ambient + (1.0 - ambient) * light_intensity).clamp(0.0, 1.0);
293
294 let r = (base_color.r() as f32 * final_intensity) as u8;
296 let g = (base_color.g() as f32 * final_intensity) as u8;
297 let b = (base_color.b() as f32 * final_intensity) as u8;
298
299 Rgb565::new(r, g, b)
300 }
301}
302
303#[cfg(test)]
304mod tests {
305
306 use super::*;
307 use embedded_graphics_core::pixelcolor::Rgb565;
308 use nalgebra::Point3;
309 use std::cmp::Ordering;
310
311 #[test]
312 fn test_sorting_by_depth() {
313 let mut triangles = std::vec![
314 DepthSortedTriangle {
315 primitive: DrawPrimitive::Line(
316 [nalgebra::Point2::new(0, 0), nalgebra::Point2::new(1, 1)],
317 Rgb565::new(31, 0, 0),
318 ),
319 avg_depth: 10.0,
320 },
321 DepthSortedTriangle {
322 primitive: DrawPrimitive::Line(
323 [nalgebra::Point2::new(0, 0), nalgebra::Point2::new(1, 1)],
324 Rgb565::new(0, 63, 0),
325 ),
326 avg_depth: 5.0,
327 },
328 DepthSortedTriangle {
329 primitive: DrawPrimitive::Line(
330 [nalgebra::Point2::new(0, 0), nalgebra::Point2::new(1, 1)],
331 Rgb565::new(0, 0, 31),
332 ),
333 avg_depth: 15.0,
334 },
335 ];
336
337 triangles.sort_by(|a, b| {
338 b.avg_depth
339 .partial_cmp(&a.avg_depth)
340 .unwrap_or(Ordering::Equal)
341 });
342
343 assert_eq!(triangles[0].avg_depth, 15.0);
345 assert_eq!(triangles[1].avg_depth, 10.0);
346 assert_eq!(triangles[2].avg_depth, 5.0);
347 }
348
349 #[test]
350 fn painters_algorithm_clips_partially_offscreen_triangle() {
351 let mut engine = K3dengine::new(320, 240);
352 engine.camera.set_position(Point3::new(0.0, 0.0, 5.0));
353 engine.camera.set_target(Point3::new(0.0, 0.0, 0.0));
354
355 let vertices = [
356 [-1.0f32, -1.0, 0.0],
357 [1.0f32, -1.0, 0.0],
358 [20.0f32, 2.0, 0.0], ];
360 let faces = [[0usize, 1usize, 2usize]];
361 let geometry = crate::mesh::Geometry {
362 vertices: &vertices,
363 faces: &faces,
364 colors: &[],
365 lines: &[],
366 normals: &[],
367 vertex_normals: &[],
368 uvs: &[],
369 texture_id: None,
370 };
371 let mut mesh = crate::mesh::K3dMesh::new(geometry);
372 mesh.set_render_mode(crate::mesh::RenderMode::Solid);
373 mesh.set_color(Rgb565::new(31, 0, 0));
374
375 let mut triangles = std::vec![DepthSortedTriangle::DUMMY; 256];
376 let count =
377 engine.render_painters_algorithm(std::iter::once(&mesh), &mut triangles, |_| {});
378
379 assert!(count > 0);
382 }
383
384 #[test]
385 fn painters_clipped_fan_pieces_share_sort_depth() {
386 let mut engine = K3dengine::new(320, 240);
387 engine.camera.set_position(Point3::new(0.0, 0.0, 5.0));
388 engine.camera.set_target(Point3::new(0.0, 0.0, 0.0));
389
390 let vertices = [
391 [-1.0f32, -1.0, 0.0],
392 [1.0f32, -1.0, 0.0],
393 [30.0f32, 2.0, 0.0], ];
395 let faces = [[0usize, 1usize, 2usize]];
396 let geometry = crate::mesh::Geometry {
397 vertices: &vertices,
398 faces: &faces,
399 colors: &[],
400 lines: &[],
401 normals: &[],
402 vertex_normals: &[],
403 uvs: &[],
404 texture_id: None,
405 };
406 let mut mesh = crate::mesh::K3dMesh::new(geometry);
407 mesh.set_render_mode(crate::mesh::RenderMode::Solid);
408 mesh.set_color(Rgb565::new(0, 63, 0));
409
410 let mut triangles = std::vec![DepthSortedTriangle::DUMMY; 256];
411 let count =
412 engine.render_painters_algorithm(std::iter::once(&mesh), &mut triangles, |_| {});
413 assert!(count > 0);
414 let first = triangles[0].avg_depth;
415 assert!(
416 triangles[0..count]
417 .iter()
418 .all(|t| (t.avg_depth - first).abs() < 1e-5)
419 );
420 }
421}