use crate::color::Color;
use crate::geometry::Point;
#[derive(Clone, Debug)]
pub struct Mesh {
pub vertices: Vec<Point>,
pub colors: Vec<Color>,
pub indices: Vec<u32>,
}
impl Mesh {
pub fn new(vertices: Vec<Point>, colors: Vec<Color>, indices: Vec<u32>) -> Self {
assert_eq!(
vertices.len(),
colors.len(),
"Mesh::new: vertices and colors must have the same length \
({} vs {})",
vertices.len(),
colors.len(),
);
assert!(
indices.len().is_multiple_of(3),
"Mesh::new: indices length must be a multiple of 3, got {}",
indices.len(),
);
let n = vertices.len();
for (i, idx) in indices.iter().enumerate() {
assert!(
(*idx as usize) < n,
"Mesh::new: indices[{i}] = {idx} out of bounds (vertices.len() = {n})",
);
}
Self {
vertices,
colors,
indices,
}
}
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
pub fn is_empty(&self) -> bool {
self.indices.is_empty()
}
pub fn bounding_box(&self) -> crate::geometry::Rect {
if self.vertices.is_empty() {
return crate::geometry::Rect::ZERO;
}
let mut x0 = f64::INFINITY;
let mut x1 = f64::NEG_INFINITY;
let mut y0 = f64::INFINITY;
let mut y1 = f64::NEG_INFINITY;
for p in &self.vertices {
if p.x < x0 {
x0 = p.x;
}
if p.x > x1 {
x1 = p.x;
}
if p.y < y0 {
y0 = p.y;
}
if p.y > y1 {
y1 = p.y;
}
}
crate::geometry::Rect::new(x0, y0, x1, y1)
}
pub fn iter_triangles(&self) -> impl Iterator<Item = ([Point; 3], [Color; 3])> + '_ {
self.indices.chunks_exact(3).map(move |tri| {
let i = tri[0] as usize;
let j = tri[1] as usize;
let k = tri[2] as usize;
(
[self.vertices[i], self.vertices[j], self.vertices[k]],
[self.colors[i], self.colors[j], self.colors[k]],
)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pt(x: f64, y: f64) -> Point {
Point::new(x, y)
}
fn red() -> Color {
Color::new([1.0, 0.0, 0.0, 1.0])
}
fn green() -> Color {
Color::new([0.0, 1.0, 0.0, 1.0])
}
fn blue() -> Color {
Color::new([0.0, 0.0, 1.0, 1.0])
}
#[test]
fn new_single_triangle() {
let m = Mesh::new(
vec![pt(0.0, 0.0), pt(10.0, 0.0), pt(0.0, 10.0)],
vec![red(), green(), blue()],
vec![0, 1, 2],
);
assert_eq!(m.vertex_count(), 3);
assert_eq!(m.triangle_count(), 1);
assert!(!m.is_empty());
}
#[test]
fn bounding_box_spans_all_vertices() {
let m = Mesh::new(
vec![pt(-1.0, 2.0), pt(5.0, -3.0), pt(2.0, 8.0)],
vec![red(); 3],
vec![0, 1, 2],
);
let b = m.bounding_box();
assert_eq!(b.x0, -1.0);
assert_eq!(b.x1, 5.0);
assert_eq!(b.y0, -3.0);
assert_eq!(b.y1, 8.0);
}
#[test]
fn empty_mesh_bounding_box_is_zero() {
let m = Mesh::new(Vec::new(), Vec::new(), Vec::new());
assert_eq!(m.bounding_box(), crate::geometry::Rect::ZERO);
assert!(m.is_empty());
assert_eq!(m.triangle_count(), 0);
}
#[test]
fn iter_triangles_yields_per_vertex_data() {
let m = Mesh::new(
vec![pt(0.0, 0.0), pt(1.0, 0.0), pt(0.0, 1.0), pt(1.0, 1.0)],
vec![red(), green(), blue(), red()],
vec![0, 1, 2, 1, 3, 2],
);
let tris: Vec<_> = m.iter_triangles().collect();
assert_eq!(tris.len(), 2);
assert_eq!(tris[0].0[0], pt(0.0, 0.0));
assert_eq!(tris[0].1[1], green());
assert_eq!(tris[1].0[2], pt(0.0, 1.0));
}
#[test]
#[should_panic(expected = "must have the same length")]
fn mismatched_lengths_panic() {
let _ = Mesh::new(vec![pt(0.0, 0.0)], vec![red(), green()], vec![]);
}
#[test]
#[should_panic(expected = "must be a multiple of 3")]
fn non_multiple_of_three_indices_panic() {
let _ = Mesh::new(vec![pt(0.0, 0.0); 3], vec![red(); 3], vec![0, 1]);
}
#[test]
#[should_panic(expected = "out of bounds")]
fn out_of_bounds_index_panics() {
let _ = Mesh::new(vec![pt(0.0, 0.0); 3], vec![red(); 3], vec![0, 1, 5]);
}
}