threecrate-core 0.10.0

Core data structures and traits for threecrate
Documentation
//! Mesh data structures and functionality

use crate::point::*;
// use nalgebra::{Point3, Vector3};
use serde::{Deserialize, Serialize};

/// A triangle mesh with vertices and faces
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriangleMesh {
    pub vertices: Vec<Point3f>,
    pub faces: Vec<[usize; 3]>,
    pub normals: Option<Vec<Vector3f>>,
    pub colors: Option<Vec<[u8; 3]>>,
}

/// A mesh with colored vertices
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColoredTriangleMesh {
    pub vertices: Vec<ColoredPoint3f>,
    pub faces: Vec<[usize; 3]>,
    pub normals: Option<Vec<Vector3f>>,
}

impl TriangleMesh {
    /// Create a new empty mesh
    pub fn new() -> Self {
        Self {
            vertices: Vec::new(),
            faces: Vec::new(),
            normals: None,
            colors: None,
        }
    }

    /// Create a mesh from vertices and faces
    pub fn from_vertices_and_faces(vertices: Vec<Point3f>, faces: Vec<[usize; 3]>) -> Self {
        Self {
            vertices,
            faces,
            normals: None,
            colors: None,
        }
    }

    /// Get the number of vertices
    pub fn vertex_count(&self) -> usize {
        self.vertices.len()
    }

    /// Get the number of faces
    pub fn face_count(&self) -> usize {
        self.faces.len()
    }

    /// Check if the mesh is empty
    pub fn is_empty(&self) -> bool {
        self.vertices.is_empty() || self.faces.is_empty()
    }

    /// Check that every face index points at an existing vertex. Meshes from
    /// files or user arrays can hold any number; algorithms index without checks.
    pub fn validate_faces(&self) -> crate::Result<()> {
        let n = self.vertices.len();
        match self.faces.iter().position(|f| f.iter().any(|&i| i >= n)) {
            None => Ok(()),
            Some(face) => Err(crate::Error::InvalidData(format!(
                "face {face} uses vertex index {:?}, but the mesh has only {n} vertices",
                self.faces[face]
            ))),
        }
    }

    /// Add a vertex to the mesh
    pub fn add_vertex(&mut self, vertex: Point3f) -> usize {
        let index = self.vertices.len();
        self.vertices.push(vertex);
        index
    }

    /// Add a face to the mesh
    pub fn add_face(&mut self, face: [usize; 3]) {
        self.faces.push(face);
    }

    /// Calculate face normals
    pub fn calculate_face_normals(&self) -> Vec<Vector3f> {
        self.faces
            .iter()
            .map(|face| {
                let v0 = self.vertices[face[0]];
                let v1 = self.vertices[face[1]];
                let v2 = self.vertices[face[2]];

                let edge1 = v1 - v0;
                let edge2 = v2 - v0;

                edge1.cross(&edge2).normalize()
            })
            .collect()
    }

    /// Set vertex normals
    pub fn set_normals(&mut self, normals: Vec<Vector3f>) {
        if normals.len() == self.vertices.len() {
            self.normals = Some(normals);
        }
    }

    /// Set vertex colors
    pub fn set_colors(&mut self, colors: Vec<[u8; 3]>) {
        if colors.len() == self.vertices.len() {
            self.colors = Some(colors);
        }
    }

    /// Clear the mesh
    pub fn clear(&mut self) {
        self.vertices.clear();
        self.faces.clear();
        self.normals = None;
        self.colors = None;
    }
}

impl Default for TriangleMesh {
    fn default() -> Self {
        Self::new()
    }
}

impl ColoredTriangleMesh {
    /// Create a new empty colored mesh
    pub fn new() -> Self {
        Self {
            vertices: Vec::new(),
            faces: Vec::new(),
            normals: None,
        }
    }

    /// Get the number of vertices
    pub fn vertex_count(&self) -> usize {
        self.vertices.len()
    }

    /// Get the number of faces
    pub fn face_count(&self) -> usize {
        self.faces.len()
    }

    /// Check if the mesh is empty
    pub fn is_empty(&self) -> bool {
        self.vertices.is_empty() || self.faces.is_empty()
    }
}

impl Default for ColoredTriangleMesh {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn validate_faces_rejects_out_of_range_indices() {
        let vertices = vec![Point3f::origin(); 3];
        assert!(
            TriangleMesh::from_vertices_and_faces(vertices.clone(), vec![[0, 1, 2]])
                .validate_faces()
                .is_ok()
        );
        let err = TriangleMesh::from_vertices_and_faces(vertices, vec![[0, 1, 2], [0, 1, 99]])
            .validate_faces()
            .unwrap_err();
        assert!(err.to_string().contains("face 1"), "{err}");
    }
}