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