dreamwell-engine 1.0.0

Dreamwell pure-logic engine library — transforms, hierarchy, canon pipeline, spatial math, hashing, tile rules, validation, waymark schema, material/lighting descriptors. No SpacetimeDB dependency.
Documentation
//! Imported mesh types — GPU-neutral geometry data.

use serde::{Deserialize, Serialize};

/// A single imported mesh with interleaved vertex data and index buffer.
/// Vertices, normals, and UVs share the same length (one per vertex).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportedMesh {
    /// Human-readable name (from source file).
    pub name: String,
    /// Vertex positions `[x, y, z]`.
    pub positions: Vec<[f32; 3]>,
    /// Per-vertex normals `[nx, ny, nz]`. Same length as `positions`.
    pub normals: Vec<[f32; 3]>,
    /// Per-vertex texture coordinates `[u, v]`. Same length as `positions`.
    pub uvs: Vec<[f32; 2]>,
    /// Triangle indices (3 per triangle). Each index references a vertex.
    pub indices: Vec<u32>,
}

impl ImportedMesh {
    /// Number of vertices.
    pub fn vertex_count(&self) -> usize {
        self.positions.len()
    }

    /// Number of triangles.
    pub fn triangle_count(&self) -> usize {
        self.indices.len() / 3
    }

    /// Validate mesh integrity.
    pub fn validate(&self) -> Result<(), String> {
        let n = self.positions.len();
        if self.normals.len() != n {
            return Err(format!(
                "content_mesh_normals_mismatch:expected {n} normals, got {}",
                self.normals.len()
            ));
        }
        if self.uvs.len() != n {
            return Err(format!(
                "content_mesh_uvs_mismatch:expected {n} uvs, got {}",
                self.uvs.len()
            ));
        }
        if !self.indices.len().is_multiple_of(3) {
            return Err(format!(
                "content_mesh_indices_not_triangles:index count {} not divisible by 3",
                self.indices.len()
            ));
        }
        let max_idx = n as u32;
        for &idx in &self.indices {
            if idx >= max_idx {
                return Err(format!("content_mesh_index_oob:index {idx} >= vertex count {n}"));
            }
        }
        Ok(())
    }
}

/// A set of imported meshes from a single source file.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ImportedMeshSet {
    pub meshes: Vec<ImportedMesh>,
}

impl ImportedMeshSet {
    /// Total vertex count across all meshes.
    pub fn total_vertices(&self) -> usize {
        self.meshes.iter().map(|m| m.vertex_count()).sum()
    }

    /// Total triangle count across all meshes.
    pub fn total_triangles(&self) -> usize {
        self.meshes.iter().map(|m| m.triangle_count()).sum()
    }

    /// Validate all meshes.
    pub fn validate(&self) -> Result<(), String> {
        for (i, mesh) in self.meshes.iter().enumerate() {
            mesh.validate().map_err(|e| format!("mesh[{i}] '{}': {e}", mesh.name))?;
        }
        Ok(())
    }
}

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

    fn triangle_mesh() -> ImportedMesh {
        ImportedMesh {
            name: "Triangle".into(),
            positions: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
            normals: vec![[0.0, 0.0, 1.0]; 3],
            uvs: vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]],
            indices: vec![0, 1, 2],
        }
    }

    #[test]
    fn valid_triangle() {
        let m = triangle_mesh();
        assert!(m.validate().is_ok());
        assert_eq!(m.vertex_count(), 3);
        assert_eq!(m.triangle_count(), 1);
    }

    #[test]
    fn normals_mismatch() {
        let mut m = triangle_mesh();
        m.normals.pop();
        assert!(m.validate().unwrap_err().contains("normals_mismatch"));
    }

    #[test]
    fn uvs_mismatch() {
        let mut m = triangle_mesh();
        m.uvs.pop();
        assert!(m.validate().unwrap_err().contains("uvs_mismatch"));
    }

    #[test]
    fn indices_not_triangles() {
        let mut m = triangle_mesh();
        m.indices.push(0);
        assert!(m.validate().unwrap_err().contains("not_triangles"));
    }

    #[test]
    fn index_out_of_bounds() {
        let mut m = triangle_mesh();
        m.indices[2] = 99;
        assert!(m.validate().unwrap_err().contains("index_oob"));
    }

    #[test]
    fn mesh_set_totals() {
        let set = ImportedMeshSet {
            meshes: vec![triangle_mesh(), triangle_mesh()],
        };
        assert_eq!(set.total_vertices(), 6);
        assert_eq!(set.total_triangles(), 2);
        assert!(set.validate().is_ok());
    }

    #[test]
    fn empty_mesh_set() {
        let set = ImportedMeshSet::default();
        assert_eq!(set.total_vertices(), 0);
        assert!(set.validate().is_ok());
    }
}