draco_core/mesh.rs
1use crate::geometry_indices::{AttributeValueIndex, FaceIndex, PointIndex, VertexIndex};
2use crate::point_cloud::PointCloud;
3use crate::status::{DracoError, Status};
4use std::collections::HashMap;
5use std::ops::{Deref, DerefMut};
6
7/// Triangle face represented by three point indices.
8pub type Face = [PointIndex; 3];
9
10/// Triangle mesh geometry decoded from, or prepared for, a Draco bitstream.
11///
12/// A mesh owns triangle topology and dereferences to its underlying
13/// [`PointCloud`], where attributes and metadata are stored.
14#[derive(Debug, Default, Clone)]
15pub struct Mesh {
16 point_cloud: PointCloud,
17 faces: Vec<Face>,
18}
19
20impl Mesh {
21 /// Creates an empty mesh with no faces, points, attributes, or metadata.
22 pub fn new() -> Self {
23 Self::default()
24 }
25
26 /// Takes the underlying point cloud, dropping the triangle topology.
27 ///
28 /// A mesh already *is* a point cloud with faces on top, and `Deref` lends
29 /// that half out for reading. Encoding it as a point cloud needs it owned:
30 /// a reader that produced a mesh from a file with no faces — a PLY point
31 /// cloud, a file whose payload is per-point attributes — otherwise has no
32 /// way to reach [`PointCloudEncoder`](crate::PointCloudEncoder) without
33 /// rebuilding every attribute.
34 ///
35 /// Faces are discarded rather than triangulated into anything; this is for
36 /// geometry that had none to begin with, and for callers that have decided
37 /// the connectivity is not what they are encoding.
38 pub fn into_point_cloud(self) -> PointCloud {
39 self.point_cloud
40 }
41
42 /// Drops every face and everything the underlying point cloud holds,
43 /// keeping the allocated capacity of both lists.
44 ///
45 /// What a decode does to the mesh it is given, so that decoding into one
46 /// that already holds geometry replaces it rather than adding to it.
47 pub fn clear(&mut self) {
48 self.point_cloud.clear();
49 self.faces.clear();
50 }
51
52 /// Appends one triangle face.
53 pub fn add_face(&mut self, face: Face) {
54 self.faces.push(face);
55 }
56
57 /// Sets a face, growing the face list with zeroed faces when needed.
58 pub fn set_face(&mut self, face_id: FaceIndex, face: Face) {
59 if face_id.0 as usize >= self.faces.len() {
60 self.faces
61 .resize(face_id.0 as usize + 1, [PointIndex(0); 3]);
62 }
63 self.faces[face_id.0 as usize] = face;
64 }
65
66 /// Bulk-set all faces from a flat u32 index array (3 indices per face).
67 /// Assumes `set_num_faces` has already been called with the right count.
68 #[inline]
69 /// Fills every face from a corner table's corner-to-vertex map.
70 ///
71 /// The map lays the three corners of face `f` at `3f..3f + 3`, in the
72 /// order a face stores them, so an edgebreaker decode without attribute
73 /// seams -- where a corner-table vertex index *is* a point index -- is a
74 /// straight copy. Reading it back through `vertex`/`vertex_after`/
75 /// `vertex_before` instead costs three bounds-checked `Option` lookups
76 /// and two modular corner computations per face for indices already
77 /// known to be consecutive: 51 instructions per face against upstream's
78 /// 23, on a table whose bounds the caller's consistency scan has just
79 /// proved.
80 pub fn set_faces_from_corner_vertices(&mut self, corner_to_vertex_map: &[VertexIndex]) {
81 let (corners_per_face, _) = corner_to_vertex_map.as_chunks::<3>();
82 // Matches `set_face`, which grows rather than refusing a face past
83 // the end; the edgebreaker caller has already sized the mesh to the
84 // table, so this is a fallback and not the path taken.
85 if self.faces.len() < corners_per_face.len() {
86 self.faces
87 .resize(corners_per_face.len(), [PointIndex(0); 3]);
88 }
89 for (face, corners) in self.faces.iter_mut().zip(corners_per_face) {
90 *face = [
91 PointIndex(corners[0].0),
92 PointIndex(corners[1].0),
93 PointIndex(corners[2].0),
94 ];
95 }
96 }
97
98 pub fn set_faces_from_flat_indices(&mut self, indices: &[u32]) {
99 debug_assert_eq!(indices.len(), self.faces.len() * 3);
100 for (i, face) in self.faces.iter_mut().enumerate() {
101 let base = i * 3;
102 *face = [
103 PointIndex(indices[base]),
104 PointIndex(indices[base + 1]),
105 PointIndex(indices[base + 2]),
106 ];
107 }
108 }
109
110 /// Bulk-set all faces from tightly packed u8 indices.
111 /// Assumes `set_num_faces` has already been called with the right count.
112 #[inline]
113 pub fn set_faces_from_u8_indices(&mut self, bytes: &[u8]) {
114 debug_assert_eq!(bytes.len(), self.faces.len() * 3);
115 for (face, chunk) in self.faces.iter_mut().zip(bytes.as_chunks::<3>().0) {
116 *face = [
117 PointIndex(chunk[0] as u32),
118 PointIndex(chunk[1] as u32),
119 PointIndex(chunk[2] as u32),
120 ];
121 }
122 }
123
124 /// Bulk-set all faces from tightly packed little-endian u16 indices.
125 /// Assumes `set_num_faces` has already been called with the right count.
126 #[inline]
127 pub fn set_faces_from_le_u16_indices(&mut self, bytes: &[u8]) {
128 debug_assert_eq!(bytes.len(), self.faces.len() * 3 * 2);
129 for (face, chunk) in self.faces.iter_mut().zip(bytes.as_chunks::<6>().0) {
130 *face = [
131 PointIndex(u16::from_le_bytes([chunk[0], chunk[1]]) as u32),
132 PointIndex(u16::from_le_bytes([chunk[2], chunk[3]]) as u32),
133 PointIndex(u16::from_le_bytes([chunk[4], chunk[5]]) as u32),
134 ];
135 }
136 }
137
138 /// Bulk-set all faces from tightly packed little-endian u32 indices.
139 /// Assumes `set_num_faces` has already been called with the right count.
140 #[inline]
141 pub fn set_faces_from_le_u32_indices(&mut self, bytes: &[u8]) {
142 debug_assert_eq!(bytes.len(), self.faces.len() * 3 * 4);
143 for (face, chunk) in self.faces.iter_mut().zip(bytes.as_chunks::<12>().0) {
144 *face = [
145 PointIndex(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])),
146 PointIndex(u32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]])),
147 PointIndex(u32::from_le_bytes([
148 chunk[8], chunk[9], chunk[10], chunk[11],
149 ])),
150 ];
151 }
152 }
153
154 /// Sets one face from raw u32 point ids.
155 #[inline]
156 pub fn set_face_from_indices(&mut self, face_id: usize, indices: [u32; 3]) {
157 self.faces[face_id] = [
158 PointIndex(indices[0]),
159 PointIndex(indices[1]),
160 PointIndex(indices[2]),
161 ];
162 }
163
164 /// Returns the point indices for a face.
165 pub fn face(&self, face_id: FaceIndex) -> Face {
166 self.faces[face_id.0 as usize]
167 }
168
169 /// Every face's point indices, in face order.
170 ///
171 /// For a caller that walks all of them: `as_flattened()` on the result is
172 /// the mesh's corners in the corner table's own order, which lets a walk
173 /// over both zip two slices instead of deriving a corner index from a face
174 /// index and re-proving the bound at each of them.
175 pub fn faces(&self) -> &[Face] {
176 &self.faces
177 }
178
179 /// Returns the number of triangle faces.
180 pub fn num_faces(&self) -> usize {
181 self.faces.len()
182 }
183
184 /// Resizes the face list, filling new faces with point index zero.
185 pub fn set_num_faces(&mut self, num_faces: usize) {
186 self.faces.resize(num_faces, [PointIndex(0); 3]);
187 }
188
189 /// Fallibly resizes the face list.
190 pub fn try_set_num_faces(&mut self, num_faces: usize) -> Status {
191 if num_faces > self.faces.len() {
192 self.faces
193 .try_reserve_exact(num_faces - self.faces.len())
194 .map_err(|_| DracoError::general("Failed to allocate mesh faces".to_string()))?;
195 }
196 self.faces.resize(num_faces, [PointIndex(0); 3]);
197 Ok(())
198 }
199
200 /// Merges points whose attribute values all coincide, and rewrites the
201 /// faces that named them.
202 ///
203 /// Port of upstream's `Mesh::ApplyPointIdDeduplication` path: the point
204 /// cloud merges the points, then the faces follow the same map. Pair it
205 /// with [`deduplicate_attribute_values`](crate::PointCloud::deduplicate_attribute_values),
206 /// which has to run first -- two vertices carrying equal bytes hold
207 /// distinct value indices until it merges them, so nothing here would see
208 /// them as one point.
209 pub fn deduplicate_point_ids(&mut self) {
210 self.deduplicate_point_ids_returning_map();
211 }
212
213 /// [`deduplicate_point_ids`](Self::deduplicate_point_ids), additionally
214 /// handing back the old-point-to-new-point map -- identity when nothing
215 /// merged -- for a caller that has to carry data addressed by the
216 /// original point (an FBX corner's skin weight or morph delta) onto the
217 /// point that now stands in for it.
218 pub fn deduplicate_point_ids_returning_map(&mut self) -> Vec<u32> {
219 let original_num_points = self.num_points() as u32;
220 let Some(index_map) = self.point_cloud.deduplicate_point_ids_returning_map() else {
221 return (0..original_num_points).collect();
222 };
223 for face in &mut self.faces {
224 for corner in face.iter_mut() {
225 // A corner past the point count keeps its value. Such a face
226 // is not this function's to reject -- the encoder refuses it
227 // where the refusal can be reported -- and there is no new id
228 // to map it onto.
229 if let Some(new) = index_map.get(corner.0 as usize) {
230 *corner = PointIndex(*new);
231 }
232 }
233 }
234 index_map
235 }
236
237 /// Drops points no face names, and then the attribute values left with no
238 /// point, keeping everything else in the order it was in.
239 ///
240 /// Nothing downstream keeps such a point: both this encoder and upstream's
241 /// write the geometry the connectivity reaches, so an unreferenced vertex
242 /// never reaches a decoder either way. What it does reach is the
243 /// quantization range, which is computed over the values an attribute
244 /// holds -- so a stray vertex far from the mesh spends bits on empty space
245 /// and every coordinate that survives comes back less precisely. Measured
246 /// on a unit triangle with a fourth vertex at `1000, 1000, 1000`: the
247 /// encoded size does not move and `1.0` returns as `1.007095`.
248 ///
249 /// Upstream keeps them, which is why `COMPATIBILITY.md` carries this. Its
250 /// readers size a position attribute from the vertex list before they know
251 /// which entries the faces use, and it has no step that revisits the
252 /// question -- `RemoveUnusedValues` exists there but is compiled into the
253 /// transcoder alone.
254 pub fn remove_points_unused_by_faces(&mut self) {
255 let num_points = self.num_points();
256 if num_points == 0 {
257 return;
258 }
259 // A face naming a point this mesh does not have describes nothing: a
260 // PLY carries face indices straight from the file, so the count and
261 // the indices need not agree. Such a face is dropped rather than
262 // renumbered -- renumbering it would invent a point for it, which is
263 // what the face-order renumbering this replaced used to do, and what
264 // left writers emitting indices their own readers refuse.
265 self.faces
266 .retain(|face| face.iter().all(|corner| (corner.0 as usize) < num_points));
267
268 let mut used = vec![false; num_points];
269 for face in &self.faces {
270 for corner in face.iter() {
271 used[corner.0 as usize] = true;
272 }
273 }
274 let num_used = used.iter().filter(|u| **u).count();
275 if num_used == num_points {
276 // Still worth the second half: an attribute can carry values no
277 // point names even when every point is named by a face.
278 self.remove_unused_attribute_values();
279 return;
280 }
281
282 let mut old_to_new = vec![u32::MAX; num_points];
283 let mut next = 0u32;
284 for (point, keep) in used.iter().enumerate() {
285 if *keep {
286 old_to_new[point] = next;
287 next += 1;
288 }
289 }
290 for face in &mut self.faces {
291 for corner in face.iter_mut() {
292 // Same as above: a corner naming no point of this mesh is left
293 // alone for the encoder to refuse.
294 if let Some(new) = old_to_new.get(corner.0 as usize) {
295 *corner = PointIndex(*new);
296 }
297 }
298 }
299 for att_id in 0..self.point_cloud.num_attributes() {
300 let kept: Vec<AttributeValueIndex> = (0..num_points)
301 .filter(|point| used[*point])
302 .map(|point| {
303 self.point_cloud
304 .attribute(att_id)
305 .mapped_index(PointIndex(point as u32))
306 })
307 .collect();
308 self.point_cloud
309 .attribute_mut(att_id)
310 .set_explicit_mapping_from(&kept);
311 }
312 self.point_cloud.set_num_points(num_used);
313 self.remove_unused_attribute_values();
314 }
315
316 /// Drops attribute values no point maps to.
317 fn remove_unused_attribute_values(&mut self) {
318 for att_id in 0..self.point_cloud.num_attributes() {
319 self.point_cloud
320 .attribute_mut(att_id)
321 .remove_unused_values();
322 }
323 }
324
325 /// How a reader finishes a mesh it built from scratch, before anything
326 /// encodes it: merges bit-identical attribute values, then merges the
327 /// points those values made identical, then drops what no face names.
328 ///
329 /// The first two steps are upstream's `TriangleSoupMeshBuilder::Finalize`,
330 /// and their order is load-bearing: two vertices carrying equal bytes hold
331 /// distinct value indices until the values merge, so a point merge run
332 /// first would find nothing to do.
333 ///
334 /// Doing this is not tidying. Until the points merge, the triangles around
335 /// two vertices at one position share a vertex rather than an edge, so the
336 /// encoder sees two connected components where upstream sees one and
337 /// writes a larger stream that decodes to more points than it was given.
338 ///
339 /// The third step goes past upstream, which stops after the merge. See
340 /// [`remove_points_unused_by_faces`](Self::remove_points_unused_by_faces)
341 /// for why an unreferenced point still costs precision, and
342 /// `COMPATIBILITY.md` for the departure it records.
343 pub fn finalize(&mut self) -> Status {
344 self.deduplicate_attribute_values()?;
345 self.deduplicate_point_ids();
346 self.remove_points_unused_by_faces();
347 Ok(())
348 }
349
350 /// [`finalize`](Self::finalize), additionally handing back the point-merge
351 /// map -- what a caller that built one point per polygon corner needs, and
352 /// the readers that build a vertex list do not.
353 ///
354 /// An FBX corner carries its own skin weight and morph delta, so such a
355 /// caller has to move that data onto whichever point now stands in for the
356 /// corner. The unused-point drop cannot disturb the map: a mesh built one
357 /// point per corner has, by construction, no point that starts out unused.
358 pub fn finalize_returning_corner_map(&mut self) -> Result<Vec<u32>, DracoError> {
359 self.deduplicate_attribute_values()?;
360 let corner_to_point = self.deduplicate_point_ids_returning_map();
361 let before = self.num_points();
362 self.remove_points_unused_by_faces();
363 debug_assert_eq!(
364 self.num_points(),
365 before,
366 "a mesh built one point per corner should have no point left unused by a face"
367 );
368 Ok(corner_to_point)
369 }
370
371 /// Renumbers points into the order the faces first name them, dropping any
372 /// point no face names at all.
373 ///
374 /// Not a deduplication, despite what this was once called, and not
375 /// upstream's operation: [`deduplicate_point_ids`](Self::deduplicate_point_ids)
376 /// merges points whose values coincide and keeps the order they arrived
377 /// in, while this one merges nothing and reorders everything.
378 ///
379 /// What it reproduces is the *numbering* upstream's OBJ reader ends up
380 /// with, because that reader emits one point per face corner and its
381 /// point order is therefore corner order already. A reader whose points
382 /// arrive as a vertex list -- PLY, glTF -- gets a different numbering from
383 /// this than upstream gets from its own pair, so it is not a substitute
384 /// for them.
385 pub fn renumber_points_in_face_order(&mut self) {
386 if self.faces.is_empty() || self.num_points() == 0 {
387 return;
388 }
389
390 // Build mapping from old point ID to new point ID
391 // Points are assigned new IDs in the order they're first seen in faces
392 let mut old_to_new: HashMap<u32, u32> = HashMap::new();
393 let mut new_id = 0u32;
394
395 // First pass: determine the mapping
396 for face in &self.faces {
397 for &point_idx in face.iter() {
398 if let std::collections::hash_map::Entry::Vacant(e) = old_to_new.entry(point_idx.0)
399 {
400 e.insert(new_id);
401 new_id += 1;
402 }
403 }
404 }
405
406 // If no remapping needed (already in correct order), skip
407 let needs_remap = old_to_new.iter().any(|(&old, &new)| old != new);
408 if !needs_remap {
409 return;
410 }
411
412 // Build reverse mapping for reordering attributes
413 let num_unique = new_id as usize;
414 let mut new_to_old = vec![0u32; num_unique];
415 for (&old, &new) in &old_to_new {
416 new_to_old[new as usize] = old;
417 }
418
419 // Second pass: update face indices
420 for face in &mut self.faces {
421 for point_idx in face.iter_mut() {
422 point_idx.0 = old_to_new[&point_idx.0];
423 }
424 }
425
426 // Third pass: reorder attribute data
427 // For each attribute, create new buffer with data in new order
428 for att_idx in 0..self.num_attributes() {
429 let att = self.attribute(att_idx);
430 let stride = att.byte_stride() as usize;
431 let old_buffer = att.buffer().data().to_vec();
432
433 // Create new buffer with reordered data
434 let mut new_buffer = vec![0u8; num_unique * stride];
435 for new_idx in 0..num_unique {
436 let old_idx = new_to_old[new_idx] as usize;
437 if old_idx * stride + stride <= old_buffer.len() {
438 new_buffer[new_idx * stride..new_idx * stride + stride]
439 .copy_from_slice(&old_buffer[old_idx * stride..old_idx * stride + stride]);
440 }
441 }
442
443 // Update the attribute through `resize_unique_entries` rather than
444 // resizing its buffer directly: the buffer is only half of an
445 // attribute's size, and leaving `size()` at the pre-dedup count
446 // makes the attribute claim entries its buffer no longer holds.
447 // Anything that walks the attribute by `size()` then reads past the
448 // end -- reachable from any mesh with vertices no face references,
449 // which is ordinary in scanned geometry.
450 let att_mut = self.attribute_mut(att_idx);
451 if att_mut.resize_unique_entries(num_unique).is_ok() {
452 att_mut.buffer_mut().write(0, &new_buffer);
453 }
454 }
455
456 // Update point count
457 self.set_num_points(num_unique);
458 }
459}
460
461impl Deref for Mesh {
462 type Target = PointCloud;
463
464 fn deref(&self) -> &Self::Target {
465 &self.point_cloud
466 }
467}
468
469impl DerefMut for Mesh {
470 fn deref_mut(&mut self) -> &mut Self::Target {
471 &mut self.point_cloud
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use crate::draco_types::DataType;
479 use crate::geometry_attribute::{GeometryAttributeType, PointAttribute};
480
481 #[test]
482 fn into_point_cloud_keeps_points_attributes_and_metadata() {
483 let mut mesh = Mesh::new();
484 mesh.set_num_points(3);
485 mesh.set_num_faces(1);
486
487 let mut attribute = PointAttribute::new();
488 attribute.init(
489 GeometryAttributeType::Position,
490 3,
491 DataType::Float32,
492 false,
493 3,
494 );
495 let id = mesh.add_attribute(attribute);
496 let unique_id = mesh.attribute(id).unique_id();
497
498 let mut entries = crate::metadata::Metadata::new();
499 entries.set_string("name", "carried").unwrap();
500 mesh.metadata_or_insert()
501 .set_attribute_metadata(unique_id, entries);
502
503 let cloud = mesh.into_point_cloud();
504 assert_eq!(cloud.num_points(), 3);
505 assert_eq!(cloud.num_attributes(), 1);
506 assert_eq!(
507 cloud
508 .attribute_metadata_by_unique_id(unique_id)
509 .and_then(|metadata| metadata.metadata().get_string("name")),
510 Some("carried"),
511 "the half a point cloud encoder reads must come through intact"
512 );
513 }
514
515 /// A mesh whose vertices are not all referenced by faces -- ordinary in
516 /// scanned geometry, where the raw point set outlives the triangulation.
517 ///
518 /// Deduplication drops the unreferenced ones, and every attribute has to
519 /// come away describing the points that are left. It used to rewrite the
520 /// buffer but leave `size()` at the old count, so the attribute claimed
521 /// entries whose bytes were gone and readers walked off the end.
522 #[test]
523 fn renumbering_shrinks_attribute_size_with_its_buffer() {
524 let mut mesh = Mesh::new();
525 let num_points = 5;
526 mesh.set_num_points(num_points);
527 mesh.set_num_faces(1);
528
529 let mut attribute = PointAttribute::new();
530 attribute.init(
531 GeometryAttributeType::Position,
532 3,
533 DataType::Float32,
534 false,
535 num_points,
536 );
537 for point in 0..num_points {
538 for component in 0..3 {
539 let value = (point * 3 + component) as f32;
540 attribute
541 .buffer_mut()
542 .update(&value.to_le_bytes(), Some((point * 3 + component) * 4));
543 }
544 }
545 mesh.add_attribute(attribute);
546
547 // Only three of the five points are reachable through a face.
548 mesh.set_face(FaceIndex(0), [PointIndex(4), PointIndex(2), PointIndex(0)]);
549
550 mesh.renumber_points_in_face_order();
551
552 assert_eq!(
553 mesh.num_points(),
554 3,
555 "unreferenced points should be dropped"
556 );
557 let attribute = mesh.attribute(0);
558 assert_eq!(
559 attribute.size(),
560 3,
561 "attribute still claims entries it no longer stores"
562 );
563 assert_eq!(
564 attribute.buffer().data().len(),
565 3 * attribute.byte_stride() as usize,
566 "buffer and size disagree"
567 );
568
569 // The surviving values must be the ones the faces pointed at, in the
570 // order the faces first reach them.
571 let read = |entry: usize| -> f32 {
572 let offset = entry * attribute.byte_stride() as usize;
573 f32::from_le_bytes(
574 attribute.buffer().data()[offset..offset + 4]
575 .try_into()
576 .unwrap(),
577 )
578 };
579 assert_eq!(read(0), 12.0, "first face corner was old point 4");
580 assert_eq!(read(1), 6.0, "second face corner was old point 2");
581 assert_eq!(read(2), 0.0, "third face corner was old point 0");
582 }
583}