Skip to main content

draco_oxide_core/mesh/
builder.rs

1use std::collections::HashMap;
2use std::hash::{Hash, Hasher};
3
4use thiserror::Error;
5
6use super::Mesh;
7use crate::attribute::{Attribute, AttributeDomain, AttributeId, AttributeType, ComponentDataType};
8use crate::types::{PointIdx, VecPointIdx, Vector};
9
10/// Rotates a triangle so its smallest `PointIdx` comes first, preserving the
11/// cyclic order: two triangles share a canonical form iff they have the same
12/// points AND the same winding.
13fn canonical_face(f: &[PointIdx; 3]) -> [PointIdx; 3] {
14    if f[0] <= f[1] && f[0] <= f[2] {
15        [f[0], f[1], f[2]]
16    } else if f[1] <= f[0] && f[1] <= f[2] {
17        [f[1], f[2], f[0]]
18    } else {
19        [f[2], f[0], f[1]]
20    }
21}
22
23pub struct MeshBuilder {
24    pub attributes: Vec<Attribute>,
25    faces: Vec<[usize; 3]>,
26    current_id: usize,
27}
28
29impl Default for MeshBuilder {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl MeshBuilder {
36    pub fn new() -> Self {
37        Self {
38            attributes: Vec::new(),
39            current_id: 0,
40            faces: Vec::new(),
41        }
42    }
43
44    pub fn add_attribute<Data, const N: usize>(
45        &mut self,
46        data: Vec<Data>,
47        att_type: AttributeType,
48        domain: AttributeDomain,
49        parents: Vec<AttributeId>,
50    ) -> AttributeId
51    where
52        Data: Vector<N>,
53    {
54        let unique_id = AttributeId::new(self.current_id);
55        self.attributes
56            .push(Attribute::from(unique_id, data, att_type, domain, parents));
57        self.current_id += 1;
58        unique_id
59    }
60
61    pub fn add_empty_attribute(
62        &mut self,
63        att_type: AttributeType,
64        domain: AttributeDomain,
65        component_type: ComponentDataType,
66        num_component: usize,
67    ) -> AttributeId {
68        let unique_id = AttributeId::new(self.current_id);
69        let att = Attribute::new_empty(unique_id, att_type, domain, component_type, num_component);
70        self.attributes.push(att);
71        self.current_id += 1;
72        unique_id
73    }
74
75    pub fn set_connectivity_attribute(&mut self, data: Vec<[usize; 3]>) {
76        self.faces = data;
77    }
78
79    pub fn build(self) -> Result<Mesh, Err> {
80        self.dependency_check()?;
81
82        let Self {
83            attributes, faces, ..
84        } = self;
85
86        let attributes = Self::get_sorted_attributes(attributes);
87
88        let faces = faces
89            .into_iter()
90            .map(|[a, b, c]| [PointIdx::from(a), PointIdx::from(b), PointIdx::from(c)])
91            .collect::<Vec<_>>();
92
93        // Always perform vertex deduplication based on positions
94        let (mut attributes, faces) =
95            Self::deduplicate_vertices_based_on_positions(attributes, faces)?;
96
97        // Remove degenerate faces
98        let mut faces = faces
99            .into_iter()
100            .filter(|f| f[0] != f[1] && f[1] != f[2] && f[2] != f[0]) // filter out degenerate faces
101            .collect::<Vec<_>>();
102
103        // Remove duplicate faces (identical points and winding); an
104        // opposite-wound face is a distinct face and is kept.
105        let mut seen_faces = std::collections::HashSet::new();
106        faces.retain(|f| seen_faces.insert(canonical_face(f)));
107
108        Self::remove_unused_vertices(&mut attributes, &mut faces)?;
109
110        Ok(Mesh {
111            attributes,
112            faces,
113            ..Mesh::new()
114        })
115    }
116
117    /// Checks if attributes have a valid dependency structure.
118    fn dependency_check(&self) -> Result<(), Err> {
119        // Check if all attributes has at least minimal dependencies
120        for att in &self.attributes {
121            if let Some(d) = att
122                .get_attribute_type()
123                .get_minimum_dependency()
124                .iter() // for each minimum dependency, ...
125                .find(|ty| {
126                    att.get_parents()
127                        .iter() // for each parent id, ...
128                        .map(|parent_id| {
129                            self.attributes
130                                .iter()
131                                .find(|att| &att.get_id() == parent_id)
132                                .unwrap()
133                        }) // for each parent attribute, ...
134                        .all(|parent| parent.get_attribute_type() != **ty)
135                })
136            {
137                return Err(Err::MinimumDependencyError(att.get_attribute_type(), *d));
138            }
139        }
140        Ok(())
141    }
142
143    /// Sorts the attributes in a way that the parent attributes are before their children.
144    fn get_sorted_attributes(mut original: Vec<Attribute>) -> Vec<Attribute> {
145        // Find position attribute if it exists
146        if let Some(pos_att_idx) = original
147            .iter()
148            .position(|att| att.get_attribute_type() == AttributeType::Position)
149        {
150            original.swap(0, pos_att_idx); // Ensure Position attribute is first
151        }
152        // If no position attribute exists, we'll just return the attributes as-is
153        // This can happen with compressed meshes that haven't been decoded yet
154
155        original
156    }
157
158    /// Removes unused vertices from the attributes.
159    /// This is done by checking the connectivity (faces) and removing any vertices that are not referenced.
160    fn remove_unused_vertices(
161        attributes: &mut [Attribute],
162        faces: &mut [[PointIdx; 3]],
163    ) -> Result<(), Err> {
164        if faces.is_empty() || attributes.is_empty() {
165            return Ok(());
166        }
167
168        // Find the maximum vertex index used in faces
169        let max_vertex_index = faces
170            .iter()
171            .flat_map(|face| face.iter())
172            .copied()
173            .max()
174            .unwrap_or(PointIdx::from(0));
175
176        // Create a set of used vertices (up to max_vertex_index)
177        let num_relevant = usize::from(max_vertex_index) + 1;
178        let mut used_vertices = vec![false; num_relevant];
179        for face in faces.iter() {
180            for &vertex in face {
181                let v = usize::from(vertex);
182                if v < num_relevant {
183                    used_vertices[v] = true;
184                }
185            }
186        }
187
188        // Build sorted list of point indices to keep (only used vertices within range)
189        let keep_indices: Vec<usize> = used_vertices
190            .iter()
191            .enumerate()
192            .filter_map(|(idx, &used)| if used { Some(idx) } else { None })
193            .collect();
194
195        // Check if any removal is needed
196        let all_used = keep_indices.len() == num_relevant;
197        let no_excess = attributes.iter().all(|att| att.len() <= num_relevant);
198        if all_used && no_excess {
199            return Ok(());
200        }
201
202        // Bulk-retain only the kept points from each attribute
203        for att in attributes.iter_mut() {
204            att.retain_points_dyn(&keep_indices);
205        }
206
207        // Build offset mapping for face index remapping
208        let mut old_to_new = vec![0usize; num_relevant];
209        let mut new_idx = 0;
210        for v in 0..num_relevant {
211            if used_vertices[v] {
212                old_to_new[v] = new_idx;
213                new_idx += 1;
214            }
215        }
216
217        // Remap the faces
218        for face in faces.iter_mut() {
219            for vertex in face.iter_mut() {
220                *vertex = PointIdx::from(old_to_new[usize::from(*vertex)]);
221            }
222        }
223
224        Ok(())
225    }
226
227    /// Deduplicate vertices by combining all attribute values and creating a mapping
228    /// Only processes Position domain attributes - Corner domain attributes are left unchanged
229    fn deduplicate_vertices_based_on_positions(
230        attributes: Vec<Attribute>,
231        faces: Vec<[PointIdx; 3]>,
232    ) -> Result<(Vec<Attribute>, Vec<[PointIdx; 3]>), Err> {
233        if attributes.is_empty() {
234            return Ok((attributes, faces));
235        }
236
237        let num_vertices = faces
238            .iter()
239            .flat_map(|face| face.iter())
240            .map(|&point_idx| usize::from(point_idx))
241            .max()
242            .unwrap_or(0)
243            + 1; // +1 because PointIdx is zero-based
244        if num_vertices == 0 {
245            return Ok((attributes, faces));
246        }
247
248        // Create a hash map to find unique vertices (only considering Position domain attributes)
249        let mut unique_points: HashMap<VertexHash, PointIdx> = HashMap::new();
250        let mut point_mapping: VecPointIdx<PointIdx> = VecPointIdx::with_capacity(num_vertices);
251        let mut duplicates: Vec<PointIdx> = Vec::new();
252        let mut unique_count = 0;
253
254        // Process each vertex using only Position domain attributes for hashing
255        for point_idx in 0..num_vertices {
256            let point_idx = PointIdx::from(point_idx);
257            let vertex_hash = Self::hash_vertex(&attributes, point_idx);
258
259            if let Some(&existing_idx) = unique_points.get(&vertex_hash) {
260                // Vertex already exists, map to existing index
261                point_mapping.push(existing_idx);
262                duplicates.push(point_idx);
263            } else {
264                // New unique vertex
265                unique_points.insert(vertex_hash, PointIdx::from(unique_count));
266                point_mapping.push(PointIdx::from(unique_count));
267                unique_count += 1;
268            }
269        }
270
271        // If no duplicates found, return original data
272        if unique_count == num_vertices {
273            return Ok((attributes, faces));
274        }
275
276        // Create remapped attributes
277        let mut remapped_attributes = Vec::with_capacity(attributes.len());
278        for attribute in attributes {
279            // Remap Position domain attributes
280            let remapped_attribute =
281                Self::remap_attribute(attribute, &point_mapping, unique_count)?;
282            remapped_attributes.push(remapped_attribute);
283        }
284
285        // Remap faces
286        let remapped_faces: Vec<[PointIdx; 3]> = faces
287            .into_iter()
288            .map(|[a, b, c]| [point_mapping[a], point_mapping[b], point_mapping[c]])
289            .collect();
290
291        Ok((remapped_attributes, remapped_faces))
292    }
293
294    /// Hash a vertex by combining all its attribute values
295    /// Only considers the provided attributes (typically Position domain attributes)
296    fn hash_vertex(attributes: &[Attribute], point_idx: PointIdx) -> VertexHash {
297        let mut hasher = std::collections::hash_map::DefaultHasher::new();
298
299        for attribute in attributes {
300            if usize::from(point_idx) < attribute.len() {
301                // Hash the attribute type and metadata
302                attribute.get_attribute_type().hash(&mut hasher);
303                attribute.get_component_type().hash(&mut hasher);
304                attribute.get_num_components().hash(&mut hasher);
305
306                // Hash the raw bytes of the vertex data
307                let component_size = attribute.get_component_type().size();
308                let num_components = attribute.get_num_components();
309                let value_size = component_size * num_components;
310                let value_idx = attribute.get_unique_val_idx(point_idx);
311                // Get raw bytes for this vertex
312                let vertex_bytes = &attribute.get_data_as_bytes()[usize::from(value_idx)
313                    * value_size
314                    ..usize::from(value_idx) * value_size + value_size];
315                vertex_bytes.hash(&mut hasher);
316            }
317        }
318
319        VertexHash(hasher.finish())
320    }
321
322    /// Remap an attribute according to the vertex mapping by creating a new attribute
323    fn remap_attribute(
324        mut attribute: Attribute,
325        point_mapping: &VecPointIdx<PointIdx>,
326        unique_count: usize,
327    ) -> Result<Attribute, Err> {
328        // If no deduplication is needed, return the original attribute
329        if unique_count == attribute.len() {
330            return Ok(attribute);
331        }
332
333        // Build the sorted list of point indices to keep (first occurrence of each unique vertex)
334        let mut points_met = vec![false; unique_count];
335        let keep_indices: Vec<usize> = (0..point_mapping.len())
336            .filter(|&v| {
337                let mapped = usize::from(point_mapping[PointIdx::from(v)]);
338                if points_met[mapped] {
339                    false
340                } else {
341                    points_met[mapped] = true;
342                    true
343                }
344            })
345            .collect();
346
347        attribute.retain_points_dyn(&keep_indices);
348        Ok(attribute)
349    }
350}
351
352#[derive(Debug, Clone, PartialEq, Eq, Hash)]
353struct VertexHash(u64);
354
355#[remain::sorted]
356#[derive(Error, Debug, Clone)]
357pub enum Err {
358    #[error("The attribute {0} has {1} values, but the parent attribute {2} has a size of {3}.")]
359    AttributeSizeError(usize, usize, usize, usize),
360
361    #[error("Failed to deduplicate vertices: {0}")]
362    DeduplicationError(String),
363
364    #[error("Duplicate attribute ID: {0:?}")]
365    DuplicateAttributeId(AttributeId),
366
367    #[error("One of the attributes does not meet the minimum dependency; {:?} must depend on {:?}.", .0, .1)]
368    MinimumDependencyError(AttributeType, AttributeType),
369
370    #[error("The connectivity attribute and the position attribute are not compatible; the connectivity attribute has a maximum index of {0} and the position attribute has a length of {1}.")]
371    PositionAndConnectivityNotCompatible(usize, usize),
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377    use crate::types::NdVector;
378
379    #[test]
380    fn test_with_tetrahedron() {
381        let faces = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]];
382        let pos = vec![
383            NdVector::from([0.0f32, 0.0, 0.0]),
384            NdVector::from([1.0f32, 0.0, 0.0]),
385            NdVector::from([2.0f32, 0.0, 0.0]),
386            NdVector::from([0.0f32, 0.0, 0.0]),
387            NdVector::from([3.0f32, 0.0, 0.0]),
388            NdVector::from([1.0f32, 0.0, 0.0]),
389            NdVector::from([1.0f32, 0.0, 0.0]),
390            NdVector::from([3.0f32, 0.0, 0.0]),
391            NdVector::from([2.0f32, 0.0, 0.0]),
392            NdVector::from([0.0f32, 0.0, 0.0]),
393            NdVector::from([2.0f32, 0.0, 0.0]),
394            NdVector::from([3.0f32, 0.0, 0.0]),
395        ];
396        let mut builder = MeshBuilder::new();
397        builder.set_connectivity_attribute(faces.to_vec());
398        builder.add_attribute(
399            pos,
400            AttributeType::Position,
401            AttributeDomain::Position,
402            vec![],
403        );
404        let mesh = builder.build().expect("Failed to build mesh");
405        assert_eq!(mesh.get_faces().len(), 4, "Mesh should have 4 faces");
406        assert_eq!(
407            mesh.get_attributes().len(),
408            1,
409            "Mesh should have 1 attribute"
410        );
411        assert_eq!(
412            mesh.get_attributes()[0].len(),
413            4,
414            "Position attribute should have 4 vertices as duplicates are merged"
415        );
416    }
417}