Skip to main content

draco_core/
point_cloud.rs

1use crate::geometry_attribute::{GeometryAttributeType, PointAttribute};
2use crate::metadata::{AttributeMetadata, GeometryMetadata, Metadata};
3use crate::status::DracoError;
4
5/// Point cloud geometry with typed attributes and optional metadata.
6#[derive(Debug, Default, Clone)]
7pub struct PointCloud {
8    attributes: Vec<PointAttribute>,
9    num_points: usize,
10    metadata: Option<GeometryMetadata>,
11}
12
13impl PointCloud {
14    /// Creates an empty point cloud.
15    pub fn new() -> Self {
16        Self::default()
17    }
18
19    /// Sets the number of logical points.
20    pub fn set_num_points(&mut self, num_points: usize) {
21        self.num_points = num_points;
22    }
23
24    /// Adds an attribute and assigns it a unique id matching its attribute id.
25    pub fn add_attribute(&mut self, mut attribute: PointAttribute) -> i32 {
26        if self.num_points == 0 && attribute.size() > 0 {
27            self.num_points = attribute.size();
28        }
29        let id = self.attributes.len() as i32;
30        attribute.set_unique_id(id as u32);
31        self.attributes.push(attribute);
32        id
33    }
34
35    /// Adds an attribute while preserving its existing unique id.
36    pub fn add_attribute_preserve_unique_id(&mut self, attribute: PointAttribute) -> i32 {
37        if self.num_points == 0 && attribute.size() > 0 {
38            self.num_points = attribute.size();
39        }
40        let id = self.attributes.len() as i32;
41        self.attributes.push(attribute);
42        id
43    }
44
45    /// Places an attribute at `att_id`, growing the attribute list if needed.
46    ///
47    /// Mirrors C++ `PointCloud::SetAttribute`: the attribute's unique id is set
48    /// to `att_id`. Any vacancies created when growing the list are filled with
49    /// empty attributes.
50    pub fn set_attribute(&mut self, att_id: i32, mut attribute: PointAttribute) {
51        debug_assert!(att_id >= 0);
52        let index = att_id as usize;
53        if index >= self.attributes.len() {
54            self.attributes.resize_with(index + 1, PointAttribute::new);
55        }
56        attribute.set_unique_id(att_id as u32);
57        self.attributes[index] = attribute;
58    }
59
60    /// Returns the number of attributes.
61    pub fn num_attributes(&self) -> i32 {
62        self.attributes.len() as i32
63    }
64
65    /// Returns the attribute id for the given Draco unique id, or -1.
66    ///
67    /// Mirrors C++ `PointCloud::GetAttributeIdByUniqueId`.
68    pub fn attribute_id_by_unique_id(&self, unique_id: u32) -> i32 {
69        for (i, att) in self.attributes.iter().enumerate() {
70            if att.unique_id() == unique_id {
71                return i as i32;
72            }
73        }
74        -1
75    }
76
77    /// Returns the attribute with the given Draco unique id.
78    ///
79    /// Mirrors C++ `PointCloud::GetAttributeByUniqueId`.
80    pub fn attribute_by_unique_id(&self, unique_id: u32) -> Option<&PointAttribute> {
81        let id = self.attribute_id_by_unique_id(unique_id);
82        (id >= 0).then(|| &self.attributes[id as usize])
83    }
84
85    /// Returns an attribute by attribute id.
86    pub fn attribute(&self, att_id: i32) -> &PointAttribute {
87        &self.attributes[att_id as usize]
88    }
89
90    /// Fallibly returns an attribute by attribute id.
91    pub fn try_attribute(&self, att_id: i32) -> Result<&PointAttribute, DracoError> {
92        let Some(attribute) = (att_id >= 0)
93            .then_some(att_id as usize)
94            .and_then(|index| self.attributes.get(index))
95        else {
96            return Err(DracoError::DracoError(
97                "Point cloud attribute id out of range".to_string(),
98            ));
99        };
100        Ok(attribute)
101    }
102
103    /// Returns a mutable attribute by attribute id.
104    pub fn attribute_mut(&mut self, att_id: i32) -> &mut PointAttribute {
105        &mut self.attributes[att_id as usize]
106    }
107
108    /// Fallibly returns a mutable attribute by attribute id.
109    pub fn try_attribute_mut(&mut self, att_id: i32) -> Result<&mut PointAttribute, DracoError> {
110        let Some(attribute) = (att_id >= 0)
111            .then_some(att_id as usize)
112            .and_then(|index| self.attributes.get_mut(index))
113        else {
114            return Err(DracoError::DracoError(
115                "Point cloud attribute id out of range".to_string(),
116            ));
117        };
118        Ok(attribute)
119    }
120
121    /// Returns the first attribute id with the requested semantic type, or -1.
122    pub fn named_attribute_id(&self, att_type: GeometryAttributeType) -> i32 {
123        for (i, att) in self.attributes.iter().enumerate() {
124            if att.attribute_type() == att_type {
125                return i as i32;
126            }
127        }
128        -1
129    }
130
131    /// Returns the first attribute with the requested semantic type.
132    pub fn named_attribute(&self, att_type: GeometryAttributeType) -> Option<&PointAttribute> {
133        let id = self.named_attribute_id(att_type);
134        if id >= 0 {
135            Some(&self.attributes[id as usize])
136        } else {
137            None
138        }
139    }
140
141    /// Returns the number of logical points.
142    pub fn num_points(&self) -> usize {
143        self.num_points
144    }
145
146    /// Returns geometry metadata, if present.
147    pub fn metadata(&self) -> Option<&GeometryMetadata> {
148        self.metadata.as_ref()
149    }
150
151    /// Returns mutable geometry metadata, if present.
152    pub fn metadata_mut(&mut self) -> Option<&mut GeometryMetadata> {
153        self.metadata.as_mut()
154    }
155
156    /// Returns geometry metadata, inserting an empty block when absent.
157    pub fn metadata_or_insert(&mut self) -> &mut GeometryMetadata {
158        self.metadata.get_or_insert_with(GeometryMetadata::new)
159    }
160
161    /// Replaces geometry metadata.
162    pub fn set_metadata(&mut self, metadata: Option<GeometryMetadata>) {
163        self.metadata = metadata;
164    }
165
166    /// Finds per-attribute metadata by Draco attribute unique id.
167    pub fn attribute_metadata_by_unique_id(
168        &self,
169        attribute_unique_id: u32,
170    ) -> Option<&AttributeMetadata> {
171        self.metadata
172            .as_ref()
173            .and_then(|metadata| metadata.attribute_metadata_by_unique_id(attribute_unique_id))
174    }
175
176    /// Finds per-attribute metadata by a string metadata entry.
177    pub fn attribute_metadata_by_string_entry(
178        &self,
179        entry_name: &str,
180        entry_value: &str,
181    ) -> Option<&AttributeMetadata> {
182        self.metadata.as_ref().and_then(|metadata| {
183            metadata.attribute_metadata_by_string_entry(entry_name, entry_value)
184        })
185    }
186
187    /// Sets metadata for an attribute id.
188    pub fn set_attribute_metadata(
189        &mut self,
190        att_id: i32,
191        metadata: Metadata,
192    ) -> Result<(), DracoError> {
193        let unique_id = self.try_attribute(att_id)?.unique_id();
194        self.metadata_or_insert()
195            .set_attribute_metadata(unique_id, metadata);
196        Ok(())
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn try_attribute_rejects_out_of_range_ids() {
206        let mut point_cloud = PointCloud::new();
207
208        assert!(point_cloud.try_attribute(-1).is_err());
209        assert!(point_cloud.try_attribute(0).is_err());
210        assert!(point_cloud.try_attribute_mut(-1).is_err());
211        assert!(point_cloud.try_attribute_mut(0).is_err());
212    }
213}