use embedded_graphics_core::pixelcolor::{Bgr888, WebColors};
use log::error;
use nalgebra::{Point3, Similarity3, UnitQuaternion, Vector3};
#[derive(Debug, PartialEq)]
pub enum RenderMode {
Points,
Lines,
Solid,
SolidLightDir(Vector3<f32>),
}
#[derive(Debug, Default)]
pub struct Geometry<'a> {
pub vertices: &'a [[f32; 3]],
pub faces: &'a [[usize; 3]],
pub colors: &'a [Bgr888],
pub lines: &'a [[usize; 2]],
pub normals: &'a [[f32; 3]],
}
impl Geometry<'_> {
fn check_validity(&self) -> bool {
if self.vertices.is_empty() {
error!("Vertices are empty");
return false;
}
for face in self.faces {
if face[0] >= self.vertices.len()
|| face[1] >= self.vertices.len()
|| face[2] >= self.vertices.len()
{
error!("Face vertices are out of bounds");
return false;
}
}
for line in self.lines {
if line[0] >= self.vertices.len() || line[1] >= self.vertices.len() {
error!("Line vertices are out of bounds");
return false;
}
}
if !self.colors.is_empty()
&& (((self.faces.is_empty() && self.lines.is_empty())
&& self.colors.len() != self.vertices.len())
|| (!self.faces.is_empty() && self.colors.len() != self.faces.len())
|| (!self.lines.is_empty() && self.colors.len() != self.lines.len()))
{
error!("Colors are not the same length as vertices|faces|lines");
return false;
}
true
}
pub fn lines_from_faces(faces: &[[usize; 3]]) -> Vec<(usize, usize)> {
let mut lines = Vec::new();
for face in faces {
for line in &[(face[0], face[1]), (face[1], face[2]), (face[2], face[0])] {
let (a, b) = if line.0 < line.1 {
(line.0, line.1)
} else {
(line.1, line.0)
};
if !lines.contains(&(a, b)) {
lines.push((a, b));
}
}
}
lines
}
}
pub struct K3dMesh<'a> {
pub similarity: Similarity3<f32>,
pub model_matrix: nalgebra::Matrix4<f32>,
pub color: Bgr888,
pub render_mode: RenderMode,
pub geometry: Geometry<'a>,
}
impl K3dMesh<'_> {
pub fn new(geometry: Geometry) -> K3dMesh {
assert!(geometry.check_validity());
let sim = Similarity3::new(Vector3::new(0.0, 0.0, 0.0), nalgebra::zero(), 1.0);
K3dMesh {
model_matrix: sim.to_homogeneous(),
similarity: sim,
color: Bgr888::CSS_WHITE,
render_mode: RenderMode::Points,
geometry,
}
}
pub fn set_color(&mut self, color: Bgr888) {
self.color = color;
}
pub fn set_render_mode(&mut self, mode: RenderMode) {
self.render_mode = mode;
}
pub fn set_position(&mut self, x: f32, y: f32, z: f32) {
self.similarity.isometry.translation.x = x;
self.similarity.isometry.translation.y = y;
self.similarity.isometry.translation.z = z;
self.update_model_matrix();
}
pub fn get_position(&self) -> Point3<f32> {
self.similarity.isometry.translation.vector.into()
}
pub fn set_attitude(&mut self, roll: f32, pitch: f32, yaw: f32) {
self.similarity.isometry.rotation = UnitQuaternion::from_euler_angles(roll, pitch, yaw);
self.update_model_matrix();
}
pub fn set_target(&mut self, target: Point3<f32>) {
let view = Similarity3::look_at_rh(
&self.similarity.isometry.translation.vector.into(),
&target,
&Vector3::y(),
1.0,
);
self.similarity = view;
self.update_model_matrix();
}
pub fn set_scale(&mut self, s: f32) {
if s == 0.0 {
return;
}
self.similarity.set_scaling(s);
self.update_model_matrix();
}
fn update_model_matrix(&mut self) {
self.model_matrix = self.similarity.to_homogeneous();
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use nalgebra::Vector3;
#[ignore = "Vertx color interpolation not supported yet"]
#[test]
fn test_geometry_check_validity_valid_data_0() {
let vertices = &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = &[[0, 1, 2]];
let colors = &[Bgr888::CSS_RED, Bgr888::CSS_GREEN, Bgr888::CSS_BLUE];
let lines = &[[0, 1]];
let normals = &[[0.0, 0.0, 1.0]];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
assert!(geometry.check_validity());
}
#[test]
fn test_geometry_check_validity_valid_data_1() {
let vertices = &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = &[[0, 1, 2]];
let colors = &[Bgr888::CSS_RED];
let colors_bad = &[Bgr888::CSS_RED, Bgr888::CSS_GREEN, Bgr888::CSS_BLUE];
let lines = &[[0, 1]];
let normals = &[[0.0, 0.0, 1.0]];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
assert!(geometry.check_validity());
let geometry_bad = Geometry {
vertices,
faces,
colors: colors_bad,
lines,
normals,
};
assert!(!geometry_bad.check_validity());
}
#[test]
fn test_geometry_check_validity_empty_vertices() {
let vertices: &[[f32; 3]] = &[];
let faces: &[[usize; 3]] = &[];
let colors: &[Bgr888] = &[];
let lines: &[[usize; 2]] = &[];
let normals: &[[f32; 3]] = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
assert!(!geometry.check_validity());
}
#[test]
fn test_geometry_check_validity_invalid_face_indices() {
let vertices = &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]; let faces = &[[0, 1, 2]]; let colors = &[];
let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
assert!(!geometry.check_validity());
}
#[test]
fn test_geometry_check_validity_invalid_line_indices() {
let vertices = &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]; let faces = &[];
let colors = &[];
let lines = &[[0, 2]]; let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
assert!(!geometry.check_validity());
}
#[test]
fn test_geometry_check_validity_mismatched_colors() {
let vertices = &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = &[];
let colors = &[Bgr888::CSS_RED, Bgr888::CSS_GREEN]; let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
assert!(!geometry.check_validity());
}
#[test]
fn test_lines_from_faces() {
let faces = &[
[0, 1, 2], [2, 1, 3], ];
let lines = Geometry::lines_from_faces(faces);
assert_eq!(lines.len(), 5);
assert!(lines.contains(&(0, 1)));
assert!(lines.contains(&(0, 2)));
assert!(lines.contains(&(1, 2)));
assert!(lines.contains(&(1, 3)));
assert!(lines.contains(&(2, 3)));
}
#[test]
fn test_mesh_creation() {
let vertices = &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = &[[0, 1, 2]];
let colors = &[];
let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
let mesh = K3dMesh::new(geometry);
assert_eq!(mesh.render_mode, RenderMode::Points);
assert_eq!(mesh.color, Bgr888::CSS_WHITE);
let position = mesh.get_position();
assert_relative_eq!(position.x, 0.0);
assert_relative_eq!(position.y, 0.0);
assert_relative_eq!(position.z, 0.0);
}
#[test]
fn test_set_color() {
let vertices = &[[0.0, 0.0, 0.0]];
let faces = &[];
let colors = &[];
let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
let mut mesh = K3dMesh::new(geometry);
mesh.set_color(Bgr888::CSS_RED);
assert_eq!(mesh.color, Bgr888::CSS_RED);
}
#[test]
fn test_set_render_mode() {
let vertices = &[[0.0, 0.0, 0.0]];
let faces = &[];
let colors = &[];
let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
let mut mesh = K3dMesh::new(geometry);
mesh.set_render_mode(RenderMode::Lines);
assert_eq!(mesh.render_mode, RenderMode::Lines);
mesh.set_render_mode(RenderMode::Solid);
assert_eq!(mesh.render_mode, RenderMode::Solid);
let light_dir = Vector3::new(0.0, 1.0, 0.0);
mesh.set_render_mode(RenderMode::SolidLightDir(light_dir));
match mesh.render_mode {
RenderMode::SolidLightDir(dir) => {
assert_relative_eq!(dir.x, light_dir.x);
assert_relative_eq!(dir.y, light_dir.y);
assert_relative_eq!(dir.z, light_dir.z);
}
_ => panic!("Expected SolidLightDir render mode"),
}
}
#[test]
fn test_set_position() {
let vertices = &[[0.0, 0.0, 0.0]];
let faces = &[];
let colors = &[];
let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
let mut mesh = K3dMesh::new(geometry);
mesh.set_position(1.0, 2.0, 3.0);
let position = mesh.get_position();
assert_relative_eq!(position.x, 1.0);
assert_relative_eq!(position.y, 2.0);
assert_relative_eq!(position.z, 3.0);
assert_relative_eq!(mesh.model_matrix[(0, 3)], 1.0);
assert_relative_eq!(mesh.model_matrix[(1, 3)], 2.0);
assert_relative_eq!(mesh.model_matrix[(2, 3)], 3.0);
}
#[test]
fn test_set_attitude() {
let vertices = &[[0.0, 0.0, 0.0]];
let faces = &[];
let colors = &[];
let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
let mut mesh = K3dMesh::new(geometry);
let roll = std::f32::consts::PI / 4.0; let pitch = std::f32::consts::PI / 6.0; let yaw = std::f32::consts::PI / 3.0;
mesh.set_attitude(roll, pitch, yaw);
assert_ne!(mesh.model_matrix, nalgebra::Matrix4::identity());
}
#[test]
fn test_set_scale() {
let vertices = &[[0.0, 0.0, 0.0]];
let faces = &[];
let colors = &[];
let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
let mut mesh = K3dMesh::new(geometry);
mesh.set_scale(2.0);
assert_relative_eq!(mesh.similarity.scaling(), 2.0);
assert_relative_eq!(mesh.model_matrix[(0, 0)], 2.0);
assert_relative_eq!(mesh.model_matrix[(1, 1)], 2.0);
assert_relative_eq!(mesh.model_matrix[(2, 2)], 2.0);
mesh.set_scale(0.0);
assert_relative_eq!(mesh.similarity.scaling(), 2.0); }
#[test]
fn test_set_target() {
let vertices = &[[0.0, 0.0, 0.0]];
let faces = &[];
let colors = &[];
let lines = &[];
let normals = &[];
let geometry = Geometry {
vertices,
faces,
colors,
lines,
normals,
};
let mut mesh = K3dMesh::new(geometry);
mesh.set_position(0.0, 0.0, 5.0);
let target = Point3::new(0.0, 0.0, 0.0);
mesh.set_target(target);
assert_ne!(mesh.model_matrix, nalgebra::Matrix4::identity());
let position = mesh.get_position();
assert_relative_eq!(position.z, -5.0);
}
}