mesh_tools/
mesh.rs

1//! # Mesh Creation and Manipulation
2//!
3//! This module provides utilities for working with 3D meshes in the glTF format.
4//! Meshes in glTF are composed of one or more mesh primitives, each representing
5//! a drawable part of the mesh with its own geometry and material.
6//!
7//! The `MeshBuilder` struct provides a builder pattern for creating mesh objects
8//! with various attributes:
9//! - Vertex positions
10//! - Vertex indices (triangulation)
11//! - Normal vectors
12//! - Texture coordinates (UV mapping)
13//! - Material references
14//!
15//! The module also provides helper functions for processing mesh data such as calculating
16//! attribute mappings for glTF primitives.
17//!
18//! ## Example
19//!
20//! ```rust
21//! use mesh_tools::mesh::MeshBuilder;
22//!
23//! // Create a mesh with custom data
24//! let mesh = MeshBuilder::new(Some("CustomMesh".to_string()))
25//!     .with_positions(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0])
26//!     .with_indices(vec![0, 1, 2])
27//!     .with_normals(vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0])
28//!     .build();
29//! ```
30
31use crate::models::{Primitive, Mesh};
32use std::collections::HashMap;
33
34/// Builder for creating and configuring 3D mesh objects
35pub struct MeshBuilder {
36    builder: Option<Box<dyn FnMut(&[f32], &[u16], Option<&[f32]>, Option<Vec<Vec<f32>>>, Option<usize>) -> Primitive>>,
37    positions: Vec<f32>,
38    indices: Vec<u16>,
39    normals: Option<Vec<f32>>,
40    texcoords: Option<Vec<Vec<f32>>>,
41    material: Option<usize>,
42    name: Option<String>,
43}
44
45impl MeshBuilder {
46    /// Create a new mesh builder
47    pub fn new(name: Option<String>) -> Self {
48        Self {
49            builder: None,
50            positions: Vec::new(),
51            indices: Vec::new(),
52            normals: None,
53            texcoords: None,
54            material: None,
55            name,
56        }
57    }
58    
59    /// Set the positions for the mesh
60    pub fn with_positions(mut self, positions: Vec<f32>) -> Self {
61        self.positions = positions;
62        self
63    }
64    
65    /// Set the indices for the mesh
66    pub fn with_indices(mut self, indices: Vec<u16>) -> Self {
67        self.indices = indices;
68        self
69    }
70    
71    /// Set the normals for the mesh
72    pub fn with_normals(mut self, normals: Vec<f32>) -> Self {
73        self.normals = Some(normals);
74        self
75    }
76    
77    /// Set a single set of texture coordinates for the mesh
78    pub fn with_texcoords(mut self, texcoords: Vec<f32>) -> Self {
79        let mut texcoord_sets = Vec::new();
80        texcoord_sets.push(texcoords);
81        self.texcoords = Some(texcoord_sets);
82        self
83    }
84    
85    /// Set multiple sets of texture coordinates for the mesh
86    pub fn with_multiple_texcoords(mut self, texcoord_sets: Vec<Vec<f32>>) -> Self {
87        self.texcoords = Some(texcoord_sets);
88        self
89    }
90    
91    /// Set the material index for the mesh
92    pub fn with_material(mut self, material: usize) -> Self {
93        self.material = Some(material);
94        self
95    }
96    
97    /// Set the primitive builder function
98    pub fn with_primitive_builder(
99        mut self,
100        builder: Box<dyn FnMut(&[f32], &[u16], Option<&[f32]>, Option<Vec<Vec<f32>>>, Option<usize>) -> Primitive>,
101    ) -> Self {
102        self.builder = Some(builder);
103        self
104    }
105    
106    /// Build the mesh
107    pub fn build(self) -> Mesh {
108        let mut mesh = Mesh::default();
109        mesh.name = self.name;
110        
111        let normals_ref = self.normals.as_deref();
112        
113        if let Some(mut builder) = self.builder {
114            let primitive = builder(
115                &self.positions,
116                &self.indices,
117                normals_ref,
118                self.texcoords,
119                self.material,
120            );
121            mesh.primitives.push(primitive);
122        } else {
123            // Default implementation if no builder provided
124            let attributes = HashMap::new();
125            // Would need accessor indices which are typically provided by GltfBuilder
126            
127            let primitive = Primitive {
128                attributes,
129                indices: None, // Would need accessor index
130                material: self.material,
131                mode: None,    // Default to triangles
132            };
133            
134            mesh.primitives.push(primitive);
135        }
136        
137        mesh
138    }
139}
140
141/// Calculate min and max bounds of positions
142pub fn calculate_bounds(positions: &[f32]) -> (Vec<f32>, Vec<f32>) {
143    if positions.is_empty() {
144        return (vec![], vec![]);
145    }
146    
147    // Assuming positions are in format [x1, y1, z1, x2, y2, z2, ...]
148    let components_per_vertex = 3;
149    
150    let mut min = vec![f32::MAX; components_per_vertex];
151    let mut max = vec![f32::MIN; components_per_vertex];
152    
153    for i in (0..positions.len()).step_by(components_per_vertex) {
154        for j in 0..components_per_vertex {
155            if i + j < positions.len() {
156                min[j] = min[j].min(positions[i + j]);
157                max[j] = max[j].max(positions[i + j]);
158            }
159        }
160    }
161    
162    (min, max)
163}
164
165/// Create attribute mapping for mesh primitive
166pub fn create_attribute_mapping(
167    position_accessor: usize,
168    normal_accessor: Option<usize>,
169    texcoord_accessors: Option<Vec<usize>>,
170) -> HashMap<String, usize> {
171    let mut attributes = HashMap::new();
172    
173    // Position attribute is always required
174    attributes.insert("POSITION".to_string(), position_accessor);
175    
176    // Add normal attribute if provided
177    if let Some(normal_index) = normal_accessor {
178        attributes.insert("NORMAL".to_string(), normal_index);
179    }
180    
181    // Add texture coordinate attributes if provided
182    if let Some(texcoord_indices) = texcoord_accessors {
183        for (i, texcoord_index) in texcoord_indices.iter().enumerate() {
184            attributes.insert(format!("TEXCOORD_{}", i), *texcoord_index);
185        }
186    }
187    
188    attributes
189}