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
10fn 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 let (mut attributes, faces) =
95 Self::deduplicate_vertices_based_on_positions(attributes, faces)?;
96
97 let mut faces = faces
99 .into_iter()
100 .filter(|f| f[0] != f[1] && f[1] != f[2] && f[2] != f[0]) .collect::<Vec<_>>();
102
103 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 fn dependency_check(&self) -> Result<(), Err> {
119 for att in &self.attributes {
121 if let Some(d) = att
122 .get_attribute_type()
123 .get_minimum_dependency()
124 .iter() .find(|ty| {
126 att.get_parents()
127 .iter() .map(|parent_id| {
129 self.attributes
130 .iter()
131 .find(|att| &att.get_id() == parent_id)
132 .unwrap()
133 }) .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 fn get_sorted_attributes(mut original: Vec<Attribute>) -> Vec<Attribute> {
145 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); }
152 original
156 }
157
158 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 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 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 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 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 for att in attributes.iter_mut() {
204 att.retain_points_dyn(&keep_indices);
205 }
206
207 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 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 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; if num_vertices == 0 {
245 return Ok((attributes, faces));
246 }
247
248 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 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 point_mapping.push(existing_idx);
262 duplicates.push(point_idx);
263 } else {
264 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 unique_count == num_vertices {
273 return Ok((attributes, faces));
274 }
275
276 let mut remapped_attributes = Vec::with_capacity(attributes.len());
278 for attribute in attributes {
279 let remapped_attribute =
281 Self::remap_attribute(attribute, &point_mapping, unique_count)?;
282 remapped_attributes.push(remapped_attribute);
283 }
284
285 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 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 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 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 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 fn remap_attribute(
324 mut attribute: Attribute,
325 point_mapping: &VecPointIdx<PointIdx>,
326 unique_count: usize,
327 ) -> Result<Attribute, Err> {
328 if unique_count == attribute.len() {
330 return Ok(attribute);
331 }
332
333 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}