1#![no_std]
2use camera::Camera;
3use embedded_graphics_core::pixelcolor::Rgb565;
4use embedded_graphics_core::pixelcolor::RgbColor;
5use mesh::K3dMesh;
6use mesh::RenderMode;
7use nalgebra::Matrix4;
8use nalgebra::Point2;
9use nalgebra::Point3;
10use nalgebra::Vector3;
11
12#[allow(unused_imports)]
15use nalgebra::ComplexField;
16
17pub mod animation;
18pub mod billboard;
19pub mod camera;
20pub mod display_backend;
21pub mod draw;
22pub mod lut;
23pub mod mesh;
24#[cfg(feature = "std")]
25pub mod painters;
26#[cfg(feature = "perfcounter")]
27pub mod perfcounter;
28pub mod physics;
29pub mod skeleton;
30pub mod softbody;
31pub mod swapchain;
32pub mod texture;
33
34pub use embedded_graphics_framebuf::{
36 FrameBuf,
37 backends::{DMACapableFrameBufferBackend, EndianCorrectedBuffer, EndianCorrection},
38};
39
40pub use draw::ReadPixel;
41
42#[derive(Debug, Clone)]
43pub enum DrawPrimitive {
44 ColoredPoint(Point2<i32>, Rgb565),
45 Line([Point2<i32>; 2], Rgb565),
46 ColoredTriangle([Point2<i32>; 3], Rgb565),
47 ColoredTriangleWithDepth {
48 points: [Point2<i32>; 3],
49 depths: [f32; 3],
50 color: Rgb565,
51 },
52 GouraudTriangle {
53 points: [Point2<i32>; 3],
54 colors: [Rgb565; 3],
55 },
56 GouraudTriangleWithDepth {
57 points: [Point2<i32>; 3],
58 depths: [f32; 3],
59 colors: [Rgb565; 3],
60 },
61 TexturedTriangle {
62 points: [Point2<i32>; 3],
63 uvs: [[f32; 2]; 3],
64 texture_id: u32,
65 },
66 TexturedTriangleWithDepth {
67 points: [Point2<i32>; 3],
68 depths: [f32; 3],
69 uvs: [[f32; 2]; 3],
70 texture_id: u32,
71 },
72}
73
74pub struct K3dengine {
75 pub camera: Camera,
76 width: u16,
77 height: u16,
78}
79
80impl K3dengine {
81 pub fn new(width: u16, height: u16) -> K3dengine {
82 K3dengine {
83 camera: Camera::new(width as f32 / height as f32),
84 width,
85 height,
86 }
87 }
88
89 #[inline]
92 fn should_cull_mesh(&self, mesh: &K3dMesh) -> bool {
93 let mesh_pos = mesh.get_position();
95
96 let to_mesh = mesh_pos - self.camera.position;
98 let distance = to_mesh.norm(); let radius_sq = mesh.compute_bounding_radius_sq();
103 let radius = radius_sq.sqrt(); if distance - radius > self.camera.far {
107 return true;
108 }
109
110 if distance + radius < self.camera.near {
112 return true;
113 }
114
115 false
117 }
118
119 #[inline(always)]
120 fn transform_point(&self, point: &[f32; 3], model_matrix: Matrix4<f32>) -> Option<Point3<i32>> {
121 let point = nalgebra::Vector4::new(point[0], point[1], point[2], 1.0);
122 let point = model_matrix * point;
123
124 if point.w < 0.0 {
125 return None;
126 }
127 if point.z < self.camera.near || point.z > self.camera.far {
128 return None;
129 }
130
131 let point = Point3::from_homogeneous(point)?;
132
133 let x = ((1.0 + point.x) * 0.5 * self.width as f32) as i32;
134 let y = ((1.0 - point.y) * 0.5 * self.height as f32) as i32;
135
136 if x < 0 || x >= self.width as i32 || y < 0 || y >= self.height as i32 {
137 return None;
138 }
139
140 Some(Point3::new(
141 x,
142 y,
143 (point.z * (self.camera.far - self.camera.near) + self.camera.near) as i32,
144 ))
145 }
146
147 #[inline(always)]
148 pub fn transform_points<const N: usize>(
149 &self,
150 indices: &[usize; N],
151 vertices: &[[f32; 3]],
152 model_matrix: Matrix4<f32>,
153 ) -> Option<[Point3<i32>; N]> {
154 let mut ret = [Point3::new(0, 0, 0); N];
155
156 for i in 0..N {
157 ret[i] = self.transform_point(&vertices[indices[i]], model_matrix)?;
158 }
159
160 Some(ret)
161 }
162
163 pub fn render<'a, MS, F>(&self, meshes: MS, mut callback: F)
164 where
165 MS: IntoIterator<Item = &'a K3dMesh<'a>>,
166 F: FnMut(DrawPrimitive),
167 {
168 for mesh in meshes {
169 if mesh.geometry.vertices.is_empty() {
170 continue;
171 }
172
173 if self.should_cull_mesh(mesh) {
177 continue;
178 }
179
180 let mesh_pos = mesh.get_position();
182 let distance = (mesh_pos - self.camera.position).norm();
183 let geometry = mesh.select_lod(distance);
184
185 let transform_matrix = self.camera.vp_matrix * mesh.model_matrix;
186
187 match mesh.render_mode {
188 RenderMode::Points => {
189 let screen_space_points = geometry
190 .vertices
191 .iter()
192 .filter_map(|v| self.transform_point(v, transform_matrix));
193
194 if geometry.colors.len() == geometry.vertices.len() {
195 for (point, color) in screen_space_points.zip(geometry.colors) {
196 callback(DrawPrimitive::ColoredPoint(point.xy(), *color));
197 }
198 } else {
199 for point in screen_space_points {
200 callback(DrawPrimitive::ColoredPoint(point.xy(), mesh.color));
201 }
202 }
203 }
204
205 RenderMode::Lines if !geometry.lines.is_empty() => {
206 for line in geometry.lines {
207 if let Some([p1, p2]) =
208 self.transform_points(line, geometry.vertices, transform_matrix)
209 {
210 callback(DrawPrimitive::Line([p1.xy(), p2.xy()], mesh.color));
211 }
212 }
213 }
214
215 RenderMode::Lines if !geometry.faces.is_empty() => {
216 for face in geometry.faces {
217 if let Some([p1, p2, p3]) =
218 self.transform_points(face, geometry.vertices, transform_matrix)
219 {
220 callback(DrawPrimitive::Line([p1.xy(), p2.xy()], mesh.color));
221 callback(DrawPrimitive::Line([p2.xy(), p3.xy()], mesh.color));
222 callback(DrawPrimitive::Line([p3.xy(), p1.xy()], mesh.color));
223 }
224 }
225 }
226
227 RenderMode::Lines => {}
228
229 RenderMode::SolidLightDir(direction) => {
230 let color_as_float = Vector3::new(
233 mesh.color.r() as f32 / 32.0,
234 mesh.color.g() as f32 / 64.0,
235 mesh.color.b() as f32 / 32.0,
236 );
237
238 let ambient_color = color_as_float * 0.1;
240
241 let adjusted_dir = Vector3::new(direction.x, direction.y, -direction.z);
244
245 for (face, normal) in geometry.faces.iter().zip(geometry.normals.iter()) {
246 let normal = Vector3::new(normal[0], normal[1], normal[2]);
248
249 let transformed_normal = mesh.model_matrix.transform_vector(&normal);
250
251 if self.camera.get_direction().dot(&transformed_normal) < 0.0 {
255 continue;
256 }
257
258 if let Some([p1, p2, p3]) =
259 self.transform_points(face, geometry.vertices, transform_matrix)
260 {
261 let intensity = transformed_normal.dot(&adjusted_dir).max(0.0);
263
264 let final_color = color_as_float * intensity + ambient_color;
266
267 let final_color = Vector3::new(
268 final_color.x.clamp(0.0, 1.0),
269 final_color.y.clamp(0.0, 1.0),
270 final_color.z.clamp(0.0, 1.0),
271 );
272
273 let color = Rgb565::new(
274 (final_color.x * 31.0) as u8,
275 (final_color.y * 63.0) as u8,
276 (final_color.z * 31.0) as u8,
277 );
278 callback(DrawPrimitive::ColoredTriangleWithDepth {
279 points: [p1.xy(), p2.xy(), p3.xy()],
280 depths: [p1.z as f32, p2.z as f32, p3.z as f32],
281 color,
282 });
283 }
284 }
285 }
286
287 RenderMode::GouraudLightDir(direction) => {
288 let color_as_float = Vector3::new(
289 mesh.color.r() as f32 / 32.0,
290 mesh.color.g() as f32 / 64.0,
291 mesh.color.b() as f32 / 32.0,
292 );
293 let ambient_color = color_as_float * 0.1;
294 let adjusted_dir = Vector3::new(direction.x, direction.y, -direction.z);
295
296 for (face, face_normal) in geometry.faces.iter().zip(geometry.normals.iter()) {
297 let fn_vec = Vector3::new(face_normal[0], face_normal[1], face_normal[2]);
298 let transformed_fn = mesh.model_matrix.transform_vector(&fn_vec);
299
300 if self.camera.get_direction().dot(&transformed_fn) < 0.0 {
301 continue;
302 }
303
304 if let Some([p1, p2, p3]) =
305 self.transform_points(face, geometry.vertices, transform_matrix)
306 {
307 let vertex_colors: [Rgb565; 3] = core::array::from_fn(|k| {
309 let vn = if !geometry.vertex_normals.is_empty() {
310 let vn_arr = geometry.vertex_normals[face[k]];
311 let vn_vec = Vector3::new(vn_arr[0], vn_arr[1], vn_arr[2]);
312 mesh.model_matrix.transform_vector(&vn_vec)
313 } else {
314 transformed_fn
315 };
316
317 let intensity = vn.dot(&adjusted_dir).max(0.0);
318 let c = color_as_float * intensity + ambient_color;
319 Rgb565::new(
320 (c.x.clamp(0.0, 1.0) * 31.0) as u8,
321 (c.y.clamp(0.0, 1.0) * 63.0) as u8,
322 (c.z.clamp(0.0, 1.0) * 31.0) as u8,
323 )
324 });
325
326 callback(DrawPrimitive::GouraudTriangleWithDepth {
327 points: [p1.xy(), p2.xy(), p3.xy()],
328 depths: [p1.z as f32, p2.z as f32, p3.z as f32],
329 colors: vertex_colors,
330 });
331 }
332 }
333 }
334
335 RenderMode::BlinnPhong {
336 light_dir,
337 specular_intensity,
338 shininess,
339 } => {
340 let color_as_float = Vector3::new(
342 mesh.color.r() as f32 / 32.0,
343 mesh.color.g() as f32 / 64.0,
344 mesh.color.b() as f32 / 32.0,
345 );
346
347 let ambient_color = color_as_float * 0.1;
349
350 let adjusted_light_dir = Vector3::new(light_dir.x, light_dir.y, -light_dir.z);
353
354 let light_dir_normalized = adjusted_light_dir.normalize();
356
357 for (face, normal) in geometry.faces.iter().zip(geometry.normals.iter()) {
358 let normal = Vector3::new(normal[0], normal[1], normal[2]);
360 let transformed_normal = mesh.model_matrix.transform_vector(&normal);
361 let normalized_normal = transformed_normal.normalize();
362
363 if self.camera.get_direction().dot(&normalized_normal) < 0.0 {
365 continue;
366 }
367
368 if let Some([p1, p2, p3]) =
369 self.transform_points(face, geometry.vertices, transform_matrix)
370 {
371 let v0 = geometry.vertices[face[0]];
373 let v1 = geometry.vertices[face[1]];
374 let v2 = geometry.vertices[face[2]];
375 let face_center = Point3::new(
376 (v0[0] + v1[0] + v2[0]) / 3.0,
377 (v0[1] + v1[1] + v2[1]) / 3.0,
378 (v0[2] + v1[2] + v2[2]) / 3.0,
379 );
380 let face_center_world = mesh.model_matrix.transform_point(&face_center);
381
382 let view_dir = (self.camera.position - face_center_world).normalize();
384
385 let half_vector = (light_dir_normalized + view_dir).normalize();
387
388 let diffuse_intensity =
390 normalized_normal.dot(&light_dir_normalized).max(0.0);
391
392 let specular_term =
394 normalized_normal.dot(&half_vector).max(0.0).powf(shininess);
395
396 let diffuse_color = color_as_float * diffuse_intensity;
398 let specular_color =
399 Vector3::new(1.0, 1.0, 1.0) * specular_term * specular_intensity;
400 let final_color = ambient_color + diffuse_color + specular_color;
401
402 let final_color = Vector3::new(
403 final_color.x.clamp(0.0, 1.0),
404 final_color.y.clamp(0.0, 1.0),
405 final_color.z.clamp(0.0, 1.0),
406 );
407
408 let color = Rgb565::new(
409 (final_color.x * 31.0) as u8,
410 (final_color.y * 63.0) as u8,
411 (final_color.z * 31.0) as u8,
412 );
413 callback(DrawPrimitive::ColoredTriangleWithDepth {
414 points: [p1.xy(), p2.xy(), p3.xy()],
415 depths: [p1.z as f32, p2.z as f32, p3.z as f32],
416 color,
417 });
418 }
419 }
420 }
421
422 RenderMode::Solid => {
423 if geometry.normals.is_empty() {
424 for face in geometry.faces.iter() {
425 if let Some([p1, p2, p3]) =
426 self.transform_points(face, geometry.vertices, transform_matrix)
427 {
428 callback(DrawPrimitive::ColoredTriangleWithDepth {
429 points: [p1.xy(), p2.xy(), p3.xy()],
430 depths: [p1.z as f32, p2.z as f32, p3.z as f32],
431 color: mesh.color,
432 });
433 }
434 }
435 } else {
436 for (face, normal) in geometry.faces.iter().zip(geometry.normals) {
437 let normal = Vector3::new(normal[0], normal[1], normal[2]);
439
440 let transformed_normal = mesh.model_matrix.transform_vector(&normal);
441
442 if self.camera.get_direction().dot(&transformed_normal) < 0.0 {
444 continue;
445 }
446
447 if let Some([p1, p2, p3]) =
448 self.transform_points(face, geometry.vertices, transform_matrix)
449 {
450 callback(DrawPrimitive::ColoredTriangleWithDepth {
451 points: [p1.xy(), p2.xy(), p3.xy()],
452 depths: [p1.z as f32, p2.z as f32, p3.z as f32],
453 color: mesh.color,
454 });
455 }
456 }
457 }
458 }
459 }
460 }
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 extern crate std;
467 use super::*;
468
469 #[test]
470 fn test_engine_creation() {
471 let engine = K3dengine::new(640, 480);
472 assert_eq!(engine.width, 640);
473 assert_eq!(engine.height, 480);
474 assert!((engine.camera.get_aspect_ratio() - 640.0 / 480.0).abs() < 0.001);
475 }
476
477 #[test]
478 fn test_transform_point_basic() {
479 let engine = K3dengine::new(640, 480);
480 let transform_matrix = engine.camera.vp_matrix;
482
483 let point = [0.0, 0.0, -5.0];
486 let result = engine.transform_point(&point, transform_matrix);
487
488 if let Some(transformed) = result {
489 assert!(transformed.x >= 0 && transformed.x < 640);
491 assert!(transformed.y >= 0 && transformed.y < 480);
492 }
493 }
495
496 #[test]
497 fn test_transform_point_clamps_out_of_bounds() {
498 let engine = K3dengine::new(640, 480);
499 let model_matrix = nalgebra::Matrix4::identity();
500
501 let point = [100.0, 100.0, -5.0];
503 let result = engine.transform_point(&point, model_matrix);
504 assert!(result.is_none());
506 }
507
508 #[test]
509 fn test_transform_point_behind_camera() {
510 let engine = K3dengine::new(640, 480);
511 let transform_matrix = engine.camera.vp_matrix;
512
513 let point = [0.0, 0.0, 1.0];
515 let _result = engine.transform_point(&point, transform_matrix);
516 }
520
521 #[test]
522 fn test_transform_point_near_plane_clipping() {
523 let engine = K3dengine::new(640, 480);
524 let model_matrix = nalgebra::Matrix4::identity();
525
526 let point = [0.0, 0.0, -0.01];
528 let result = engine.transform_point(&point, model_matrix);
529 assert!(result.is_none());
530 }
531
532 #[test]
533 fn test_transform_point_far_plane_clipping() {
534 let engine = K3dengine::new(640, 480);
535 let model_matrix = nalgebra::Matrix4::identity();
536
537 let point = [0.0, 0.0, -1000.0];
539 let result = engine.transform_point(&point, model_matrix);
540 assert!(result.is_none());
541 }
542
543 #[test]
544 fn test_transform_points_array() {
545 let engine = K3dengine::new(640, 480);
546 let transform_matrix = engine.camera.vp_matrix;
547
548 let vertices = [[0.0, 0.0, -5.0], [0.1, 0.0, -5.0], [0.0, 0.1, -5.0]];
549 let indices = [0, 1, 2];
550
551 let result = engine.transform_points(&indices, &vertices, transform_matrix);
552
553 if let Some(points) = result {
555 assert_eq!(points.len(), 3);
556 }
557 }
559
560 #[test]
561 fn test_render_empty_faces_mesh() {
562 let engine = K3dengine::new(640, 480);
563 let vertices = [[0.0, 0.0, -5.0]]; let geometry = mesh::Geometry {
565 vertices: &vertices,
566 faces: &[],
567 colors: &[],
568 lines: &[],
569 normals: &[],
570 vertex_normals: &[],
571 uvs: &[],
572 texture_id: None,
573 };
574 let mesh = mesh::K3dMesh::new(geometry);
575
576 let mut callback_count = 0;
577 engine.render(std::iter::once(&mesh), |_| {
578 callback_count += 1;
579 });
580
581 assert!(callback_count > 0);
583 }
584
585 #[test]
586 fn test_render_points_mode() {
587 let engine = K3dengine::new(640, 480);
588
589 let vertices = [[0.0, 0.0, -5.0], [0.5, 0.0, -5.0]];
590
591 let geometry = mesh::Geometry {
592 vertices: &vertices,
593 faces: &[],
594 colors: &[],
595 lines: &[],
596 normals: &[],
597 vertex_normals: &[],
598 uvs: &[],
599 texture_id: None,
600 };
601
602 let mut mesh = mesh::K3dMesh::new(geometry);
603 mesh.set_render_mode(mesh::RenderMode::Points);
604
605 let mut primitives = std::vec::Vec::new();
606 engine.render(std::iter::once(&mesh), |prim| {
607 primitives.push(prim);
608 });
609
610 assert!(primitives.len() > 0);
612 for prim in primitives {
613 assert!(matches!(prim, DrawPrimitive::ColoredPoint(_, _)));
614 }
615 }
616
617 #[test]
618 fn test_render_lines_mode_with_faces() {
619 let engine = K3dengine::new(640, 480);
620
621 let vertices = [[0.0, 0.0, -5.0], [0.5, 0.0, -5.0], [0.0, 0.5, -5.0]];
622
623 let faces = [[0, 1, 2]];
624
625 let geometry = mesh::Geometry {
626 vertices: &vertices,
627 faces: &faces,
628 colors: &[],
629 lines: &[],
630 normals: &[],
631 vertex_normals: &[],
632 uvs: &[],
633 texture_id: None,
634 };
635
636 let mut mesh = mesh::K3dMesh::new(geometry);
637 mesh.set_render_mode(mesh::RenderMode::Lines);
638
639 let mut primitives = std::vec::Vec::new();
640 engine.render(std::iter::once(&mesh), |prim| {
641 primitives.push(prim);
642 });
643
644 assert_eq!(primitives.len(), 3);
646 for prim in primitives {
647 assert!(matches!(prim, DrawPrimitive::Line(_, _)));
648 }
649 }
650
651 #[test]
652 fn test_render_gouraud_light_dir() {
653 let mut engine = K3dengine::new(640, 480);
654 engine.camera.set_position(Point3::new(0.0, 0.0, -10.0));
655 engine.camera.set_target(Point3::new(0.0, 0.0, 0.0));
656
657 let vertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
658 let faces = [[0, 1, 2]];
659 let normals = [[0.0, 0.0, -1.0]]; let vertex_normals = [[0.0, 0.0, -1.0], [0.0, 0.0, -1.0], [0.0, 0.0, -1.0]];
661
662 let geometry = mesh::Geometry {
663 vertices: &vertices,
664 faces: &faces,
665 colors: &[],
666 lines: &[],
667 normals: &normals,
668 vertex_normals: &vertex_normals,
669 uvs: &[],
670 texture_id: None,
671 };
672
673 let mut mesh = mesh::K3dMesh::new(geometry);
674 mesh.set_render_mode(mesh::RenderMode::GouraudLightDir(Vector3::new(
675 0.0, 0.0, 1.0,
676 )));
677
678 let mut primitives = std::vec::Vec::new();
679 engine.render(std::iter::once(&mesh), |prim| {
680 primitives.push(prim);
681 });
682
683 assert!(!primitives.is_empty());
685 for prim in &primitives {
686 assert!(matches!(
687 prim,
688 DrawPrimitive::GouraudTriangleWithDepth { .. }
689 ));
690 }
691 }
692}