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/// Widen one stored scalar to `f32`, whatever Draco declared its type to be.
9///
10/// `bytes` must be exactly the declared type's width; callers slice it from the
11/// attribute buffer. A type with no numeric reading -- `Bool` and the 64-bit
12/// integers, which do not survive an `f32` anyway -- reads as zero.
13fn scalar_as_f32(data_type: DataType, bytes: &[u8]) -> f32 {
14    match data_type {
15        DataType::Float32 => f32::from_le_bytes(bytes.try_into().unwrap()),
16        DataType::Float64 => f64::from_le_bytes(bytes.try_into().unwrap()) as f32,
17        DataType::Int8 => bytes[0] as i8 as f32,
18        DataType::Uint8 => bytes[0] as f32,
19        DataType::Int16 => i16::from_le_bytes(bytes.try_into().unwrap()) as f32,
20        DataType::Uint16 => u16::from_le_bytes(bytes.try_into().unwrap()) as f32,
21        DataType::Int32 => i32::from_le_bytes(bytes.try_into().unwrap()) as f32,
22        DataType::Uint32 => u32::from_le_bytes(bytes.try_into().unwrap()) as f32,
23        _ => 0.0,
24    }
25}
26
27/// Semantic role of a geometry attribute.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum GeometryAttributeType {
30    /// Invalid or unset attribute type.
31    Invalid = -1,
32    /// Vertex or point positions.
33    Position = 0,
34    /// Vertex or point normals.
35    Normal,
36    /// Vertex or point colors.
37    Color,
38    /// Texture coordinates.
39    TexCoord,
40    /// Application-defined attribute data.
41    Generic,
42}
43
44impl TryFrom<u8> for GeometryAttributeType {
45    type Error = DracoError;
46
47    fn try_from(value: u8) -> Result<Self, Self::Error> {
48        match value {
49            0 => Ok(Self::Position),
50            1 => Ok(Self::Normal),
51            2 => Ok(Self::Color),
52            3 => Ok(Self::TexCoord),
53            4 => Ok(Self::Generic),
54            _ => Err(DracoError::general(format!(
55                "Invalid geometry attribute type: {value}"
56            ))),
57        }
58    }
59}
60
61/// Format descriptor shared by point and mesh attributes.
62#[derive(Debug, Clone)]
63pub struct GeometryAttribute {
64    attribute_type: GeometryAttributeType,
65    data_type: DataType,
66    num_components: u8,
67    normalized: bool,
68    byte_stride: i64,
69    byte_offset: i64,
70    unique_id: u32,
71}
72
73impl Default for GeometryAttribute {
74    fn default() -> Self {
75        Self {
76            attribute_type: GeometryAttributeType::Invalid,
77            data_type: DataType::Invalid,
78            num_components: 0,
79            normalized: false,
80            byte_stride: 0,
81            byte_offset: 0,
82            unique_id: 0,
83        }
84    }
85}
86
87impl GeometryAttribute {
88    // Attribute initialization requires 7 parameters to fully specify metadata:
89    // type, components, data_type, normalized flag, num_values, byte_stride, byte_offset.
90    // This matches the C++ PointAttribute::Init() signature and cannot be simplified
91    // without breaking API compatibility or making attribute setup less explicit.
92    /// Initializes the attribute format descriptor.
93    #[allow(clippy::too_many_arguments)]
94    pub fn init(
95        &mut self,
96        attribute_type: GeometryAttributeType,
97        _buffer: Option<&DataBuffer>,
98        num_components: u8,
99        data_type: DataType,
100        normalized: bool,
101        byte_stride: i64,
102        byte_offset: i64,
103    ) {
104        self.attribute_type = attribute_type;
105        self.num_components = num_components;
106        self.data_type = data_type;
107        self.normalized = normalized;
108        self.byte_stride = byte_stride;
109        self.byte_offset = byte_offset;
110    }
111
112    /// Returns the semantic attribute type.
113    pub fn attribute_type(&self) -> GeometryAttributeType {
114        self.attribute_type
115    }
116
117    /// Returns the scalar data type used by each component.
118    pub fn data_type(&self) -> DataType {
119        self.data_type
120    }
121
122    /// Returns the number of scalar components per attribute value.
123    pub fn num_components(&self) -> u8 {
124        self.num_components
125    }
126
127    /// Returns whether integer data should be interpreted as normalized.
128    pub fn normalized(&self) -> bool {
129        self.normalized
130    }
131
132    /// Returns the byte stride between consecutive values.
133    pub fn byte_stride(&self) -> i64 {
134        self.byte_stride
135    }
136
137    /// Returns the byte offset of the first value.
138    pub fn byte_offset(&self) -> i64 {
139        self.byte_offset
140    }
141
142    /// Returns the stable Draco unique id for this attribute.
143    pub fn unique_id(&self) -> u32 {
144        self.unique_id
145    }
146
147    /// Sets the stable Draco unique id for this attribute.
148    pub fn set_unique_id(&mut self, id: u32) {
149        self.unique_id = id;
150    }
151
152    /// Sets the semantic attribute type.
153    pub fn set_attribute_type(&mut self, attribute_type: GeometryAttributeType) {
154        self.attribute_type = attribute_type;
155    }
156
157    /// Sets the scalar data type.
158    pub fn set_data_type(&mut self, data_type: DataType) {
159        self.data_type = data_type;
160    }
161
162    /// Sets the number of scalar components per value.
163    pub fn set_num_components(&mut self, num_components: u8) {
164        self.num_components = num_components;
165    }
166}
167
168/// Typed attribute values attached to points in a point cloud or mesh.
169///
170/// Attribute data is stored in a contiguous byte buffer. Point ids either map
171/// directly to attribute value ids, or through an explicit point-to-value map
172/// when multiple points share or reorder attribute entries.
173#[derive(Debug, Clone)]
174pub struct PointAttribute {
175    base: GeometryAttribute,
176    buffer: DataBuffer,
177    indices_map: Vec<AttributeValueIndex>,
178    identity_mapping: bool,
179    num_unique_entries: usize,
180    attribute_transform_data: Option<Box<AttributeTransformData>>,
181}
182
183impl Default for PointAttribute {
184    fn default() -> Self {
185        Self {
186            base: GeometryAttribute::default(),
187            buffer: DataBuffer::new(),
188            indices_map: Vec::new(),
189            identity_mapping: true,
190            num_unique_entries: 0,
191            attribute_transform_data: None,
192        }
193    }
194}
195
196impl PointAttribute {
197    /// Creates an empty attribute with an invalid semantic type.
198    pub fn new() -> Self {
199        Self::default()
200    }
201
202    /// Initializes the attribute and allocates storage for its values.
203    pub fn init(
204        &mut self,
205        attribute_type: GeometryAttributeType,
206        num_components: u8,
207        data_type: DataType,
208        normalized: bool,
209        num_attribute_values: usize,
210    ) {
211        let byte_stride = (num_components as usize * data_type.byte_length()) as i64;
212        self.base.init(
213            attribute_type,
214            None,
215            num_components,
216            data_type,
217            normalized,
218            byte_stride,
219            0,
220        );
221        self.buffer
222            .resize(num_attribute_values * byte_stride as usize);
223        self.num_unique_entries = num_attribute_values;
224        self.identity_mapping = true;
225    }
226
227    /// Fallibly initializes the attribute and allocates storage for its values.
228    pub fn try_init(
229        &mut self,
230        attribute_type: GeometryAttributeType,
231        num_components: u8,
232        data_type: DataType,
233        normalized: bool,
234        num_attribute_values: usize,
235    ) -> Result<(), DracoError> {
236        let byte_stride = num_components as usize * data_type.byte_length();
237        let buffer_size = num_attribute_values
238            .checked_mul(byte_stride)
239            .ok_or_else(|| {
240                DracoError::general("Point attribute buffer size overflow".to_string())
241            })?;
242        self.base.init(
243            attribute_type,
244            None,
245            num_components,
246            data_type,
247            normalized,
248            byte_stride as i64,
249            0,
250        );
251        self.buffer.try_resize(buffer_size).map_err(|_| {
252            DracoError::general("Failed to allocate point attribute buffer".to_string())
253        })?;
254        self.num_unique_entries = num_attribute_values;
255        self.identity_mapping = true;
256        Ok(())
257    }
258
259    /// The same shape, without reserving for the values.
260    ///
261    /// For decode paths where the count comes from the header: it is what the
262    /// stream *claims*, and reserving for a claim is how a nine-byte header
263    /// names gigabytes. The decoders that fill this buffer size it themselves
264    /// as the values arrive -- the integer path resizes before it writes, the
265    /// generic path resizes only once the bytes are in the stream to be read --
266    /// so nothing here needs the room before there is anything to put in it.
267    ///
268    /// `num_unique_entries` still reports the declared count, because that is
269    /// the ceiling the decode works towards; what is deferred is the memory.
270    /// Not for the KD-tree path, which writes at computed offsets and needs the
271    /// buffer sized first; that path bounds its own allocation instead.
272    ///
273    /// Used by decoder paths where the count comes from the bitstream and the
274    /// value payload has not been validated yet.
275    #[cfg(feature = "decoder")]
276    pub(crate) fn init_deferred(
277        &mut self,
278        attribute_type: GeometryAttributeType,
279        num_components: u8,
280        data_type: DataType,
281        normalized: bool,
282        num_attribute_values: usize,
283    ) -> Result<(), DracoError> {
284        let byte_stride = num_components as usize * data_type.byte_length();
285        // Still checked, so an overflowing shape is refused here rather than
286        // wrapping into a small buffer somewhere later.
287        num_attribute_values
288            .checked_mul(byte_stride)
289            .ok_or_else(|| {
290                DracoError::general("Point attribute buffer size overflow".to_string())
291            })?;
292        self.base.init(
293            attribute_type,
294            None,
295            num_components,
296            data_type,
297            normalized,
298            byte_stride as i64,
299            0,
300        );
301        self.num_unique_entries = num_attribute_values;
302        self.identity_mapping = true;
303        Ok(())
304    }
305
306    /// Maps a point id to the corresponding attribute value id.
307    pub fn mapped_index(&self, point_index: PointIndex) -> AttributeValueIndex {
308        if self.identity_mapping {
309            AttributeValueIndex(point_index.0)
310        } else if (point_index.0 as usize) < self.indices_map.len() {
311            self.indices_map[point_index.0 as usize]
312        } else {
313            INVALID_ATTRIBUTE_VALUE_INDEX
314        }
315    }
316
317    /// Returns the number of unique attribute values.
318    pub fn size(&self) -> usize {
319        self.num_unique_entries
320    }
321
322    /// Read `components` scalars per point as `f32`, in point order.
323    ///
324    /// The inverse of [`DataBuffer::update_f32s_le`](crate::data_buffer::DataBuffer::update_f32s_le):
325    /// whatever component type the attribute stores widens to `f32`, and the
326    /// value index mapping is followed, so an attribute with fewer unique
327    /// values than points still lands on the right one.
328    ///
329    /// The output is always `num_points * components` long, zero-filled where
330    /// the attribute has nothing to give. That matters because the result is a
331    /// channel addressed by vertex index: returning a short row for an
332    /// attribute with fewer components than asked for would shift every vertex
333    /// after it onto the wrong values, which is worse than a zero.
334    ///
335    /// The ordinary case -- float32, tightly packed, identity mapping, asking
336    /// for exactly what the attribute has -- is converted in one pass rather
337    /// than one bounds-checked read per point.
338    pub fn read_f32s(&self, num_points: usize, components: usize) -> Vec<f32> {
339        let mut values = vec![0.0f32; num_points * components];
340        if components == 0 {
341            return values;
342        }
343        let stride = self.byte_stride() as usize;
344        let width = self.data_type().byte_length();
345        let data = self.buffer.data();
346
347        if self.identity_mapping
348            && self.data_type() == DataType::Float32
349            && components == self.num_components() as usize
350            && stride == components * 4
351            && data.len() >= num_points * stride
352        {
353            let packed = &data[..num_points * stride];
354            for (out, bytes) in values.iter_mut().zip(packed.as_chunks::<4>().0) {
355                *out = f32::from_le_bytes(*bytes);
356            }
357            return values;
358        }
359
360        let available = (self.num_components() as usize).min(components);
361        for point in 0..num_points {
362            let value_index = self.mapped_index(PointIndex(point as u32)).0 as usize;
363            // Also catches INVALID_ATTRIBUTE_VALUE_INDEX, whose `usize` product
364            // with the stride would overflow on a 32-bit target.
365            if value_index >= self.num_unique_entries {
366                continue;
367            }
368            let base = value_index * stride;
369            for component in 0..available {
370                let offset = base + component * width;
371                if offset + width > data.len() {
372                    continue;
373                }
374                values[point * components + component] =
375                    scalar_as_f32(self.data_type(), &data[offset..offset + width]);
376            }
377        }
378        values
379    }
380
381    /// Resizes the unique attribute value storage.
382    pub fn resize_unique_entries(&mut self, num_attribute_values: usize) -> Result<(), DracoError> {
383        let byte_stride = self.byte_stride() as usize;
384        let buffer_size = num_attribute_values
385            .checked_mul(byte_stride)
386            .ok_or_else(|| {
387                DracoError::general("Point attribute buffer size overflow".to_string())
388            })?;
389        self.buffer.try_resize(buffer_size).map_err(|_| {
390            DracoError::general("Failed to allocate point attribute buffer".to_string())
391        })?;
392        self.num_unique_entries = num_attribute_values;
393        if self.identity_mapping {
394            self.indices_map.clear();
395        }
396        Ok(())
397    }
398
399    /// Merges values that are bit-identical, and remaps the point map onto
400    /// what survives. Returns how many values are left.
401    ///
402    /// Port of upstream's `PointAttribute::DeduplicateValues`, which every one
403    /// of its readers runs before handing a mesh to the encoder. Two vertices
404    /// carrying the same position arrive as two values here and as one there,
405    /// and the difference is not cosmetic: with the values merged, triangles
406    /// that shared only a vertex come to share an edge, so the encoder sees
407    /// one connected component where it otherwise sees two.
408    ///
409    /// Comparison is over the raw bytes, which is upstream's rule and not an
410    /// approximation of it: it bit-copies each value into an integer of the
411    /// same width before hashing, so `-0.0` and `0.0` are distinct values on
412    /// both sides and two `NaN`s merge exactly when their payloads match.
413    ///
414    /// Refuses the types upstream refuses -- everything 64 bits wide -- and
415    /// component counts outside `1..=4`, rather than silently doing something
416    /// upstream does not.
417    pub(crate) fn deduplicate_values(&mut self) -> Result<usize, DracoError> {
418        let stride = match self.data_type() {
419            DataType::Int8 | DataType::Uint8 | DataType::Bool => 1,
420            DataType::Int16 | DataType::Uint16 => 2,
421            DataType::Int32 | DataType::Uint32 | DataType::Float32 => 4,
422            other => {
423                return Err(DracoError::unsupported_feature(format!(
424                    "deduplicating {other:?} attribute values"
425                )))
426            }
427        } * usize::from(self.num_components());
428        if !(1..=4).contains(&self.num_components()) {
429            return Err(DracoError::unsupported_feature(format!(
430                "deduplicating a {}-component attribute",
431                self.num_components()
432            )));
433        }
434
435        let count = self.num_unique_entries;
436        // Widest supported value is four 32-bit components, so the key is
437        // inline and the map allocates nothing per entry.
438        let mut seen: std::collections::HashMap<[u8; 16], u32> =
439            std::collections::HashMap::with_capacity(count);
440        let mut value_map: Vec<u32> = Vec::with_capacity(count);
441        let mut unique = 0usize;
442        let data = self.buffer.data_mut();
443        for i in 0..count {
444            let at = i * stride;
445            let mut key = [0u8; 16];
446            key[..stride].copy_from_slice(&data[at..at + stride]);
447            match seen.entry(key) {
448                std::collections::hash_map::Entry::Occupied(entry) => {
449                    value_map.push(*entry.get());
450                }
451                std::collections::hash_map::Entry::Vacant(entry) => {
452                    entry.insert(unique as u32);
453                    // Survivors are packed forward as they are found, so the
454                    // buffer needs no second pass and no second allocation.
455                    data.copy_within(at..at + stride, unique * stride);
456                    value_map.push(unique as u32);
457                    unique += 1;
458                }
459            }
460        }
461        if unique == count {
462            return Ok(unique);
463        }
464
465        if self.identity_mapping {
466            // The map was point == value, so the old value count is the point
467            // count this attribute has to keep answering for.
468            self.set_explicit_mapping(count);
469            for (point, value) in value_map.iter().enumerate() {
470                self.indices_map[point] = AttributeValueIndex(*value);
471            }
472        } else {
473            for entry in self.indices_map.iter_mut() {
474                *entry = value_map
475                    .get(entry.0 as usize)
476                    .map(|value| AttributeValueIndex(*value))
477                    .unwrap_or(INVALID_ATTRIBUTE_VALUE_INDEX);
478            }
479        }
480        // The survivors were packed forward, so the tail is stale bytes. This
481        // crate's convention is that the buffer length follows the value count
482        // -- `renumbering_shrinks_attribute_size_with_its_buffer` states it --
483        // and a consumer reading `buffer().data().len()` gets the wrong answer
484        // until the tail is gone. Upstream leaves it, because its accessors go
485        // through the count instead.
486        self.buffer.resize(unique * stride);
487        self.num_unique_entries = unique;
488        Ok(unique)
489    }
490
491    /// Drops values no point maps to, and remaps the point map onto what is
492    /// left. Returns how many values survive.
493    ///
494    /// Port of upstream's `PointAttribute::RemoveUnusedValues`, which upstream
495    /// compiles only into its transcoder and never runs from a reader. Under
496    /// identity mapping every value is used by definition, so there is nothing
497    /// to do.
498    ///
499    /// A value nothing points at still costs: the encoder quantizes over the
500    /// range of the values it is given, so one stray entry spends bits on
501    /// empty space. Measured on a unit triangle carrying an unreferenced
502    /// vertex at `1000, 1000, 1000`: same encoded size, and the surviving
503    /// coordinates came back as `1.007095` instead of `1.0`.
504    pub(crate) fn remove_unused_values(&mut self) -> usize {
505        if self.identity_mapping {
506            return self.num_unique_entries;
507        }
508        let mut used = vec![false; self.num_unique_entries];
509        let mut num_used = 0usize;
510        for value in &self.indices_map {
511            let index = value.0 as usize;
512            if index < used.len() && !used[index] {
513                used[index] = true;
514                num_used += 1;
515            }
516        }
517        if num_used == self.num_unique_entries {
518            return num_used;
519        }
520
521        let stride = usize::from(self.num_components()) * self.data_type().byte_length();
522        let mut old_to_new = vec![INVALID_ATTRIBUTE_VALUE_INDEX; self.num_unique_entries];
523        let mut next = 0usize;
524        {
525            let data = self.buffer.data_mut();
526            for old in 0..used.len() {
527                if !used[old] {
528                    continue;
529                }
530                if old != next && stride > 0 {
531                    data.copy_within(old * stride..old * stride + stride, next * stride);
532                }
533                old_to_new[old] = AttributeValueIndex(next as u32);
534                next += 1;
535            }
536        }
537        for entry in self.indices_map.iter_mut() {
538            // An entry past the value count is the invalid index, which an
539            // attribute with no values maps every point to. It stays what it
540            // is: there is no value for it to point at, and the encoder is
541            // where that gets reported.
542            *entry = old_to_new
543                .get(entry.0 as usize)
544                .copied()
545                .unwrap_or(INVALID_ATTRIBUTE_VALUE_INDEX);
546        }
547        self.buffer.resize(num_used * stride);
548        self.num_unique_entries = num_used;
549        num_used
550    }
551
552    /// Returns the raw attribute value buffer.
553    pub fn buffer(&self) -> &DataBuffer {
554        &self.buffer
555    }
556
557    /// Returns the mutable raw attribute value buffer.
558    pub fn buffer_mut(&mut self) -> &mut DataBuffer {
559        &mut self.buffer
560    }
561
562    /// Returns the semantic attribute type.
563    pub fn attribute_type(&self) -> GeometryAttributeType {
564        self.base.attribute_type()
565    }
566
567    /// Returns the stable Draco unique id.
568    pub fn unique_id(&self) -> u32 {
569        self.base.unique_id()
570    }
571
572    /// Sets the stable Draco unique id.
573    pub fn set_unique_id(&mut self, id: u32) {
574        self.base.set_unique_id(id);
575    }
576
577    /// Sets the semantic attribute type.
578    pub fn set_attribute_type(&mut self, attribute_type: GeometryAttributeType) {
579        self.base.set_attribute_type(attribute_type);
580    }
581
582    /// Sets the scalar data type.
583    pub fn set_data_type(&mut self, data_type: DataType) {
584        self.base.set_data_type(data_type);
585    }
586
587    /// Sets the number of scalar components per value.
588    pub fn set_num_components(&mut self, num_components: u8) {
589        self.base.set_num_components(num_components);
590    }
591
592    /// Returns whether point ids are used directly as attribute value ids.
593    ///
594    /// The counterpart of [`set_identity_mapping`](Self::set_identity_mapping)
595    /// and [`set_explicit_mapping`](Self::set_explicit_mapping): with identity
596    /// mapping, point `i` reads value `i`, so a caller validating the mapping
597    /// answers in one comparison rather than a call to
598    /// [`mapped_index`](Self::mapped_index) per point.
599    pub fn is_mapping_identity(&self) -> bool {
600        self.identity_mapping
601    }
602
603    /// Takes the value storage and the explicit map out, leaving both empty.
604    pub(crate) fn take_storage(&mut self) -> (Vec<u8>, Vec<AttributeValueIndex>) {
605        (
606            self.buffer.take_storage(),
607            std::mem::take(&mut self.indices_map),
608        )
609    }
610
611    /// Adopts storage taken from another attribute, emptied, when this one
612    /// holds none of its own yet.
613    ///
614    /// The map is emptied rather than kept, so whether the attribute maps
615    /// points by identity or explicitly is decided exactly as it was before:
616    /// `set_explicit_mapping` grows the map to its size, filled with the
617    /// invalid index, as it does on a fresh attribute.
618    pub(crate) fn adopt_storage(&mut self, storage: (Vec<u8>, Vec<AttributeValueIndex>)) {
619        let (bytes, mut map) = storage;
620        if !self.buffer.has_storage() {
621            self.buffer.adopt_storage(bytes);
622        }
623        if self.indices_map.capacity() == 0 {
624            map.clear();
625            self.indices_map = map;
626        }
627    }
628
629    /// Uses point ids directly as attribute value ids.
630    pub fn set_identity_mapping(&mut self) {
631        self.identity_mapping = true;
632        self.indices_map.clear();
633    }
634
635    /// Allocates an explicit point-to-attribute-value map.
636    pub fn set_explicit_mapping(&mut self, num_points: usize) {
637        self.identity_mapping = false;
638        self.indices_map
639            .resize(num_points, INVALID_ATTRIBUTE_VALUE_INDEX);
640    }
641
642    /// The explicit point-to-attribute-value map, if one is set.
643    ///
644    /// `None` under identity mapping. The bulk counterpart of calling
645    /// [`mapped_index`](Self::mapped_index) per point, for callers that copy
646    /// the map somewhere whole.
647    pub fn explicit_mapping(&self) -> Option<&[AttributeValueIndex]> {
648        if self.identity_mapping {
649            None
650        } else {
651            Some(&self.indices_map)
652        }
653    }
654
655    /// Replaces the whole point-to-attribute-value map in one copy.
656    ///
657    /// The bulk form of [`set_explicit_mapping`](Self::set_explicit_mapping)
658    /// followed by one [`try_set_point_map_entry`](Self::try_set_point_map_entry)
659    /// per point: a caller that has already assembled the full map hands it
660    /// over as a slice copy instead of a fallible call per entry.
661    pub fn set_explicit_mapping_from(&mut self, entries: &[AttributeValueIndex]) {
662        self.identity_mapping = false;
663        self.indices_map.clear();
664        self.indices_map.extend_from_slice(entries);
665    }
666
667    /// Sets one point-to-attribute-value map entry.
668    pub fn set_point_map_entry(
669        &mut self,
670        point_index: PointIndex,
671        entry_index: AttributeValueIndex,
672    ) {
673        self.try_set_point_map_entry(point_index, entry_index)
674            .expect("point map entry must be in range");
675    }
676
677    /// Fallibly sets one point-to-attribute-value map entry.
678    pub fn try_set_point_map_entry(
679        &mut self,
680        point_index: PointIndex,
681        entry_index: AttributeValueIndex,
682    ) -> Result<(), DracoError> {
683        if self.identity_mapping {
684            return Ok(());
685        }
686        let Some(slot) = self.indices_map.get_mut(point_index.0 as usize) else {
687            return Err(DracoError::general(
688                "Point map entry index out of range".to_string(),
689            ));
690        };
691        *slot = entry_index;
692        Ok(())
693    }
694
695    /// Stores transform metadata associated with this attribute.
696    pub fn set_attribute_transform_data(&mut self, data: AttributeTransformData) {
697        self.attribute_transform_data = Some(Box::new(data));
698    }
699
700    /// Returns transform metadata associated with this attribute, if present.
701    pub fn attribute_transform_data(&self) -> Option<&AttributeTransformData> {
702        self.attribute_transform_data.as_deref()
703    }
704
705    /// Returns the scalar data type.
706    pub fn data_type(&self) -> DataType {
707        self.base.data_type()
708    }
709
710    /// Returns whether integer data should be interpreted as normalized.
711    pub fn normalized(&self) -> bool {
712        self.base.normalized()
713    }
714
715    /// Returns the number of scalar components per value.
716    pub fn num_components(&self) -> u8 {
717        self.base.num_components()
718    }
719
720    /// Returns the byte stride between consecutive values.
721    pub fn byte_stride(&self) -> i64 {
722        self.base.byte_stride()
723    }
724}
725
726#[cfg(test)]
727mod tests {
728    use super::*;
729
730    fn float_attribute(components: u8, values: &[f32]) -> PointAttribute {
731        let mut attribute = PointAttribute::new();
732        attribute.init(
733            GeometryAttributeType::Position,
734            components,
735            DataType::Float32,
736            false,
737            values.len() / components as usize,
738        );
739        attribute.buffer_mut().update_f32s_le(0, values);
740        attribute
741    }
742
743    /// The packed float path, which is what every WASM reader hits.
744    #[test]
745    fn read_f32s_reads_packed_floats() {
746        let attribute = float_attribute(3, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
747        assert_eq!(
748            attribute.read_f32s(2, 3),
749            vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
750        );
751    }
752
753    /// Asking for more components than the attribute has pads rather than
754    /// shortening the row. A short row would shift every later vertex onto the
755    /// wrong values, because the result is addressed by vertex index.
756    #[test]
757    fn read_f32s_pads_missing_components() {
758        let attribute = float_attribute(2, &[1.0, 2.0, 3.0, 4.0]);
759        assert_eq!(
760            attribute.read_f32s(2, 3),
761            vec![1.0, 2.0, 0.0, 3.0, 4.0, 0.0]
762        );
763    }
764
765    /// Asking for fewer takes the leading components and drops the rest.
766    #[test]
767    fn read_f32s_truncates_extra_components() {
768        let attribute = float_attribute(3, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
769        assert_eq!(attribute.read_f32s(2, 2), vec![1.0, 2.0, 4.0, 5.0]);
770    }
771
772    /// A widening type takes the slow path and still comes back as f32.
773    #[test]
774    fn read_f32s_widens_integer_components() {
775        let mut attribute = PointAttribute::new();
776        attribute.init(GeometryAttributeType::Color, 2, DataType::Uint8, true, 2);
777        attribute.buffer_mut().update(&[1, 2, 250, 255], None);
778        assert_eq!(attribute.read_f32s(2, 2), vec![1.0, 2.0, 250.0, 255.0]);
779    }
780
781    /// Points beyond what the attribute stores read as zero rather than
782    /// panicking or running off the buffer.
783    #[test]
784    fn read_f32s_zero_fills_points_past_the_end() {
785        let attribute = float_attribute(3, &[1.0, 2.0, 3.0]);
786        assert_eq!(
787            attribute.read_f32s(2, 3),
788            vec![1.0, 2.0, 3.0, 0.0, 0.0, 0.0]
789        );
790    }
791
792    /// A non-identity mapping is followed, so two points sharing one value both
793    /// read it. The fast path must not swallow this case.
794    #[test]
795    fn read_f32s_follows_the_value_mapping() {
796        let mut attribute = float_attribute(3, &[7.0, 8.0, 9.0]);
797        attribute.set_explicit_mapping(2);
798        attribute
799            .try_set_point_map_entry(PointIndex(0), AttributeValueIndex(0))
800            .unwrap();
801        attribute
802            .try_set_point_map_entry(PointIndex(1), AttributeValueIndex(0))
803            .unwrap();
804        assert_eq!(
805            attribute.read_f32s(2, 3),
806            vec![7.0, 8.0, 9.0, 7.0, 8.0, 9.0]
807        );
808    }
809
810    #[test]
811    fn try_set_point_map_entry_rejects_out_of_range_point() {
812        let mut attribute = PointAttribute::new();
813        attribute.set_explicit_mapping(1);
814
815        assert!(attribute
816            .try_set_point_map_entry(PointIndex(1), AttributeValueIndex(0))
817            .is_err());
818    }
819}