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        // Value indices below this bound have a byte offset, `index * stride`,
362        // that fits a `usize`; everything past the offset is read through a
363        // slice of what remains, so no sum can wrap either. Taken once here,
364        // it costs a point the one comparison it already paid for the
365        // unique-entry count. It also catches INVALID_ATTRIBUTE_VALUE_INDEX.
366        let readable = match stride {
367            0 => self.num_unique_entries,
368            stride => self.num_unique_entries.min(usize::MAX / stride),
369        };
370        if self.data_type() == DataType::Float32 {
371            // The general loop below, with the type's match and the per-component
372            // bounds test taken once per point: a value whose components all
373            // fit reads them as one run, which is every value of a well-formed
374            // attribute, and one that does not falls back to that loop's
375            // component-at-a-time reading.
376            for (point, out) in values.chunks_exact_mut(components).enumerate() {
377                let value_index = self.mapped_index(PointIndex(point as u32)).0 as usize;
378                if value_index >= readable {
379                    continue;
380                }
381                let Some(rest) = data.get(value_index * stride..) else {
382                    continue;
383                };
384                match rest.get(..available * 4) {
385                    Some(run) => {
386                        for (slot, bytes) in out.iter_mut().zip(run.as_chunks::<4>().0) {
387                            *slot = f32::from_le_bytes(*bytes);
388                        }
389                    }
390                    None => {
391                        for (component, slot) in out[..available].iter_mut().enumerate() {
392                            let offset = component * 4;
393                            if let Some(bytes) = rest.get(offset..offset + 4) {
394                                *slot = scalar_as_f32(DataType::Float32, bytes);
395                            }
396                        }
397                    }
398                }
399            }
400            return values;
401        }
402        for point in 0..num_points {
403            let value_index = self.mapped_index(PointIndex(point as u32)).0 as usize;
404            if value_index >= readable {
405                continue;
406            }
407            let Some(rest) = data.get(value_index * stride..) else {
408                continue;
409            };
410            for component in 0..available {
411                let offset = component * width;
412                if let Some(bytes) = rest.get(offset..offset + width) {
413                    values[point * components + component] = scalar_as_f32(self.data_type(), bytes);
414                }
415            }
416        }
417        values
418    }
419
420    /// Resizes the unique attribute value storage.
421    pub fn resize_unique_entries(&mut self, num_attribute_values: usize) -> Result<(), DracoError> {
422        let byte_stride = self.byte_stride() as usize;
423        let buffer_size = num_attribute_values
424            .checked_mul(byte_stride)
425            .ok_or_else(|| {
426                DracoError::general("Point attribute buffer size overflow".to_string())
427            })?;
428        self.buffer.try_resize(buffer_size).map_err(|_| {
429            DracoError::general("Failed to allocate point attribute buffer".to_string())
430        })?;
431        self.num_unique_entries = num_attribute_values;
432        if self.identity_mapping {
433            self.indices_map.clear();
434        }
435        Ok(())
436    }
437
438    /// Merges values that are bit-identical, and remaps the point map onto
439    /// what survives. Returns how many values are left.
440    ///
441    /// Port of upstream's `PointAttribute::DeduplicateValues`, which every one
442    /// of its readers runs before handing a mesh to the encoder. Two vertices
443    /// carrying the same position arrive as two values here and as one there,
444    /// and the difference is not cosmetic: with the values merged, triangles
445    /// that shared only a vertex come to share an edge, so the encoder sees
446    /// one connected component where it otherwise sees two.
447    ///
448    /// Comparison is over the raw bytes, which is upstream's rule and not an
449    /// approximation of it: it bit-copies each value into an integer of the
450    /// same width before hashing, so `-0.0` and `0.0` are distinct values on
451    /// both sides and two `NaN`s merge exactly when their payloads match.
452    ///
453    /// Upstream's switch stops at 32 bits and refuses anything wider; this one
454    /// also takes the 64-bit types. Nothing upstream accepts comes out
455    /// differently, and the wider types are ones Draco encodes, so a reader
456    /// carrying a PLY `double` property onto a mesh would otherwise fail on a
457    /// value it can store. Component counts outside `1..=4` are refused.
458    pub(crate) fn deduplicate_values(&mut self) -> Result<usize, DracoError> {
459        let stride = match self.data_type() {
460            DataType::Int8 | DataType::Uint8 | DataType::Bool => 1,
461            DataType::Int16 | DataType::Uint16 => 2,
462            DataType::Int32 | DataType::Uint32 | DataType::Float32 => 4,
463            DataType::Int64 | DataType::Uint64 | DataType::Float64 => 8,
464            other => {
465                return Err(DracoError::unsupported_feature(format!(
466                    "deduplicating {other:?} attribute values"
467                )))
468            }
469        } * usize::from(self.num_components());
470        if !(1..=4).contains(&self.num_components()) {
471            return Err(DracoError::unsupported_feature(format!(
472                "deduplicating a {}-component attribute",
473                self.num_components()
474            )));
475        }
476
477        let count = self.num_unique_entries;
478        let data = self.buffer.data_mut();
479        // Everything 32 bits wide fits the narrow key, and hashing it costs
480        // half what the wide one does.
481        let (value_map, unique) = if stride <= 16 {
482            pack_unique_values::<16>(data, count, stride)
483        } else {
484            pack_unique_values::<32>(data, count, stride)
485        };
486        if unique == count {
487            return Ok(unique);
488        }
489
490        if self.identity_mapping {
491            // The map was point == value, so the old value count is the point
492            // count this attribute has to keep answering for.
493            self.set_explicit_mapping(count);
494            for (point, value) in value_map.iter().enumerate() {
495                self.indices_map[point] = AttributeValueIndex(*value);
496            }
497        } else {
498            for entry in self.indices_map.iter_mut() {
499                *entry = value_map
500                    .get(entry.0 as usize)
501                    .map(|value| AttributeValueIndex(*value))
502                    .unwrap_or(INVALID_ATTRIBUTE_VALUE_INDEX);
503            }
504        }
505        // The survivors were packed forward, so the tail is stale bytes. This
506        // crate's convention is that the buffer length follows the value count
507        // -- `renumbering_shrinks_attribute_size_with_its_buffer` states it --
508        // and a consumer reading `buffer().data().len()` gets the wrong answer
509        // until the tail is gone. Upstream leaves it, because its accessors go
510        // through the count instead.
511        self.buffer.resize(unique * stride);
512        self.num_unique_entries = unique;
513        Ok(unique)
514    }
515
516    /// Drops values no point maps to, and remaps the point map onto what is
517    /// left. Returns how many values survive.
518    ///
519    /// Port of upstream's `PointAttribute::RemoveUnusedValues`, which upstream
520    /// compiles only into its transcoder and never runs from a reader. Under
521    /// identity mapping every value is used by definition, so there is nothing
522    /// to do.
523    ///
524    /// A value nothing points at still costs: the encoder quantizes over the
525    /// range of the values it is given, so one stray entry spends bits on
526    /// empty space. Measured on a unit triangle carrying an unreferenced
527    /// vertex at `1000, 1000, 1000`: same encoded size, and the surviving
528    /// coordinates came back as `1.007095` instead of `1.0`.
529    pub(crate) fn remove_unused_values(&mut self) -> usize {
530        if self.identity_mapping {
531            return self.num_unique_entries;
532        }
533        let mut used = vec![false; self.num_unique_entries];
534        let mut num_used = 0usize;
535        for value in &self.indices_map {
536            let index = value.0 as usize;
537            if index < used.len() && !used[index] {
538                used[index] = true;
539                num_used += 1;
540            }
541        }
542        if num_used == self.num_unique_entries {
543            return num_used;
544        }
545
546        let stride = usize::from(self.num_components()) * self.data_type().byte_length();
547        let mut old_to_new = vec![INVALID_ATTRIBUTE_VALUE_INDEX; self.num_unique_entries];
548        let mut next = 0usize;
549        {
550            let data = self.buffer.data_mut();
551            for old in 0..used.len() {
552                if !used[old] {
553                    continue;
554                }
555                if old != next && stride > 0 {
556                    data.copy_within(old * stride..old * stride + stride, next * stride);
557                }
558                old_to_new[old] = AttributeValueIndex(next as u32);
559                next += 1;
560            }
561        }
562        for entry in self.indices_map.iter_mut() {
563            // An entry past the value count is the invalid index, which an
564            // attribute with no values maps every point to. It stays what it
565            // is: there is no value for it to point at, and the encoder is
566            // where that gets reported.
567            *entry = old_to_new
568                .get(entry.0 as usize)
569                .copied()
570                .unwrap_or(INVALID_ATTRIBUTE_VALUE_INDEX);
571        }
572        self.buffer.resize(num_used * stride);
573        self.num_unique_entries = num_used;
574        num_used
575    }
576
577    /// Returns the raw attribute value buffer.
578    pub fn buffer(&self) -> &DataBuffer {
579        &self.buffer
580    }
581
582    /// Returns the mutable raw attribute value buffer.
583    pub fn buffer_mut(&mut self) -> &mut DataBuffer {
584        &mut self.buffer
585    }
586
587    /// Returns the semantic attribute type.
588    pub fn attribute_type(&self) -> GeometryAttributeType {
589        self.base.attribute_type()
590    }
591
592    /// Returns the stable Draco unique id.
593    pub fn unique_id(&self) -> u32 {
594        self.base.unique_id()
595    }
596
597    /// Sets the stable Draco unique id.
598    pub fn set_unique_id(&mut self, id: u32) {
599        self.base.set_unique_id(id);
600    }
601
602    /// Sets the semantic attribute type.
603    pub fn set_attribute_type(&mut self, attribute_type: GeometryAttributeType) {
604        self.base.set_attribute_type(attribute_type);
605    }
606
607    /// Sets the scalar data type.
608    pub fn set_data_type(&mut self, data_type: DataType) {
609        self.base.set_data_type(data_type);
610    }
611
612    /// Sets the number of scalar components per value.
613    pub fn set_num_components(&mut self, num_components: u8) {
614        self.base.set_num_components(num_components);
615    }
616
617    /// Returns whether point ids are used directly as attribute value ids.
618    ///
619    /// The counterpart of [`set_identity_mapping`](Self::set_identity_mapping)
620    /// and [`set_explicit_mapping`](Self::set_explicit_mapping): with identity
621    /// mapping, point `i` reads value `i`, so a caller validating the mapping
622    /// answers in one comparison rather than a call to
623    /// [`mapped_index`](Self::mapped_index) per point.
624    pub fn is_mapping_identity(&self) -> bool {
625        self.identity_mapping
626    }
627
628    /// Takes the value storage and the explicit map out, leaving both empty.
629    pub(crate) fn take_storage(&mut self) -> (Vec<u8>, Vec<AttributeValueIndex>) {
630        (
631            self.buffer.take_storage(),
632            std::mem::take(&mut self.indices_map),
633        )
634    }
635
636    /// Adopts storage taken from another attribute, emptied, when this one
637    /// holds none of its own yet.
638    ///
639    /// The map is emptied rather than kept, so whether the attribute maps
640    /// points by identity or explicitly is decided exactly as it was before:
641    /// `set_explicit_mapping` grows the map to its size, filled with the
642    /// invalid index, as it does on a fresh attribute.
643    pub(crate) fn adopt_storage(&mut self, storage: (Vec<u8>, Vec<AttributeValueIndex>)) {
644        let (bytes, mut map) = storage;
645        if !self.buffer.has_storage() {
646            self.buffer.adopt_storage(bytes);
647        }
648        if self.indices_map.capacity() == 0 {
649            map.clear();
650            self.indices_map = map;
651        }
652    }
653
654    /// Uses point ids directly as attribute value ids.
655    pub fn set_identity_mapping(&mut self) {
656        self.identity_mapping = true;
657        self.indices_map.clear();
658    }
659
660    /// Allocates an explicit point-to-attribute-value map.
661    pub fn set_explicit_mapping(&mut self, num_points: usize) {
662        self.identity_mapping = false;
663        self.indices_map
664            .resize(num_points, INVALID_ATTRIBUTE_VALUE_INDEX);
665    }
666
667    /// The explicit point-to-attribute-value map, if one is set.
668    ///
669    /// `None` under identity mapping. The bulk counterpart of calling
670    /// [`mapped_index`](Self::mapped_index) per point, for callers that copy
671    /// the map somewhere whole.
672    pub fn explicit_mapping(&self) -> Option<&[AttributeValueIndex]> {
673        if self.identity_mapping {
674            None
675        } else {
676            Some(&self.indices_map)
677        }
678    }
679
680    /// Replaces the whole point-to-attribute-value map in one copy.
681    ///
682    /// The bulk form of [`set_explicit_mapping`](Self::set_explicit_mapping)
683    /// followed by one [`try_set_point_map_entry`](Self::try_set_point_map_entry)
684    /// per point: a caller that has already assembled the full map hands it
685    /// over as a slice copy instead of a fallible call per entry.
686    pub fn set_explicit_mapping_from(&mut self, entries: &[AttributeValueIndex]) {
687        self.identity_mapping = false;
688        self.indices_map.clear();
689        self.indices_map.extend_from_slice(entries);
690    }
691
692    /// Sets one point-to-attribute-value map entry.
693    pub fn set_point_map_entry(
694        &mut self,
695        point_index: PointIndex,
696        entry_index: AttributeValueIndex,
697    ) {
698        self.try_set_point_map_entry(point_index, entry_index)
699            .expect("point map entry must be in range");
700    }
701
702    /// Fallibly sets one point-to-attribute-value map entry.
703    pub fn try_set_point_map_entry(
704        &mut self,
705        point_index: PointIndex,
706        entry_index: AttributeValueIndex,
707    ) -> Result<(), DracoError> {
708        if self.identity_mapping {
709            return Ok(());
710        }
711        let Some(slot) = self.indices_map.get_mut(point_index.0 as usize) else {
712            return Err(DracoError::general(
713                "Point map entry index out of range".to_string(),
714            ));
715        };
716        *slot = entry_index;
717        Ok(())
718    }
719
720    /// Stores transform metadata associated with this attribute.
721    pub fn set_attribute_transform_data(&mut self, data: AttributeTransformData) {
722        self.attribute_transform_data = Some(Box::new(data));
723    }
724
725    /// Returns transform metadata associated with this attribute, if present.
726    pub fn attribute_transform_data(&self) -> Option<&AttributeTransformData> {
727        self.attribute_transform_data.as_deref()
728    }
729
730    /// Returns the scalar data type.
731    pub fn data_type(&self) -> DataType {
732        self.base.data_type()
733    }
734
735    /// Returns whether integer data should be interpreted as normalized.
736    pub fn normalized(&self) -> bool {
737        self.base.normalized()
738    }
739
740    /// Returns the number of scalar components per value.
741    pub fn num_components(&self) -> u8 {
742        self.base.num_components()
743    }
744
745    /// Returns the byte stride between consecutive values.
746    pub fn byte_stride(&self) -> i64 {
747        self.base.byte_stride()
748    }
749}
750
751/// Merges bit-identical values of `stride` bytes among the first `count` in
752/// `data`, packing the survivors to the front in arrival order. Returns each
753/// old value's new index and how many survive.
754///
755/// `N` is the hash key's width and must be at least `stride`; the key is
756/// inline, so the map allocates nothing per entry.
757fn pack_unique_values<const N: usize>(
758    data: &mut [u8],
759    count: usize,
760    stride: usize,
761) -> (Vec<u32>, usize) {
762    let mut seen: std::collections::HashMap<[u8; N], u32> =
763        std::collections::HashMap::with_capacity(count);
764    let mut value_map: Vec<u32> = Vec::with_capacity(count);
765    let mut unique = 0usize;
766    for i in 0..count {
767        let at = i * stride;
768        let mut key = [0u8; N];
769        key[..stride].copy_from_slice(&data[at..at + stride]);
770        match seen.entry(key) {
771            std::collections::hash_map::Entry::Occupied(entry) => {
772                value_map.push(*entry.get());
773            }
774            std::collections::hash_map::Entry::Vacant(entry) => {
775                entry.insert(unique as u32);
776                // Survivors are packed forward as they are found, so the
777                // buffer needs no second pass and no second allocation.
778                data.copy_within(at..at + stride, unique * stride);
779                value_map.push(unique as u32);
780                unique += 1;
781            }
782        }
783    }
784    (value_map, unique)
785}
786
787#[cfg(test)]
788mod tests {
789    use super::*;
790
791    fn float_attribute(components: u8, values: &[f32]) -> PointAttribute {
792        let mut attribute = PointAttribute::new();
793        attribute.init(
794            GeometryAttributeType::Position,
795            components,
796            DataType::Float32,
797            false,
798            values.len() / components as usize,
799        );
800        attribute.buffer_mut().update_f32s_le(0, values);
801        attribute
802    }
803
804    /// The packed float path, which is what every WASM reader hits.
805    #[test]
806    fn read_f32s_reads_packed_floats() {
807        let attribute = float_attribute(3, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
808        assert_eq!(
809            attribute.read_f32s(2, 3),
810            vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
811        );
812    }
813
814    /// Asking for more components than the attribute has pads rather than
815    /// shortening the row. A short row would shift every later vertex onto the
816    /// wrong values, because the result is addressed by vertex index.
817    #[test]
818    fn read_f32s_pads_missing_components() {
819        let attribute = float_attribute(2, &[1.0, 2.0, 3.0, 4.0]);
820        assert_eq!(
821            attribute.read_f32s(2, 3),
822            vec![1.0, 2.0, 0.0, 3.0, 4.0, 0.0]
823        );
824    }
825
826    /// Asking for fewer takes the leading components and drops the rest.
827    #[test]
828    fn read_f32s_truncates_extra_components() {
829        let attribute = float_attribute(3, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
830        assert_eq!(attribute.read_f32s(2, 2), vec![1.0, 2.0, 4.0, 5.0]);
831    }
832
833    /// A widening type takes the slow path and still comes back as f32.
834    #[test]
835    fn read_f32s_widens_integer_components() {
836        let mut attribute = PointAttribute::new();
837        attribute.init(GeometryAttributeType::Color, 2, DataType::Uint8, true, 2);
838        attribute.buffer_mut().update(&[1, 2, 250, 255], None);
839        assert_eq!(attribute.read_f32s(2, 2), vec![1.0, 2.0, 250.0, 255.0]);
840    }
841
842    /// Points beyond what the attribute stores read as zero rather than
843    /// panicking or running off the buffer.
844    #[test]
845    fn read_f32s_zero_fills_points_past_the_end() {
846        let attribute = float_attribute(3, &[1.0, 2.0, 3.0]);
847        assert_eq!(
848            attribute.read_f32s(2, 3),
849            vec![1.0, 2.0, 3.0, 0.0, 0.0, 0.0]
850        );
851    }
852
853    /// A buffer that ends inside a value still yields the components before the
854    /// end and zeros after it, which is the component-at-a-time reading the
855    /// one-run float path falls back to.
856    #[test]
857    fn read_f32s_reads_a_cut_value_component_by_component() {
858        let mut attribute = float_attribute(3, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
859        attribute.buffer_mut().resize(5 * 4);
860        assert_eq!(
861            attribute.read_f32s(2, 3),
862            vec![1.0, 2.0, 3.0, 4.0, 5.0, 0.0]
863        );
864    }
865
866    /// A non-identity mapping is followed, so two points sharing one value both
867    /// read it. The fast path must not swallow this case.
868    #[test]
869    fn read_f32s_follows_the_value_mapping() {
870        let mut attribute = float_attribute(3, &[7.0, 8.0, 9.0]);
871        attribute.set_explicit_mapping(2);
872        attribute
873            .try_set_point_map_entry(PointIndex(0), AttributeValueIndex(0))
874            .unwrap();
875        attribute
876            .try_set_point_map_entry(PointIndex(1), AttributeValueIndex(0))
877            .unwrap();
878        assert_eq!(
879            attribute.read_f32s(2, 3),
880            vec![7.0, 8.0, 9.0, 7.0, 8.0, 9.0]
881        );
882    }
883
884    #[test]
885    fn try_set_point_map_entry_rejects_out_of_range_point() {
886        let mut attribute = PointAttribute::new();
887        attribute.set_explicit_mapping(1);
888
889        assert!(attribute
890            .try_set_point_map_entry(PointIndex(1), AttributeValueIndex(0))
891            .is_err());
892    }
893}