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