1#[cfg(not(test))]
2use crate::drm_render_target::RenderTarget;
3use crate::{doublebuffer::DoubleBuffer, framebuffer::DmaReadyFramebuffer};
4use camera::Camera;
5use embedded_graphics_core::pixelcolor::Bgr888;
6use embedded_graphics_core::pixelcolor::RgbColor;
7use log::trace;
8use mesh::K3dMesh;
9use mesh::RenderMode;
10use nalgebra::Matrix4;
11use nalgebra::Point2;
12use nalgebra::Point3;
13use nalgebra::Vector3;
14
15pub mod camera;
16mod card;
17pub mod doublebuffer;
18pub mod draw;
19pub mod drm_render_target;
20pub mod framebuffer;
21pub mod mesh;
22pub mod perfcounter;
23
24#[cfg(test)]
27const WIDTH: usize = 80;
28#[cfg(test)]
29const HEIGHT: usize = 60;
30
31#[derive(Debug)]
32pub enum DrawPrimitive {
33 ColoredPoint(Point2<i32>, Bgr888),
34 Line([Point2<i32>; 2], Bgr888),
35 ColoredTriangle([Point2<i32>; 3], Bgr888),
36}
37
38pub struct K3dengine {
39 pub camera: Camera,
40 width: usize,
41 height: usize,
42 buffers: DoubleBuffer,
43}
44
45impl Default for K3dengine {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl K3dengine {
52 pub fn new() -> Self {
53 #[cfg(not(test))]
54 let display = RenderTarget::default();
55 #[cfg(not(test))]
56 let width = display.mode.size().0 as usize;
57 #[cfg(not(test))]
58 let height = display.mode.size().1 as usize;
59 #[cfg(test)]
60 let (width, height) = (WIDTH, HEIGHT);
61
62 let mut buffers = DoubleBuffer::new(width, height);
63
64 #[cfg(not(test))]
65 buffers.start_thread(display);
66 #[cfg(test)]
67 buffers.start_thread();
68
69 K3dengine {
70 camera: Camera::new(width as f32 / height as f32),
71 width,
72 height,
73 buffers,
74 }
75 }
76
77 pub fn get_current_framebuffer(&mut self) -> &mut DmaReadyFramebuffer {
78 self.buffers.get_current_framebuffer()
79 }
80
81 pub fn swap_framebuffer(&mut self) -> &mut DmaReadyFramebuffer {
82 self.buffers.swap_framebuffer()
83 }
84
85 #[cfg(not(feature = "tokio-threads"))]
86 pub fn send_framebuffer(&mut self) {
87 self.buffers.send_framebuffer();
88 }
89
90 #[cfg(feature = "tokio-threads")]
91 pub async fn send_framebuffer(&mut self) {
92 self.buffers.send_framebuffer().await;
93 }
94
95 fn transform_point(&self, point: &[f32; 3], model_matrix: Matrix4<f32>) -> Option<Point3<i32>> {
96 let point = nalgebra::Vector4::new(point[0], point[1], point[2], 1.0);
97 let point = model_matrix * point;
98
99 if point.w < 0.0 {
100 return None;
101 }
102 if point.z < self.camera.near || point.z > self.camera.far {
103 return None;
104 }
105
106 let point = Point3::from_homogeneous(point)?;
107
108 Some(Point3::new(
109 ((1.0 + point.x) * 0.5 * self.width as f32) as i32,
110 ((1.0 - point.y) * 0.5 * self.height as f32) as i32,
111 (point.z * (self.camera.far - self.camera.near) + self.camera.near) as i32,
112 ))
113 }
114
115 fn transform_points<const N: usize>(
116 &self,
117 indices: &[usize; N],
118 vertices: &[[f32; 3]],
119 model_matrix: Matrix4<f32>,
120 ) -> Option<[Point3<i32>; N]> {
121 let mut ret = [Point3::new(0, 0, 0); N];
122
123 for i in 0..N {
124 ret[i] = self.transform_point(&vertices[indices[i]], model_matrix)?;
125 }
126
127 Some(ret)
128 }
129
130 pub fn render<'a, MS, F>(&self, meshes: MS, mut callback: F)
131 where
132 MS: IntoIterator<Item = &'a K3dMesh<'a>>,
133 F: FnMut(DrawPrimitive),
134 {
135 for mesh in meshes {
136 if mesh.geometry.vertices.is_empty() {
137 continue;
138 }
139
140 let transform_matrix = self.camera.vp_matrix * mesh.model_matrix;
141 trace!("rendering {:?}", mesh.render_mode);
142
143 match mesh.render_mode {
144 RenderMode::Points => {
145 let screen_space_points = mesh
146 .geometry
147 .vertices
148 .iter()
149 .filter_map(|v| self.transform_point(v, transform_matrix));
150
151 if mesh.geometry.colors.is_empty() {
152 for point in screen_space_points {
153 callback(DrawPrimitive::ColoredPoint(point.xy(), mesh.color));
154 }
155 } else {
156 for (point, color) in screen_space_points.zip(mesh.geometry.colors) {
157 callback(DrawPrimitive::ColoredPoint(point.xy(), *color));
158 }
159 }
160 }
161
162 RenderMode::Lines if !mesh.geometry.lines.is_empty() => {
163 let mut line_draw = |line: &[usize; 2], color: &Bgr888| {
164 if let Some([p1, p2]) =
165 self.transform_points(line, mesh.geometry.vertices, transform_matrix)
166 {
167 callback(DrawPrimitive::Line([p1.xy(), p2.xy()], *color));
168 }
169 };
170
171 if mesh.geometry.colors.is_empty() {
172 for line in mesh.geometry.lines {
173 line_draw(line, &mesh.color)
174 }
175 } else {
176 for (line, color) in mesh.geometry.lines.iter().zip(mesh.geometry.colors) {
177 line_draw(line, color)
178 }
179 }
180 }
181
182 RenderMode::Lines if !mesh.geometry.faces.is_empty() => {
183 let mut line_draw = |face: &[usize; 3], color: &Bgr888| {
184 if let Some([p1, p2, p3]) =
185 self.transform_points(face, mesh.geometry.vertices, transform_matrix)
186 {
187 callback(DrawPrimitive::Line([p1.xy(), p2.xy()], *color));
188 callback(DrawPrimitive::Line([p2.xy(), p3.xy()], *color));
189 callback(DrawPrimitive::Line([p3.xy(), p1.xy()], *color));
190 }
191 };
192
193 if mesh.geometry.colors.is_empty() {
194 for face in mesh.geometry.faces {
195 line_draw(face, &mesh.color);
196 }
197 } else {
198 for (face, color) in mesh.geometry.faces.iter().zip(mesh.geometry.colors) {
199 line_draw(face, color);
200 }
201 }
202 }
203
204 RenderMode::Lines => {}
205
206 RenderMode::SolidLightDir(direction) => {
207 let mut solid_render =
208 |face: &[usize; 3], normal: &[f32; 3], color: &Bgr888| {
209 let normal = Vector3::new(normal[0], normal[1], normal[2]);
211
212 let transformed_normal = mesh.model_matrix.transform_vector(&normal);
213
214 if self.camera.get_direction().dot(&transformed_normal) >= 0.0 {
215 return;
216 }
217
218 if let Some([p1, p2, p3]) = self.transform_points(
219 face,
220 mesh.geometry.vertices,
221 transform_matrix,
222 ) {
223 let color_as_float = Vector3::new(
224 color.r() as f32 / 32.0,
225 color.g() as f32 / 64.0,
226 color.b() as f32 / 32.0,
227 );
228
229 let mut final_color = Vector3::new(0.0f32, 0.0, 0.0);
230
231 let intensity = transformed_normal.dot(&direction);
232
233 let intensity = intensity.max(0.0);
234
235 final_color += color_as_float * intensity + color_as_float * 0.4;
236
237 let final_color = Vector3::new(
238 final_color.x.clamp(0.0, 1.0),
239 final_color.y.clamp(0.0, 1.0),
240 final_color.z.clamp(0.0, 1.0),
241 );
242
243 let color = Bgr888::new(
244 (final_color.x * 31.0) as u8,
245 (final_color.y * 63.0) as u8,
246 (final_color.z * 31.0) as u8,
247 );
248 callback(DrawPrimitive::ColoredTriangle(
249 [p1.xy(), p2.xy(), p3.xy()],
250 color,
251 ));
252 };
253 };
254
255 if mesh.geometry.colors.is_empty() {
256 for (face, normal) in mesh.geometry.faces.iter().zip(mesh.geometry.normals)
257 {
258 solid_render(face, normal, &mesh.color);
259 }
260 } else {
261 for ((face, normal), color) in mesh
262 .geometry
263 .faces
264 .iter()
265 .zip(mesh.geometry.normals)
266 .zip(mesh.geometry.colors)
267 {
268 solid_render(face, normal, color);
269 }
270 }
271 }
272
273 RenderMode::Solid if mesh.geometry.normals.is_empty() => {
274 let mut solid_render = |face: &[usize; 3], color: &Bgr888| {
275 if let Some([p1, p2, p3]) =
276 self.transform_points(face, mesh.geometry.vertices, transform_matrix)
277 {
278 callback(DrawPrimitive::ColoredTriangle(
279 [p1.xy(), p2.xy(), p3.xy()],
280 *color,
281 ));
282 }
283 };
284
285 if mesh.geometry.colors.is_empty() {
286 for face in mesh.geometry.faces.iter() {
287 solid_render(face, &mesh.color);
288 }
289 } else {
290 for (face, color) in mesh.geometry.faces.iter().zip(mesh.geometry.colors) {
291 solid_render(face, color);
292 }
293 }
294 }
295
296 RenderMode::Solid => {
297 let mut solid_render =
298 |face: &[usize; 3], normal: &[f32; 3], color: &Bgr888| {
299 let normal = Vector3::new(normal[0], normal[1], normal[2]);
301 let transformed_normal = mesh.model_matrix.transform_vector(&normal);
302 let bfc = self.camera.get_direction().dot(&transformed_normal);
303
304 if self.camera.get_direction().dot(&transformed_normal) >= 0.0 {
305 trace!(
306 "Culled: N: {:?} / TN: {:?} / C: {:?} / A: {:?}",
307 normal, transformed_normal, color, bfc
308 );
309 return;
310 }
311 trace!(
312 "Rendered: N: {:?} / TN: {:?} / C: {:?} / A: {:?}",
313 normal, transformed_normal, color, bfc
314 );
315
316 if let Some([p1, p2, p3]) = self.transform_points(
317 face,
318 mesh.geometry.vertices,
319 transform_matrix,
320 ) {
321 callback(DrawPrimitive::ColoredTriangle(
322 [p1.xy(), p2.xy(), p3.xy()],
323 *color,
324 ));
325 }
326 };
327
328 if mesh.geometry.colors.is_empty() {
329 for (face, normal) in mesh.geometry.faces.iter().zip(mesh.geometry.normals)
330 {
331 solid_render(face, normal, &mesh.color);
332 }
333 } else {
334 for ((face, normal), color) in mesh
335 .geometry
336 .faces
337 .iter()
338 .zip(mesh.geometry.normals)
339 .zip(mesh.geometry.colors)
340 {
341 solid_render(face, normal, color);
342 }
343 }
344 }
345 }
346 }
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353 use mesh::Geometry;
354 use nalgebra::{Matrix4, Point3, Vector3};
355
356 fn create_test_mesh() -> K3dMesh<'static> {
358 let vertices = &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
360 let faces = &[[0, 1, 2]];
361 let colors = &[];
362 let lines = &[[0, 1], [1, 2], [2, 0]];
363 let normals = &[[0.0, 0.0, 1.0]];
364
365 let geometry = Geometry {
366 vertices,
367 faces,
368 colors,
369 lines,
370 normals,
371 };
372
373 let mut mesh = K3dMesh::new(geometry);
374 mesh.set_color(Bgr888::WHITE);
375 mesh
376 }
377
378 #[test]
379 fn test_engine_creation() {
380 let engine = K3dengine::new();
381
382 assert_eq!(engine.width, WIDTH);
383 assert_eq!(engine.height, HEIGHT);
384 }
386
387 #[test]
388 fn test_transform_point_in_view() {
389 let mut engine = K3dengine::new();
390 engine.camera.set_position(Point3::new(0.0, 0.0, 5.0));
392 engine.camera.set_target(Point3::new(0.0, 0.0, 0.0));
393
394 let point = [0.0, 0.0, 0.0];
396
397 let result = engine.transform_point(&point, Matrix4::identity());
399
400 if result.is_none() {
401 println!("Transform returned None for point in view");
403 println!("Camera position: {:?}", engine.camera.position);
404 println!(
405 "Camera near: {}, far: {}",
406 engine.camera.near, engine.camera.far
407 );
408 }
409
410 }
413
414 #[test]
415 fn test_transform_point_behind_camera() {
416 let engine = K3dengine::new();
419
420 let point = [0.0, 0.0, 100.0];
422 let _result = engine.transform_point(&point, Matrix4::identity());
423
424 }
427
428 #[test]
429 fn test_transform_point_outside_frustum() {
430 let engine = K3dengine::new();
431
432 let point = [0.0, 0.0, -100.0]; let identity_matrix = Matrix4::identity();
435
436 let result = engine.transform_point(&point, identity_matrix);
437
438 assert!(result.is_none());
440 }
441
442 #[test]
443 fn test_transform_points() {
444 let mut engine = K3dengine::new();
445 engine.camera.set_position(Point3::new(0.0, 0.0, 5.0));
447 engine.camera.set_target(Point3::new(0.0, 0.0, 0.0));
448
449 let vertices = [
451 [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], ];
455
456 let indices = [0, 1, 2];
457
458 let _result = engine.transform_points(&indices, &vertices, Matrix4::identity());
460
461 }
464
465 #[test]
466 fn test_render_points_mode() {
467 let engine = K3dengine::new();
468
469 let mut mesh = create_test_mesh();
471 mesh.set_render_mode(RenderMode::Points);
472
473 mesh.set_position(0.0, 0.0, -5.0);
475
476 let mut primitives = Vec::new();
478 engine.render(std::iter::once(&mesh), |primitive| {
479 primitives.push(primitive);
480 });
481
482 assert_eq!(primitives.len(), 3);
484
485 for primitive in primitives {
487 match primitive {
488 DrawPrimitive::ColoredPoint(_, color) => {
489 assert_eq!(color, Bgr888::WHITE);
490 }
491 _ => panic!("Expected ColoredPoint primitive"),
492 }
493 }
494 }
495
496 #[test]
497 fn test_render_lines_mode() {
498 let engine = K3dengine::new();
499
500 let mut mesh = create_test_mesh();
502 mesh.set_render_mode(RenderMode::Lines);
503
504 mesh.set_position(0.0, 0.0, -5.0);
506
507 let mut primitives = Vec::new();
509 engine.render(std::iter::once(&mesh), |primitive| {
510 primitives.push(primitive);
511 });
512
513 assert_eq!(primitives.len(), 3);
515
516 for primitive in primitives {
518 match primitive {
519 DrawPrimitive::Line(_, color) => {
520 assert_eq!(color, Bgr888::WHITE);
521 }
522 _ => panic!("Expected Line primitive"),
523 }
524 }
525 }
526
527 #[test]
528 fn test_render_solid_mode() {
529 let engine = K3dengine::new();
530
531 let mut mesh = create_test_mesh();
533 mesh.set_render_mode(RenderMode::Solid);
534
535 mesh.set_position(0.0, 0.0, -5.0);
537
538 let mut primitives = Vec::new();
540 engine.render(std::iter::once(&mesh), |primitive| {
541 primitives.push(primitive);
542 });
543
544 assert_eq!(primitives.len(), 1);
546
547 for primitive in primitives {
549 match primitive {
550 DrawPrimitive::ColoredTriangle(_, color) => {
551 assert_eq!(color, Bgr888::WHITE);
552 }
553 _ => panic!("Expected ColoredTriangle primitive"),
554 }
555 }
556 }
557
558 #[test]
559 fn test_render_solid_light_mode() {
560 let engine = K3dengine::new();
561
562 let mut mesh = create_test_mesh();
564 let light_dir = Vector3::new(0.0, 0.0, 1.0);
565 mesh.set_render_mode(RenderMode::SolidLightDir(light_dir));
566
567 mesh.set_position(0.0, 0.0, -5.0);
569
570 let mut primitives = Vec::new();
572 engine.render(std::iter::once(&mesh), |primitive| {
573 primitives.push(primitive);
574 });
575
576 assert_eq!(primitives.len(), 1);
578
579 for primitive in primitives {
581 match primitive {
582 DrawPrimitive::ColoredTriangle(_, _) => {
583 }
585 _ => panic!("Expected ColoredTriangle primitive"),
586 }
587 }
588 }
589
590 #[test]
591 fn test_render_backface_culling() {
592 let mut engine = K3dengine::new();
593
594 engine.camera.set_position(Point3::new(0.0, 0.0, 5.0));
596 engine.camera.set_target(Point3::new(0.0, 0.0, 0.0));
597
598 let vertices = &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
602 let faces = &[[0, 1, 2]];
603 let colors = &[];
604 let lines = &[];
605 let normals = &[[0.0, 0.0, -1.0]];
607
608 let geometry = Geometry {
609 vertices,
610 faces,
611 colors,
612 lines,
613 normals,
614 };
615
616 let mut mesh = K3dMesh::new(geometry);
617 mesh.set_render_mode(RenderMode::Solid);
618
619 let mut primitives = Vec::new();
623 engine.render(std::iter::once(&mesh), |primitive| {
624 primitives.push(primitive);
625 });
626 }
627
628 #[test]
629 fn test_render_with_vertex_colors() {
630 let engine = K3dengine::new();
631
632 let vertices = &[[0.0, 0.0, -5.0], [1.0, 0.0, -5.0], [0.0, 1.0, -5.0]];
634 let faces = &[];
635 let red = Bgr888::new(0, 0, 255); let green = Bgr888::new(0, 255, 0); let blue = Bgr888::new(255, 0, 0); let colors = &[red, green, blue];
639 let lines = &[];
640 let normals = &[];
641
642 let geometry = Geometry {
643 vertices,
644 faces,
645 colors,
646 lines,
647 normals,
648 };
649
650 let mut mesh = K3dMesh::new(geometry);
651 mesh.set_render_mode(RenderMode::Points);
652
653 let mut primitives = Vec::new();
655 engine.render(std::iter::once(&mesh), |primitive| {
656 primitives.push(primitive);
657 });
658
659 assert_eq!(primitives.len(), 3);
661
662 let mut colors = Vec::new();
664 for primitive in primitives {
665 match primitive {
666 DrawPrimitive::ColoredPoint(_, color) => {
667 colors.push(color);
668 }
669 _ => panic!("Expected ColoredPoint primitive"),
670 }
671 }
672
673 assert!(colors.contains(&red));
675 assert!(colors.contains(&green));
676 assert!(colors.contains(&blue));
677 }
678}