Skip to main content

draco_core/
point_cloud.rs

1use std::collections::HashMap;
2
3use crate::geometry_attribute::{GeometryAttributeType, PointAttribute};
4use crate::geometry_indices::{AttributeValueIndex, PointIndex};
5use crate::metadata::{AttributeMetadata, GeometryMetadata, Metadata};
6use crate::status::{DracoError, Status};
7
8/// Point cloud geometry with typed attributes and optional metadata.
9#[derive(Debug, Default, Clone)]
10pub struct PointCloud {
11    attributes: Vec<PointAttribute>,
12    num_points: usize,
13    metadata: Option<GeometryMetadata>,
14    /// The value storage and explicit maps of attributes a `clear` dropped,
15    /// handed to the next attributes added so a decode into a cloud that has
16    /// already decoded grows into the last decode's allocations rather than
17    /// making new ones. Empty on a cloud that has never been cleared.
18    spare_storage: Vec<(Vec<u8>, Vec<AttributeValueIndex>)>,
19}
20
21impl PointCloud {
22    /// Creates an empty point cloud.
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    /// Drops every attribute, point and the metadata, keeping the allocated
28    /// capacity of the attribute list and the attributes' own storage.
29    ///
30    /// What a decode does to the cloud it is given, so that decoding into one
31    /// that already holds geometry replaces it rather than adding to it. The
32    /// values and the explicit maps of the dropped attributes are kept empty
33    /// and handed to the next attributes added, so the caller decoding many
34    /// files into one cloud reuses the allocations of the last one; the cloud
35    /// therefore holds the memory of the largest geometry it has decoded until
36    /// [`release_spare_storage`](Self::release_spare_storage) or drop.
37    pub fn clear(&mut self) {
38        for mut attribute in self.attributes.drain(..) {
39            let storage = attribute.take_storage();
40            if storage.0.capacity() > 0 || storage.1.capacity() > 0 {
41                self.spare_storage.push(storage);
42            }
43        }
44        self.num_points = 0;
45        self.metadata = None;
46    }
47
48    /// Frees the storage [`clear`](Self::clear) retained from earlier
49    /// attributes.
50    pub fn release_spare_storage(&mut self) {
51        self.spare_storage = Vec::new();
52    }
53
54    /// Hands a spare storage to an attribute that has none of its own.
55    fn adopt_spare_storage(&mut self, attribute: &mut PointAttribute) {
56        if attribute.buffer().has_storage() {
57            return;
58        }
59        if let Some(storage) = self.spare_storage.pop() {
60            attribute.adopt_storage(storage);
61        }
62    }
63
64    /// Sets the number of logical points.
65    pub fn set_num_points(&mut self, num_points: usize) {
66        self.num_points = num_points;
67    }
68
69    /// Adds an attribute and assigns it a unique id matching its attribute id.
70    pub fn add_attribute(&mut self, mut attribute: PointAttribute) -> i32 {
71        if self.num_points == 0 && attribute.size() > 0 {
72            self.num_points = attribute.size();
73        }
74        let id = self.attributes.len() as i32;
75        attribute.set_unique_id(id as u32);
76        self.adopt_spare_storage(&mut attribute);
77        self.attributes.push(attribute);
78        id
79    }
80
81    /// Adds an attribute while preserving its existing unique id.
82    pub fn add_attribute_preserve_unique_id(&mut self, mut attribute: PointAttribute) -> i32 {
83        if self.num_points == 0 && attribute.size() > 0 {
84            self.num_points = attribute.size();
85        }
86        let id = self.attributes.len() as i32;
87        self.adopt_spare_storage(&mut attribute);
88        self.attributes.push(attribute);
89        id
90    }
91
92    /// Places an attribute at `att_id`, growing the attribute list if needed.
93    ///
94    /// Mirrors C++ `PointCloud::SetAttribute`: the attribute's unique id is set
95    /// to `att_id`. Any vacancies created when growing the list are filled with
96    /// empty attributes.
97    pub fn set_attribute(&mut self, att_id: i32, mut attribute: PointAttribute) {
98        debug_assert!(att_id >= 0);
99        let index = att_id as usize;
100        if index >= self.attributes.len() {
101            self.attributes.resize_with(index + 1, PointAttribute::new);
102        }
103        attribute.set_unique_id(att_id as u32);
104        self.attributes[index] = attribute;
105    }
106
107    /// Returns the number of attributes.
108    pub fn num_attributes(&self) -> i32 {
109        self.attributes.len() as i32
110    }
111
112    /// Returns the attribute id for the given Draco unique id, or -1.
113    ///
114    /// Mirrors C++ `PointCloud::GetAttributeIdByUniqueId`.
115    pub fn attribute_id_by_unique_id(&self, unique_id: u32) -> i32 {
116        for (i, att) in self.attributes.iter().enumerate() {
117            if att.unique_id() == unique_id {
118                return i as i32;
119            }
120        }
121        -1
122    }
123
124    /// Returns the attribute with the given Draco unique id.
125    ///
126    /// Mirrors C++ `PointCloud::GetAttributeByUniqueId`.
127    pub fn attribute_by_unique_id(&self, unique_id: u32) -> Option<&PointAttribute> {
128        let id = self.attribute_id_by_unique_id(unique_id);
129        (id >= 0).then(|| &self.attributes[id as usize])
130    }
131
132    /// Returns an attribute by attribute id.
133    pub fn attribute(&self, att_id: i32) -> &PointAttribute {
134        &self.attributes[att_id as usize]
135    }
136
137    /// Fallibly returns an attribute by attribute id.
138    pub fn try_attribute(&self, att_id: i32) -> Result<&PointAttribute, DracoError> {
139        let Some(attribute) = (att_id >= 0)
140            .then_some(att_id as usize)
141            .and_then(|index| self.attributes.get(index))
142        else {
143            return Err(DracoError::general(
144                "Point cloud attribute id out of range".to_string(),
145            ));
146        };
147        Ok(attribute)
148    }
149
150    /// Returns a mutable attribute by attribute id.
151    pub fn attribute_mut(&mut self, att_id: i32) -> &mut PointAttribute {
152        &mut self.attributes[att_id as usize]
153    }
154
155    /// Fallibly returns a mutable attribute by attribute id.
156    pub fn try_attribute_mut(&mut self, att_id: i32) -> Result<&mut PointAttribute, DracoError> {
157        let Some(attribute) = (att_id >= 0)
158            .then_some(att_id as usize)
159            .and_then(|index| self.attributes.get_mut(index))
160        else {
161            return Err(DracoError::general(
162                "Point cloud attribute id out of range".to_string(),
163            ));
164        };
165        Ok(attribute)
166    }
167
168    /// Returns the first attribute id with the requested semantic type, or -1.
169    pub fn named_attribute_id(&self, att_type: GeometryAttributeType) -> i32 {
170        for (i, att) in self.attributes.iter().enumerate() {
171            if att.attribute_type() == att_type {
172                return i as i32;
173            }
174        }
175        -1
176    }
177
178    /// Returns the first attribute with the requested semantic type.
179    pub fn named_attribute(&self, att_type: GeometryAttributeType) -> Option<&PointAttribute> {
180        let id = self.named_attribute_id(att_type);
181        if id >= 0 {
182            Some(&self.attributes[id as usize])
183        } else {
184            None
185        }
186    }
187
188    /// Returns the number of logical points.
189    /// Merges bit-identical values in every attribute.
190    ///
191    /// Port of upstream's `PointCloud::DeduplicateAttributeValues`, which its
192    /// OBJ and PLY readers and its `TriangleSoupMeshBuilder` all run before
193    /// the encoder sees the geometry. Fails on an attribute whose type
194    /// upstream's own switch does not cover, which is what upstream does too.
195    pub fn deduplicate_attribute_values(&mut self) -> Status {
196        if self.num_points() == 0 {
197            return Ok(());
198        }
199        for att_id in 0..self.num_attributes() {
200            self.attribute_mut(att_id).deduplicate_values()?;
201        }
202        Ok(())
203    }
204
205    /// Merges points whose attribute values all coincide, keeping the order in
206    /// which they first appear.
207    ///
208    /// Port of upstream's `PointCloud::DeduplicatePointIds`. Two points are the
209    /// same point when every attribute maps them to the same value, which is
210    /// why [`deduplicate_attribute_values`](Self::deduplicate_attribute_values)
211    /// runs first: without it two vertices carrying equal bytes still hold
212    /// distinct value indices and nothing merges.
213    pub fn deduplicate_point_ids(&mut self) {
214        self.deduplicate_point_ids_returning_map();
215    }
216
217    /// [`deduplicate_point_ids`](Self::deduplicate_point_ids), handing back the
218    /// old-point-to-new-point map when anything merged.
219    ///
220    /// A mesh needs the map: its faces name points, and upstream's `Mesh`
221    /// override remaps them right after the point cloud's own part runs.
222    pub(crate) fn deduplicate_point_ids_returning_map(&mut self) -> Option<Vec<u32>> {
223        let num_points = self.num_points();
224        if num_points == 0 || self.num_attributes() == 0 {
225            return None;
226        }
227
228        let key_of = |pc: &Self, point: usize| -> Vec<u32> {
229            (0..pc.num_attributes())
230                .map(|att_id| {
231                    pc.attribute(att_id)
232                        .mapped_index(PointIndex(point as u32))
233                        .0
234                })
235                .collect()
236        };
237
238        let mut first_seen: HashMap<Vec<u32>, u32> = HashMap::with_capacity(num_points);
239        let mut index_map: Vec<u32> = Vec::with_capacity(num_points);
240        let mut unique_points: Vec<u32> = Vec::new();
241        let mut num_unique = 0u32;
242        for point in 0..num_points {
243            match first_seen.entry(key_of(self, point)) {
244                std::collections::hash_map::Entry::Occupied(entry) => {
245                    index_map.push(*entry.get());
246                }
247                std::collections::hash_map::Entry::Vacant(entry) => {
248                    entry.insert(num_unique);
249                    index_map.push(num_unique);
250                    unique_points.push(point as u32);
251                    num_unique += 1;
252                }
253            }
254        }
255        if num_unique as usize == num_points {
256            return None;
257        }
258
259        // Each attribute's new map is built whole and installed whole. Not
260        // through `set_point_map_entry`, which validates each entry against
261        // the value count and panics on one it does not like: an attribute
262        // with no values maps every point to the invalid index, that index is
263        // what a survivor carries, and reinstalling it is not an error -- it
264        // is the same map, shorter. The encoder refuses such an attribute
265        // later, where the refusal can be reported.
266        for att_id in 0..self.num_attributes() {
267            let values: Vec<AttributeValueIndex> = unique_points
268                .iter()
269                .map(|old| self.attribute(att_id).mapped_index(PointIndex(*old)))
270                .collect();
271            self.attribute_mut(att_id)
272                .set_explicit_mapping_from(&values);
273        }
274        self.set_num_points(num_unique as usize);
275        Some(index_map)
276    }
277
278    pub fn num_points(&self) -> usize {
279        self.num_points
280    }
281
282    /// Returns geometry metadata, if present.
283    pub fn metadata(&self) -> Option<&GeometryMetadata> {
284        self.metadata.as_ref()
285    }
286
287    /// Returns mutable geometry metadata, if present.
288    pub fn metadata_mut(&mut self) -> Option<&mut GeometryMetadata> {
289        self.metadata.as_mut()
290    }
291
292    /// Returns geometry metadata, inserting an empty block when absent.
293    pub fn metadata_or_insert(&mut self) -> &mut GeometryMetadata {
294        self.metadata.get_or_insert_with(GeometryMetadata::new)
295    }
296
297    /// Replaces geometry metadata.
298    pub fn set_metadata(&mut self, metadata: Option<GeometryMetadata>) {
299        self.metadata = metadata;
300    }
301
302    /// Finds per-attribute metadata by Draco attribute unique id.
303    pub fn attribute_metadata_by_unique_id(
304        &self,
305        attribute_unique_id: u32,
306    ) -> Option<&AttributeMetadata> {
307        self.metadata
308            .as_ref()
309            .and_then(|metadata| metadata.attribute_metadata_by_unique_id(attribute_unique_id))
310    }
311
312    /// Finds per-attribute metadata by a string metadata entry.
313    pub fn attribute_metadata_by_string_entry(
314        &self,
315        entry_name: &str,
316        entry_value: &str,
317    ) -> Option<&AttributeMetadata> {
318        self.metadata.as_ref().and_then(|metadata| {
319            metadata.attribute_metadata_by_string_entry(entry_name, entry_value)
320        })
321    }
322
323    /// Sets metadata for an attribute id.
324    pub fn set_attribute_metadata(
325        &mut self,
326        att_id: i32,
327        metadata: Metadata,
328    ) -> Result<(), DracoError> {
329        let unique_id = self.try_attribute(att_id)?.unique_id();
330        self.metadata_or_insert()
331            .set_attribute_metadata(unique_id, metadata);
332        Ok(())
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    use crate::draco_types::DataType;
340    use crate::geometry_indices::INVALID_ATTRIBUTE_VALUE_INDEX;
341
342    fn attribute_with_values(num_values: usize, fill: u8) -> PointAttribute {
343        let mut attribute = PointAttribute::new();
344        attribute.init(
345            GeometryAttributeType::Position,
346            3,
347            DataType::Float32,
348            false,
349            num_values,
350        );
351        attribute.buffer_mut().data_mut().fill(fill);
352        attribute.set_explicit_mapping(num_values);
353        attribute
354    }
355
356    /// A cloud that has been cleared hands the dropped attributes' storage to
357    /// the next attributes added, and what they read from it is what a fresh
358    /// attribute reads: zeros where nothing was written, the invalid index
359    /// where no mapping was set.
360    #[test]
361    fn clear_keeps_attribute_storage_for_the_next_attributes_and_hands_it_over_empty() {
362        let mut point_cloud = PointCloud::new();
363        point_cloud.add_attribute(attribute_with_values(100, 0xAB));
364        point_cloud.clear();
365        assert_eq!(point_cloud.num_attributes(), 0);
366        assert_eq!(point_cloud.spare_storage.len(), 1);
367
368        let mut next = PointAttribute::new();
369        next.init_deferred(
370            GeometryAttributeType::Position,
371            3,
372            DataType::Float32,
373            false,
374            10,
375        )
376        .unwrap();
377        let id = point_cloud.add_attribute(next);
378        assert!(point_cloud.spare_storage.is_empty());
379        let next = point_cloud.attribute_mut(id);
380        assert!(next.buffer().has_storage(), "the storage was handed over");
381        assert_eq!(next.buffer().data_size(), 0);
382        next.resize_unique_entries(10).unwrap();
383        assert!(next.buffer().data().iter().all(|&b| b == 0));
384        next.set_explicit_mapping(10);
385        assert!((0..10).all(|p| next.mapped_index(PointIndex(p)) == INVALID_ATTRIBUTE_VALUE_INDEX));
386    }
387
388    /// An attribute that already owns storage keeps it; the spare stays for
389    /// one that does not.
390    #[test]
391    fn an_attribute_with_its_own_storage_does_not_take_a_spare() {
392        let mut point_cloud = PointCloud::new();
393        point_cloud.add_attribute(attribute_with_values(100, 0xAB));
394        point_cloud.clear();
395        point_cloud.add_attribute(attribute_with_values(5, 0xCD));
396        assert_eq!(point_cloud.spare_storage.len(), 1);
397        assert!(point_cloud
398            .attribute(0)
399            .buffer()
400            .data()
401            .iter()
402            .all(|&b| b == 0xCD));
403    }
404
405    #[test]
406    fn release_spare_storage_drops_what_clear_kept() {
407        let mut point_cloud = PointCloud::new();
408        point_cloud.add_attribute(attribute_with_values(100, 0xAB));
409        point_cloud.clear();
410        point_cloud.release_spare_storage();
411        assert!(point_cloud.spare_storage.is_empty());
412    }
413
414    #[test]
415    fn try_attribute_rejects_out_of_range_ids() {
416        let mut point_cloud = PointCloud::new();
417
418        assert!(point_cloud.try_attribute(-1).is_err());
419        assert!(point_cloud.try_attribute(0).is_err());
420        assert!(point_cloud.try_attribute_mut(-1).is_err());
421        assert!(point_cloud.try_attribute_mut(0).is_err());
422    }
423
424    /// One position repeated, under identity mapping: the values merge and the
425    /// mapping becomes explicit, because two points now name one value.
426    #[test]
427    fn identical_values_merge_and_the_mapping_turns_explicit() {
428        use crate::draco_types::DataType;
429
430        let positions: [f32; 12] = [
431            0.0, 0.0, 0.0, //
432            1.0, 0.0, 0.0, //
433            1.0, 0.0, 0.0, // the duplicate
434            0.0, 1.0, 0.0,
435        ];
436        let mut attribute = PointAttribute::new();
437        attribute.init(
438            GeometryAttributeType::Position,
439            3,
440            DataType::Float32,
441            false,
442            4,
443        );
444        let bytes: Vec<u8> = positions.iter().flat_map(|v| v.to_le_bytes()).collect();
445        attribute.buffer_mut().data_mut().copy_from_slice(&bytes);
446        attribute.set_identity_mapping();
447
448        let mut point_cloud = PointCloud::new();
449        point_cloud.set_num_points(4);
450        point_cloud.add_attribute(attribute);
451        point_cloud
452            .deduplicate_attribute_values()
453            .expect("supported");
454
455        let attribute = point_cloud.attribute(0);
456        assert_eq!(attribute.size(), 3, "the duplicate value survived");
457        assert!(!attribute.is_mapping_identity());
458        assert_eq!(
459            attribute.mapped_index(PointIndex(1)),
460            AttributeValueIndex(1)
461        );
462        assert_eq!(
463            attribute.mapped_index(PointIndex(2)),
464            AttributeValueIndex(1),
465            "the duplicate does not point at the value that replaced it"
466        );
467        assert_eq!(
468            attribute.mapped_index(PointIndex(3)),
469            AttributeValueIndex(2)
470        );
471    }
472
473    /// Points are the same point when every attribute maps them to the same
474    /// value, and the survivors keep the order they arrived in.
475    #[test]
476    fn points_naming_the_same_values_merge_in_arrival_order() {
477        use crate::draco_types::DataType;
478
479        let mut attribute = PointAttribute::new();
480        attribute.init(
481            GeometryAttributeType::Position,
482            3,
483            DataType::Float32,
484            false,
485            2,
486        );
487        let bytes: Vec<u8> = [0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0]
488            .iter()
489            .flat_map(|v| v.to_le_bytes())
490            .collect();
491        attribute.buffer_mut().data_mut().copy_from_slice(&bytes);
492        // Four points over two values: 0, 1, 1, 0.
493        attribute.set_explicit_mapping_from(&[
494            AttributeValueIndex(0),
495            AttributeValueIndex(1),
496            AttributeValueIndex(1),
497            AttributeValueIndex(0),
498        ]);
499
500        let mut point_cloud = PointCloud::new();
501        point_cloud.set_num_points(4);
502        point_cloud.add_attribute(attribute);
503        point_cloud.deduplicate_point_ids();
504
505        assert_eq!(point_cloud.num_points(), 2);
506        let attribute = point_cloud.attribute(0);
507        assert_eq!(
508            attribute.mapped_index(PointIndex(0)),
509            AttributeValueIndex(0)
510        );
511        assert_eq!(
512            attribute.mapped_index(PointIndex(1)),
513            AttributeValueIndex(1)
514        );
515    }
516
517    /// A four-component `f64` value is 32 bytes, and two values that differ
518    /// only in the last component differ only past byte 16. Both stay; the
519    /// exact repeat merges.
520    #[test]
521    fn wide_values_are_compared_over_their_whole_width() {
522        use crate::draco_types::DataType;
523
524        let mut attribute = PointAttribute::new();
525        attribute.init(
526            GeometryAttributeType::Generic,
527            4,
528            DataType::Float64,
529            false,
530            3,
531        );
532        let bytes: Vec<u8> = [
533            [1.0f64, 2.0, 3.0, 4.0],
534            [1.0, 2.0, 3.0, 5.0],
535            [1.0, 2.0, 3.0, 4.0],
536        ]
537        .iter()
538        .flatten()
539        .flat_map(|v| v.to_le_bytes())
540        .collect();
541        attribute.buffer_mut().data_mut().copy_from_slice(&bytes);
542        attribute.set_identity_mapping();
543
544        let mut point_cloud = PointCloud::new();
545        point_cloud.set_num_points(3);
546        point_cloud.add_attribute(attribute);
547        point_cloud.deduplicate_attribute_values().unwrap();
548
549        let attribute = point_cloud.attribute(0);
550        assert_eq!(attribute.size(), 2);
551        assert_eq!(attribute.buffer().data(), &bytes[..64]);
552        let mapped: Vec<u32> = (0..3)
553            .map(|point| attribute.mapped_index(PointIndex(point)).0)
554            .collect();
555        assert_eq!(mapped, [0, 1, 0]);
556    }
557
558    /// Upstream's switch covers one to four components, and returns an error
559    /// rather than leaving the values alone. So does this.
560    #[test]
561    fn a_component_count_upstream_does_not_deduplicate_is_refused() {
562        use crate::draco_types::DataType;
563
564        let mut attribute = PointAttribute::new();
565        attribute.init(
566            GeometryAttributeType::Generic,
567            5,
568            DataType::Float32,
569            false,
570            2,
571        );
572        attribute.set_identity_mapping();
573
574        let mut point_cloud = PointCloud::new();
575        point_cloud.set_num_points(2);
576        point_cloud.add_attribute(attribute);
577        assert!(point_cloud.deduplicate_attribute_values().is_err());
578    }
579}