Skip to main content

draco_core/
geometry_attribute.rs

1use crate::attribute_transform_data::AttributeTransformData;
2use crate::data_buffer::DataBuffer;
3use crate::draco_types::DataType;
4use crate::geometry_indices::{AttributeValueIndex, PointIndex, INVALID_ATTRIBUTE_VALUE_INDEX};
5use crate::status::DracoError;
6use std::convert::TryFrom;
7
8/// Semantic role of a geometry attribute.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum GeometryAttributeType {
11    /// Invalid or unset attribute type.
12    Invalid = -1,
13    /// Vertex or point positions.
14    Position = 0,
15    /// Vertex or point normals.
16    Normal,
17    /// Vertex or point colors.
18    Color,
19    /// Texture coordinates.
20    TexCoord,
21    /// Application-defined attribute data.
22    Generic,
23}
24
25impl TryFrom<u8> for GeometryAttributeType {
26    type Error = DracoError;
27
28    fn try_from(value: u8) -> Result<Self, Self::Error> {
29        match value {
30            0 => Ok(Self::Position),
31            1 => Ok(Self::Normal),
32            2 => Ok(Self::Color),
33            3 => Ok(Self::TexCoord),
34            4 => Ok(Self::Generic),
35            _ => Err(DracoError::DracoError(format!(
36                "Invalid geometry attribute type: {value}"
37            ))),
38        }
39    }
40}
41
42/// Format descriptor shared by point and mesh attributes.
43#[derive(Debug, Clone)]
44pub struct GeometryAttribute {
45    attribute_type: GeometryAttributeType,
46    data_type: DataType,
47    num_components: u8,
48    normalized: bool,
49    byte_stride: i64,
50    byte_offset: i64,
51    unique_id: u32,
52}
53
54impl Default for GeometryAttribute {
55    fn default() -> Self {
56        Self {
57            attribute_type: GeometryAttributeType::Invalid,
58            data_type: DataType::Invalid,
59            num_components: 0,
60            normalized: false,
61            byte_stride: 0,
62            byte_offset: 0,
63            unique_id: 0,
64        }
65    }
66}
67
68impl GeometryAttribute {
69    // Attribute initialization requires 7 parameters to fully specify metadata:
70    // type, components, data_type, normalized flag, num_values, byte_stride, byte_offset.
71    // This matches the C++ PointAttribute::Init() signature and cannot be simplified
72    // without breaking API compatibility or making attribute setup less explicit.
73    /// Initializes the attribute format descriptor.
74    #[allow(clippy::too_many_arguments)]
75    pub fn init(
76        &mut self,
77        attribute_type: GeometryAttributeType,
78        _buffer: Option<&DataBuffer>,
79        num_components: u8,
80        data_type: DataType,
81        normalized: bool,
82        byte_stride: i64,
83        byte_offset: i64,
84    ) {
85        self.attribute_type = attribute_type;
86        self.num_components = num_components;
87        self.data_type = data_type;
88        self.normalized = normalized;
89        self.byte_stride = byte_stride;
90        self.byte_offset = byte_offset;
91    }
92
93    /// Returns the semantic attribute type.
94    pub fn attribute_type(&self) -> GeometryAttributeType {
95        self.attribute_type
96    }
97
98    /// Returns the scalar data type used by each component.
99    pub fn data_type(&self) -> DataType {
100        self.data_type
101    }
102
103    /// Returns the number of scalar components per attribute value.
104    pub fn num_components(&self) -> u8 {
105        self.num_components
106    }
107
108    /// Returns whether integer data should be interpreted as normalized.
109    pub fn normalized(&self) -> bool {
110        self.normalized
111    }
112
113    /// Returns the byte stride between consecutive values.
114    pub fn byte_stride(&self) -> i64 {
115        self.byte_stride
116    }
117
118    /// Returns the byte offset of the first value.
119    pub fn byte_offset(&self) -> i64 {
120        self.byte_offset
121    }
122
123    /// Returns the stable Draco unique id for this attribute.
124    pub fn unique_id(&self) -> u32 {
125        self.unique_id
126    }
127
128    /// Sets the stable Draco unique id for this attribute.
129    pub fn set_unique_id(&mut self, id: u32) {
130        self.unique_id = id;
131    }
132
133    /// Sets the semantic attribute type.
134    pub fn set_attribute_type(&mut self, attribute_type: GeometryAttributeType) {
135        self.attribute_type = attribute_type;
136    }
137
138    /// Sets the scalar data type.
139    pub fn set_data_type(&mut self, data_type: DataType) {
140        self.data_type = data_type;
141    }
142
143    /// Sets the number of scalar components per value.
144    pub fn set_num_components(&mut self, num_components: u8) {
145        self.num_components = num_components;
146    }
147}
148
149/// Typed attribute values attached to points in a point cloud or mesh.
150///
151/// Attribute data is stored in a contiguous byte buffer. Point ids either map
152/// directly to attribute value ids, or through an explicit point-to-value map
153/// when multiple points share or reorder attribute entries.
154#[derive(Debug, Clone)]
155pub struct PointAttribute {
156    base: GeometryAttribute,
157    buffer: DataBuffer,
158    indices_map: Vec<AttributeValueIndex>,
159    identity_mapping: bool,
160    num_unique_entries: usize,
161    attribute_transform_data: Option<Box<AttributeTransformData>>,
162}
163
164impl Default for PointAttribute {
165    fn default() -> Self {
166        Self {
167            base: GeometryAttribute::default(),
168            buffer: DataBuffer::new(),
169            indices_map: Vec::new(),
170            identity_mapping: true,
171            num_unique_entries: 0,
172            attribute_transform_data: None,
173        }
174    }
175}
176
177impl PointAttribute {
178    /// Creates an empty attribute with an invalid semantic type.
179    pub fn new() -> Self {
180        Self::default()
181    }
182
183    /// Initializes the attribute and allocates storage for its values.
184    pub fn init(
185        &mut self,
186        attribute_type: GeometryAttributeType,
187        num_components: u8,
188        data_type: DataType,
189        normalized: bool,
190        num_attribute_values: usize,
191    ) {
192        let byte_stride = (num_components as usize * data_type.byte_length()) as i64;
193        self.base.init(
194            attribute_type,
195            None,
196            num_components,
197            data_type,
198            normalized,
199            byte_stride,
200            0,
201        );
202        self.buffer
203            .resize(num_attribute_values * byte_stride as usize);
204        self.num_unique_entries = num_attribute_values;
205        self.identity_mapping = true;
206    }
207
208    /// Fallibly initializes the attribute and allocates storage for its values.
209    pub fn try_init(
210        &mut self,
211        attribute_type: GeometryAttributeType,
212        num_components: u8,
213        data_type: DataType,
214        normalized: bool,
215        num_attribute_values: usize,
216    ) -> Result<(), DracoError> {
217        let byte_stride = num_components as usize * data_type.byte_length();
218        let buffer_size = num_attribute_values
219            .checked_mul(byte_stride)
220            .ok_or_else(|| {
221                DracoError::DracoError("Point attribute buffer size overflow".to_string())
222            })?;
223        self.base.init(
224            attribute_type,
225            None,
226            num_components,
227            data_type,
228            normalized,
229            byte_stride as i64,
230            0,
231        );
232        self.buffer.try_resize(buffer_size).map_err(|_| {
233            DracoError::DracoError("Failed to allocate point attribute buffer".to_string())
234        })?;
235        self.num_unique_entries = num_attribute_values;
236        self.identity_mapping = true;
237        Ok(())
238    }
239
240    /// Maps a point id to the corresponding attribute value id.
241    pub fn mapped_index(&self, point_index: PointIndex) -> AttributeValueIndex {
242        if self.identity_mapping {
243            AttributeValueIndex(point_index.0)
244        } else if (point_index.0 as usize) < self.indices_map.len() {
245            self.indices_map[point_index.0 as usize]
246        } else {
247            INVALID_ATTRIBUTE_VALUE_INDEX
248        }
249    }
250
251    /// Returns the number of unique attribute values.
252    pub fn size(&self) -> usize {
253        self.num_unique_entries
254    }
255
256    /// Resizes the unique attribute value storage.
257    pub fn resize_unique_entries(&mut self, num_attribute_values: usize) -> Result<(), DracoError> {
258        let byte_stride = self.byte_stride() as usize;
259        let buffer_size = num_attribute_values
260            .checked_mul(byte_stride)
261            .ok_or_else(|| {
262                DracoError::DracoError("Point attribute buffer size overflow".to_string())
263            })?;
264        self.buffer.try_resize(buffer_size).map_err(|_| {
265            DracoError::DracoError("Failed to allocate point attribute buffer".to_string())
266        })?;
267        self.num_unique_entries = num_attribute_values;
268        if self.identity_mapping {
269            self.indices_map.clear();
270        }
271        Ok(())
272    }
273
274    /// Returns the raw attribute value buffer.
275    pub fn buffer(&self) -> &DataBuffer {
276        &self.buffer
277    }
278
279    /// Returns the mutable raw attribute value buffer.
280    pub fn buffer_mut(&mut self) -> &mut DataBuffer {
281        &mut self.buffer
282    }
283
284    /// Returns the semantic attribute type.
285    pub fn attribute_type(&self) -> GeometryAttributeType {
286        self.base.attribute_type()
287    }
288
289    /// Returns the stable Draco unique id.
290    pub fn unique_id(&self) -> u32 {
291        self.base.unique_id()
292    }
293
294    /// Sets the stable Draco unique id.
295    pub fn set_unique_id(&mut self, id: u32) {
296        self.base.set_unique_id(id);
297    }
298
299    /// Sets the semantic attribute type.
300    pub fn set_attribute_type(&mut self, attribute_type: GeometryAttributeType) {
301        self.base.set_attribute_type(attribute_type);
302    }
303
304    /// Sets the scalar data type.
305    pub fn set_data_type(&mut self, data_type: DataType) {
306        self.base.set_data_type(data_type);
307    }
308
309    /// Sets the number of scalar components per value.
310    pub fn set_num_components(&mut self, num_components: u8) {
311        self.base.set_num_components(num_components);
312    }
313
314    /// Uses point ids directly as attribute value ids.
315    pub fn set_identity_mapping(&mut self) {
316        self.identity_mapping = true;
317        self.indices_map.clear();
318    }
319
320    /// Allocates an explicit point-to-attribute-value map.
321    pub fn set_explicit_mapping(&mut self, num_points: usize) {
322        self.identity_mapping = false;
323        self.indices_map
324            .resize(num_points, INVALID_ATTRIBUTE_VALUE_INDEX);
325    }
326
327    /// Sets one point-to-attribute-value map entry.
328    pub fn set_point_map_entry(
329        &mut self,
330        point_index: PointIndex,
331        entry_index: AttributeValueIndex,
332    ) {
333        self.try_set_point_map_entry(point_index, entry_index)
334            .expect("point map entry must be in range");
335    }
336
337    /// Fallibly sets one point-to-attribute-value map entry.
338    pub fn try_set_point_map_entry(
339        &mut self,
340        point_index: PointIndex,
341        entry_index: AttributeValueIndex,
342    ) -> Result<(), DracoError> {
343        if self.identity_mapping {
344            return Ok(());
345        }
346        let Some(slot) = self.indices_map.get_mut(point_index.0 as usize) else {
347            return Err(DracoError::DracoError(
348                "Point map entry index out of range".to_string(),
349            ));
350        };
351        *slot = entry_index;
352        Ok(())
353    }
354
355    /// Stores transform metadata associated with this attribute.
356    pub fn set_attribute_transform_data(&mut self, data: AttributeTransformData) {
357        self.attribute_transform_data = Some(Box::new(data));
358    }
359
360    /// Returns transform metadata associated with this attribute, if present.
361    pub fn attribute_transform_data(&self) -> Option<&AttributeTransformData> {
362        self.attribute_transform_data.as_deref()
363    }
364
365    /// Returns the scalar data type.
366    pub fn data_type(&self) -> DataType {
367        self.base.data_type()
368    }
369
370    /// Returns whether integer data should be interpreted as normalized.
371    pub fn normalized(&self) -> bool {
372        self.base.normalized()
373    }
374
375    /// Returns the number of scalar components per value.
376    pub fn num_components(&self) -> u8 {
377        self.base.num_components()
378    }
379
380    /// Returns the byte stride between consecutive values.
381    pub fn byte_stride(&self) -> i64 {
382        self.base.byte_stride()
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389
390    #[test]
391    fn try_set_point_map_entry_rejects_out_of_range_point() {
392        let mut attribute = PointAttribute::new();
393        attribute.set_explicit_mapping(1);
394
395        assert!(attribute
396            .try_set_point_map_entry(PointIndex(1), AttributeValueIndex(0))
397            .is_err());
398    }
399}