Skip to main content

draco_core/
point_cloud_encoder.rs

1use crate::compression_config::EncodedGeometryType;
2use crate::draco_types::DataType;
3use crate::encoder_buffer::EncoderBuffer;
4use crate::encoder_options::EncoderOptions;
5use crate::geometry_attribute::PointAttribute;
6use crate::geometry_indices::PointIndex;
7use crate::kd_tree_attributes_encoder::KdTreeAttributesEncoder;
8use crate::mesh::Mesh;
9use crate::mesh_encoder::EncodedAttributeInfo;
10use crate::metadata::METADATA_FLAG_MASK;
11use crate::point_cloud::PointCloud;
12use crate::sequential_attribute_encoder::{
13    select_sequential_encoder, SequentialAttributeEncoderType,
14};
15use crate::sequential_integer_attribute_encoder::SequentialIntegerAttributeEncoder;
16use crate::sequential_normal_attribute_encoder::SequentialNormalAttributeEncoder;
17use crate::status::{DracoError, Status};
18use crate::version::{
19    has_header_flags, uses_varint_encoding, uses_varint_unique_id, DEFAULT_POINT_CLOUD_VERSION,
20};
21
22use crate::corner_table::CornerTable;
23
24/// Rejects attributes no encoder can represent, before any of them tries.
25///
26/// An attribute is a typed array, and both halves of its element type come from
27/// the caller: a component count and a scalar type. Neither is validated where
28/// it is set, because `PointAttribute::init` is a data-model API that mirrors
29/// C++ Draco and stores what it is given. So a geometry assembled from a file
30/// some other library parsed can reach the encoder with a zero-component or
31/// untyped attribute, and every encoder path then derives a stride, a
32/// dimension, or an axis count from it. The KD-tree coder takes the position
33/// attribute's component count as its dimension and indexes a per-axis array
34/// with it, which for zero components is an empty array indexed at 0.
35///
36/// This is the one place both encoders can share, so the refusal is stated once
37/// here rather than defended at each derivation.
38pub(crate) fn validate_encodable_attributes(point_cloud: &PointCloud) -> Status {
39    // The header carries the point count as a signed 32-bit field, and every
40    // decoder reads it back that way: upstream's `PointCloudSequentialDecoder`
41    // and `PointCloudKdTreeDecoder` refuse a negative value, and so does this
42    // crate's. Writing a count past `i32::MAX` therefore produces a file no
43    // decoder will read, which is the one thing an encoder must not do.
44    if point_cloud.num_points() > i32::MAX as usize {
45        return Err(DracoError::general(format!(
46            "Geometry has {} points, past the {} a Draco header can carry",
47            point_cloud.num_points(),
48            i32::MAX
49        )));
50    }
51    for att_id in 0..point_cloud.num_attributes() {
52        let attribute = point_cloud.attribute(att_id);
53        if attribute.num_components() == 0 {
54            return Err(DracoError::general(format!(
55                "Attribute {att_id} has zero components and cannot be encoded"
56            )));
57        }
58        if attribute.data_type() == DataType::Invalid {
59            return Err(DracoError::general(format!(
60                "Attribute {att_id} has an invalid data type and cannot be encoded"
61            )));
62        }
63
64        // Every point must land on a value the attribute actually holds. The
65        // encoders read attribute data by mapped index without re-checking it,
66        // which is correct for geometry they built themselves and wrong for
67        // geometry handed in: an identity-mapped attribute shorter than the
68        // point count, or an explicit map with an entry past the value array
69        // (including the invalid index a fresh map is filled with), otherwise
70        // reads past the value buffer.
71        //
72        // Identity mapping is the common case and answers in one comparison,
73        // since point i reads value i. Only an explicit map has to be walked,
74        // and that walk is the same order as the encode that follows it.
75        let num_values = attribute.size();
76        let num_points = point_cloud.num_points();
77        if attribute.is_mapping_identity() {
78            if num_points > num_values {
79                return Err(DracoError::general(format!(
80                    "Attribute {att_id} holds {num_values} values for {num_points} points"
81                )));
82            }
83        } else {
84            for point in 0..num_points {
85                let value = attribute.mapped_index(PointIndex(point as u32));
86                if (value.0 as usize) >= num_values {
87                    return Err(DracoError::general(format!(
88                        "Attribute {att_id} maps point {point} to value {} but holds \
89                         {num_values} values",
90                        value.0
91                    )));
92                }
93            }
94        }
95
96        validate_attribute_storage(att_id, attribute)?;
97    }
98    Ok(())
99}
100
101/// Rejects an attribute whose value buffer cannot hold the values it reports.
102///
103/// The mapping check above answers "is this value index one of ours"; this one
104/// answers "is that value actually in the buffer". They are different
105/// questions, because both the element size and the buffer length are settable
106/// after the fact: `PointAttribute::buffer_mut` hands out a `DataBuffer` whose
107/// `resize` is public, and `set_num_components` / `set_data_type` change the
108/// element size without recomputing the separately stored `byte_stride`. A
109/// loader that truncates the buffer, or that widens the component count after
110/// `init`, produces an attribute that satisfies every other rule here and still
111/// overruns its storage.
112///
113/// The overrun lands in `DataBuffer::read`, which slices unchecked; each
114/// encoder path reaches it through its own reader, so guarding it at each
115/// reader would mean finding them all and finding each new one. The
116/// quantization transform does bounds-check its own reads, but only float
117/// attributes enter it - integer attributes go straight to the sequential and
118/// KD-tree readers. One statement of the requirement here covers every reader,
119/// present and future.
120fn validate_attribute_storage(att_id: i32, attribute: &PointAttribute) -> Status {
121    let num_values = attribute.size();
122    if num_values == 0 {
123        return Ok(());
124    }
125
126    let component_size = attribute.data_type().byte_length();
127    let element_size = (attribute.num_components() as usize).saturating_mul(component_size);
128    let byte_stride = attribute.byte_stride().max(0) as usize;
129    if byte_stride < element_size {
130        return Err(DracoError::general(format!(
131            "Attribute {att_id} declares a {byte_stride}-byte stride for {element_size}-byte \
132             values"
133        )));
134    }
135
136    // The last value starts at `(num_values - 1) * byte_stride` and is
137    // `element_size` long, so the buffer needs that much and no more: a
138    // trailing gap the stride would imply is never read.
139    let required = (num_values - 1)
140        .checked_mul(byte_stride)
141        .and_then(|last_offset| last_offset.checked_add(element_size))
142        .ok_or_else(|| DracoError::general(format!("Attribute {att_id} value extent overflows")))?;
143    let available = attribute.buffer().data_size();
144    if available < required {
145        return Err(DracoError::general(format!(
146            "Attribute {att_id} needs {required} bytes for {num_values} values but its buffer \
147             holds {available}"
148        )));
149    }
150    Ok(())
151}
152
153/// Picks sequential or KD-tree encoding, as C++ `ExpertEncoder::EncodeToBuffer`
154/// does for a point cloud.
155///
156/// The default matters: with no explicit method and the default speed of 5, a
157/// point cloud whose attributes are all eligible is encoded with the **KD-tree**
158/// method, not the sequential one. Defaulting to sequential produces a different
159/// method byte and an entirely different payload from the reference encoder for
160/// the same input.
161///
162/// Note the asymmetry upstream has and this keeps: the `speed == 10` shortcut is
163/// guarded on the method being unset, so an explicitly requested KD-tree encode
164/// still takes that path at speed 10.
165fn select_encoding_method(
166    point_cloud: &PointCloud,
167    options: &EncoderOptions,
168) -> Result<i32, DracoError> {
169    const SEQUENTIAL: i32 = 0;
170    const KD_TREE: i32 = 1;
171
172    let requested = options.get_encoding_method();
173    if requested == Some(SEQUENTIAL) {
174        return Ok(SEQUENTIAL);
175    }
176    if requested.is_none() && options.get_speed() == 10 {
177        return Ok(SEQUENTIAL);
178    }
179
180    // Every attribute must be an integer type, or a float that something has
181    // asked to quantize -- the KD-tree coder works on integers alone.
182    let mut kd_tree_possible = true;
183    for att_id in 0..point_cloud.num_attributes() {
184        let attribute = point_cloud.attribute(att_id);
185        let data_type = attribute.data_type();
186        if !matches!(
187            data_type,
188            DataType::Float32
189                | DataType::Uint32
190                | DataType::Uint16
191                | DataType::Uint8
192                | DataType::Int32
193                | DataType::Int16
194                | DataType::Int8
195        ) {
196            kd_tree_possible = false;
197        }
198        if kd_tree_possible
199            && data_type == DataType::Float32
200            && options.get_attribute_int(att_id, "quantization_bits", -1) <= 0
201        {
202            kd_tree_possible = false; // Quantization not enabled.
203        }
204        if !kd_tree_possible {
205            break;
206        }
207    }
208
209    if kd_tree_possible {
210        return Ok(KD_TREE);
211    }
212    if requested == Some(KD_TREE) {
213        return Err(DracoError::general("Invalid encoding method.".to_string()));
214    }
215    Ok(SEQUENTIAL)
216}
217
218/// Geometry context used by attribute encoders and prediction selection.
219pub trait GeometryEncoder {
220    /// Returns point-cloud geometry when available.
221    fn point_cloud(&self) -> Option<&PointCloud>;
222    /// Returns mesh geometry when available.
223    fn mesh(&self) -> Option<&Mesh>;
224    /// Returns mesh corner-table topology when available.
225    fn corner_table(&self) -> Option<&CornerTable>;
226    /// Returns the active encoder options.
227    fn options(&self) -> &EncoderOptions;
228    /// Returns the encoded geometry type.
229    fn get_geometry_type(&self) -> EncodedGeometryType;
230    /// Returns the forced encoding method, if one is active.
231    fn get_encoding_method(&self) -> Option<i32> {
232        None
233    }
234    /// Returns a data-to-corner map for mesh attribute prediction, if present.
235    fn get_data_to_corner_map(&self) -> Option<&[u32]> {
236        None
237    }
238    /// Returns a vertex-to-data map for mesh attribute prediction, if present.
239    fn get_vertex_to_data_map(&self) -> Option<&[i32]> {
240        None
241    }
242    /// Returns the portable (quantized) form of an attribute, once the encoder
243    /// has transformed it. Prediction schemes that read a parent attribute --
244    /// tex coords and geometric normals both predict from the position -- must
245    /// use this and not the original floats, because the decoder only ever has
246    /// the portable values to predict from. Counterpart of C++
247    /// `PointCloudEncoder::GetPortableAttribute`.
248    fn get_portable_attribute(
249        &self,
250        _att_id: i32,
251    ) -> Option<&crate::geometry_attribute::PointAttribute> {
252        None
253    }
254}
255
256/// Encoder for Draco point cloud bitstreams.
257///
258/// A `PointCloudEncoder` takes a [`PointCloud`] plus [`EncoderOptions`] and
259/// writes a `.drc` bitstream into an [`EncoderBuffer`]. Depending on the options it uses
260/// either KD-tree or sequential attribute encoding, matching C++ Draco's
261/// `PointCloudEncoder` selection.
262///
263/// # Examples
264///
265/// ```
266/// use draco_core::{
267///     DataType, DecoderBuffer, EncoderBuffer, EncoderOptions, GeometryAttributeType,
268///     PointAttribute, PointCloud, PointCloudDecoder, PointCloudEncoder,
269/// };
270///
271/// // Three points with float32 positions.
272/// let mut pc = PointCloud::new();
273/// let mut position = PointAttribute::new();
274/// position.init(GeometryAttributeType::Position, 3, DataType::Float32, false, 3);
275/// let coords: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
276/// for (i, value) in coords.iter().enumerate() {
277///     position.buffer_mut().write(i * 4, &value.to_le_bytes());
278/// }
279/// pc.add_attribute(position);
280///
281/// // Encode, then decode it back.
282/// let mut encoder = PointCloudEncoder::new();
283/// encoder.set_point_cloud(pc);
284/// let mut buffer = EncoderBuffer::new();
285/// encoder.encode(&EncoderOptions::new(), &mut buffer)?;
286///
287/// let mut decoded = PointCloud::new();
288/// PointCloudDecoder::new().decode(&mut DecoderBuffer::new(buffer.data()), &mut decoded)?;
289/// assert_eq!(decoded.num_points(), 3);
290/// # Ok::<(), draco_core::DracoError>(())
291/// ```
292pub struct PointCloudEncoder {
293    point_cloud: Option<PointCloud>,
294    options: EncoderOptions,
295    encoded_point_cloud_info: Option<EncodedPointCloudInfo>,
296}
297
298/// Encoder choices and attribute metadata from a successful point-cloud encode.
299///
300/// The counterpart of [`EncodedMeshInfo`], and it exists for the same reason:
301/// the encoder picks the KD-tree coder over the sequential one whenever every
302/// attribute is eligible, and a caller had no way to find out which one ran.
303///
304/// [`EncodedMeshInfo`]: crate::mesh_encoder::EncodedMeshInfo
305#[derive(Debug, Clone, PartialEq)]
306#[non_exhaustive]
307pub struct EncodedPointCloudInfo {
308    /// Numeric Draco point-cloud encoding method used: 0 sequential, 1 KD-tree.
309    pub encoding_method: i32,
310    /// Bitstream version written, after the default was substituted for an
311    /// unset one.
312    pub bitstream_version: (u8, u8),
313    /// Speed the choices above were made at, after `encoding_speed` and
314    /// `decoding_speed` were resolved into one value.
315    pub speed: i32,
316    /// Number of points encoded into the bitstream.
317    pub num_encoded_points: usize,
318    /// Per-attribute information captured during encoding. Empty for the
319    /// KD-tree method, which encodes every attribute through one coder with no
320    /// per-attribute choice to report.
321    pub attributes: Vec<EncodedAttributeInfo>,
322}
323
324impl GeometryEncoder for PointCloudEncoder {
325    fn point_cloud(&self) -> Option<&PointCloud> {
326        self.point_cloud.as_ref()
327    }
328
329    fn mesh(&self) -> Option<&Mesh> {
330        None
331    }
332
333    fn corner_table(&self) -> Option<&CornerTable> {
334        None
335    }
336
337    fn options(&self) -> &EncoderOptions {
338        &self.options
339    }
340
341    fn get_geometry_type(&self) -> EncodedGeometryType {
342        EncodedGeometryType::PointCloud
343    }
344}
345
346impl Default for PointCloudEncoder {
347    fn default() -> Self {
348        Self::new()
349    }
350}
351
352impl PointCloudEncoder {
353    /// Creates an encoder without an assigned point cloud.
354    pub fn new() -> Self {
355        Self {
356            point_cloud: None,
357            options: EncoderOptions::default(),
358            encoded_point_cloud_info: None,
359        }
360    }
361
362    /// Returns the point cloud assigned to this encoder, if any.
363    pub fn point_cloud(&self) -> Option<&PointCloud> {
364        self.point_cloud.as_ref()
365    }
366
367    /// Returns what the last successful encode chose, if one has run.
368    pub fn encoded_point_cloud_info(&self) -> Option<&EncodedPointCloudInfo> {
369        self.encoded_point_cloud_info.as_ref()
370    }
371
372    /// Assigns the point cloud to encode.
373    pub fn set_point_cloud(&mut self, pc: PointCloud) {
374        self.point_cloud = Some(pc);
375    }
376
377    /// Encodes the assigned point cloud into an output buffer.
378    ///
379    /// A point cloud must have been provided with
380    /// [`set_point_cloud`](PointCloudEncoder::set_point_cloud) first.
381    ///
382    /// # Errors
383    ///
384    /// Returns an error if no point cloud was set, the options are
385    /// unsupported, or attribute encoding fails.
386    pub fn encode(&mut self, options: &EncoderOptions, out_buffer: &mut EncoderBuffer) -> Status {
387        self.options = options.clone();
388        self.encoded_point_cloud_info = None;
389
390        if self.point_cloud.is_none() {
391            return Err(DracoError::general("Point cloud not set".to_string()));
392        }
393        let pc = self.point_cloud.as_ref().unwrap();
394        validate_encodable_attributes(pc)?;
395        let method = select_encoding_method(pc, &self.options)?;
396        let (major, minor) = self.options.get_version();
397        let target = if method == 1 {
398            crate::version::EncodeTarget::PointCloudKdTree
399        } else {
400            crate::version::EncodeTarget::PointCloudSequential
401        };
402        crate::version::validate_encodable_version(major, minor, target)?;
403
404        let attributes = self.encode_geometry(out_buffer, method)?;
405
406        let (mut major, mut minor) = self.options.get_version();
407        if major == 0 && minor == 0 {
408            (major, minor) = DEFAULT_POINT_CLOUD_VERSION;
409        }
410        self.encoded_point_cloud_info = Some(EncodedPointCloudInfo {
411            encoding_method: method,
412            bitstream_version: (major, minor),
413            speed: self.options.get_speed(),
414            num_encoded_points: self
415                .point_cloud
416                .as_ref()
417                .expect("point cloud set")
418                .num_points(),
419            attributes,
420        });
421        Ok(())
422    }
423
424    /// The encode itself, split out so [`encode`](Self::encode) has one place
425    /// to record what it chose. Returns the per-attribute report, empty for the
426    /// KD-tree method and for a cloud with no attributes.
427    fn encode_geometry(
428        &mut self,
429        out_buffer: &mut EncoderBuffer,
430        method: i32,
431    ) -> Result<Vec<EncodedAttributeInfo>, DracoError> {
432        let pc = self.point_cloud.as_ref().expect("point cloud set");
433
434        // 1. Encode Header
435        self.encode_header(out_buffer, method)?;
436        self.encode_metadata(out_buffer)?;
437
438        if method == 1 {
439            // KD-Tree Encoding (Draco v2.3)
440
441            // Encode Geometry Data (Num points)
442            // Note: Draco point cloud encodes num_points as fixed u32 for both
443            // sequential and KD-tree, NOT as varint (matching decoder).
444            out_buffer.encode_u32(pc.num_points() as u32);
445
446            // No attributes, no encoder. Upstream calls
447            // GenerateAttributesEncoder once per attribute, so a cloud without
448            // any never creates one and writes a count of zero. Building one
449            // regardless would seed it with attribute id 0 and index a point
450            // cloud that has none.
451            if pc.num_attributes() == 0 {
452                out_buffer.encode_u8(0);
453                return Ok(Vec::new());
454            }
455
456            // Generate Attributes Encoders
457            // For now, we put all attributes into a single KdTreeAttributesEncoder
458            let mut att_encoder = KdTreeAttributesEncoder::new(0);
459            for i in 1..pc.num_attributes() {
460                att_encoder.add_attribute_id(i);
461            }
462
463            // Encode number of attribute encoders
464            out_buffer.encode_u8(1); // We have only 1 encoder
465
466            // Init (Transform attributes to portable format)
467            att_encoder
468                .transform_attributes_to_portable_format(pc, &self.options)
469                .map_err(|err| err.context("Failed to transform attributes"))?;
470
471            // Note: KD-tree encoding does NOT write an encoder type identifier byte.
472            // This is different from sequential encoding where each attribute has a decoder type.
473            // The decoder knows to use KdTreeAttributesDecoder because the encoding method
474            // in the header is 1 (KD-tree).
475
476            // Encode Attributes Encoder Data (Metadata)
477            att_encoder
478                .encode_attributes_encoder_data(pc, out_buffer)
479                .map_err(|err| err.context("Failed to encode attribute metadata"))?;
480
481            // Encode Attributes (Portable Data)
482            att_encoder
483                .encode_attributes(pc, &self.options, out_buffer)
484                .map_err(|err| err.context("Failed to encode attributes"))?;
485
486            // Encode Attributes Transform Data
487            att_encoder
488                .encode_data_needed_by_portable_transforms(out_buffer)
489                .map_err(|err| err.context("Failed to encode attribute transform data"))?;
490        } else {
491            // Sequential Encoding (Draco v1.3)
492            //
493            // C++ Structure:
494            // 1. num_points (u32)
495            // 2. num_attribute_encoders (u8)
496            // 3. For each encoder: encoder_identifier (none for sequential - skipped in v1.3)
497            // 4. For each encoder: EncodeAttributesEncoderData
498            //    - num_attributes_in_encoder (varint for v2+, u32 for v1.x)
499            //    - for each attribute: type, data_type, num_components, normalized, unique_id
500            // 5. For each attribute: decoder_type (u8)
501            // 6. For each attribute: encoded data
502
503            let num_points = pc.num_points();
504            let num_attributes = pc.num_attributes();
505            let point_ids: Vec<PointIndex> =
506                (0..num_points).map(|i| PointIndex(i as u32)).collect();
507
508            // Draco bitstream < 2.0 encodes number of points as a fixed u32.
509            out_buffer.encode_u32(num_points as u32);
510
511            // Number of attribute encoders
512            // For empty point clouds (0 attributes), we write 0 encoders
513            if num_attributes == 0 {
514                out_buffer.encode_u8(0);
515                return Ok(Vec::new());
516            }
517
518            // For non-empty point clouds, use 1 encoder for all attributes
519            out_buffer.encode_u8(1);
520
521            // Encode attributes encoder data:
522            // Use the buffer's version (set in encode_header) for version checks
523            let major = out_buffer.version_major();
524            let minor = out_buffer.version_minor();
525            if !uses_varint_encoding(major, minor) {
526                out_buffer.encode_u32(num_attributes as u32);
527            } else {
528                out_buffer.encode_varint(num_attributes as u64);
529            }
530
531            // For each attribute, encode metadata
532            for i in 0..num_attributes {
533                let att = pc.attribute(i);
534                out_buffer.encode_u8(att.attribute_type() as u8);
535                out_buffer.encode_u8(att.data_type() as u8);
536                out_buffer.encode_u8(att.num_components());
537                out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
538
539                if !uses_varint_unique_id(major, minor) {
540                    out_buffer.encode_u16(att.unique_id() as u16);
541                } else {
542                    out_buffer.encode_varint(att.unique_id() as u64);
543                }
544            }
545
546            // One identifier byte per attribute, naming the encoder that writes
547            // it. Picked once here and dispatched on below, so the byte cannot
548            // disagree with the encoder that actually runs.
549            let encoder_types: Vec<SequentialAttributeEncoderType> = (0..num_attributes)
550                .map(|i| {
551                    let quantization_bits =
552                        self.options.get_attribute_int(i, "quantization_bits", -1);
553                    select_sequential_encoder(pc.attribute(i), quantization_bits)
554                })
555                .collect();
556            for &encoder_type in &encoder_types {
557                out_buffer.encode_u8(encoder_type as u8);
558            }
559
560            // Encoding follows C++ order:
561            // 1. EncodePortableAttributes (encode_values for each attribute)
562            // 2. EncodeDataNeededByPortableTransforms (transform params for each attribute)
563
564            // Store encoders so we can call encode_data_needed_by_portable_transform later
565            let mut integer_encoders: Vec<Option<SequentialIntegerAttributeEncoder>> =
566                Vec::with_capacity(num_attributes as usize);
567            let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> =
568                Vec::with_capacity(num_attributes as usize);
569
570            // First pass: encode all values
571            for i in 0..num_attributes {
572                let att = pc.attribute(i);
573
574                match encoder_types[i as usize] {
575                    SequentialAttributeEncoderType::Normals => {
576                        let mut att_encoder = SequentialNormalAttributeEncoder::new();
577                        att_encoder.init(pc, i, &self.options).map_err(|e| {
578                            DracoError::general(format!(
579                                "Failed to init normal attribute encoder {i}: {e}"
580                            ))
581                        })?;
582
583                        att_encoder.encode_values(
584                            pc,
585                            &point_ids,
586                            out_buffer,
587                            &self.options,
588                            self,
589                        )?;
590
591                        integer_encoders.push(None);
592                        normal_encoders.push(Some(att_encoder));
593                        continue;
594                    }
595                    SequentialAttributeEncoderType::Quantization
596                    | SequentialAttributeEncoderType::Integer => {
597                        let mut att_encoder = SequentialIntegerAttributeEncoder::new();
598                        att_encoder.init(i);
599
600                        att_encoder.encode_values(
601                            pc,
602                            &point_ids,
603                            out_buffer,
604                            &self.options,
605                            self,
606                            None,
607                            false,
608                        )?;
609
610                        integer_encoders.push(Some(att_encoder));
611                    }
612                    SequentialAttributeEncoderType::Generic => {
613                        let entry_size = att.byte_stride() as usize;
614                        let data = att.buffer().data();
615                        for &point_id in &point_ids {
616                            let value_index = att.mapped_index(point_id).0 as usize;
617                            let offset = value_index.checked_mul(entry_size).ok_or_else(|| {
618                                DracoError::general(
619                                    "Point cloud raw attribute offset overflow".to_string(),
620                                )
621                            })?;
622                            let end = offset.checked_add(entry_size).ok_or_else(|| {
623                                DracoError::general(
624                                    "Point cloud raw attribute byte range overflow".to_string(),
625                                )
626                            })?;
627                            if end > data.len() {
628                                return Err(DracoError::general(
629                                    "Point cloud raw attribute data out of bounds".to_string(),
630                                ));
631                            }
632                            out_buffer.encode_data(&data[offset..end]);
633                        }
634
635                        integer_encoders.push(None);
636                    }
637                }
638
639                normal_encoders.push(None);
640            }
641
642            // Second pass: encode transform parameters (EncodeDataNeededByPortableTransforms)
643            for i in 0..num_attributes as usize {
644                if encoder_types[i] == SequentialAttributeEncoderType::Normals {
645                    if let Some(ref att_encoder) = normal_encoders[i] {
646                        let (major, minor) = self.options.get_version();
647                        let bitstream_version = crate::version::bitstream_version(major, minor);
648                        if bitstream_version != 0 && bitstream_version < 0x0102 {
649                            continue;
650                        }
651                        att_encoder
652                            .encode_data_needed_by_portable_transform(out_buffer)
653                            .map_err(|err| {
654                                DracoError::general(format!(
655                                    "Failed to encode normal attribute transform data {i}: {err}"
656                                ))
657                            })?;
658                    }
659                } else if let Some(ref att_encoder) = integer_encoders[i] {
660                    att_encoder
661                        .encode_data_needed_by_portable_transform(out_buffer)
662                        .map_err(|err| {
663                            DracoError::general(format!(
664                                "Failed to encode quantization transform data {i}: {err}"
665                            ))
666                        })?;
667                }
668            }
669
670            // Read back off the encoders that just ran: the prediction scheme
671            // is theirs to choose, and a request for one is only honoured when
672            // the attribute supports it.
673            let mut attributes = Vec::with_capacity(num_attributes as usize);
674            for i in 0..num_attributes {
675                let att = pc.attribute(i);
676                let encoder_type = encoder_types[i as usize];
677                let prediction = match encoder_type {
678                    SequentialAttributeEncoderType::Normals => normal_encoders[i as usize]
679                        .as_ref()
680                        .and_then(|encoder| encoder.selected_prediction()),
681                    _ => integer_encoders[i as usize]
682                        .as_ref()
683                        .and_then(|encoder| encoder.selected_prediction()),
684                };
685                let quantization_bits = match encoder_type {
686                    SequentialAttributeEncoderType::Quantization
687                    | SequentialAttributeEncoderType::Normals => {
688                        Some(self.options.get_attribute_int(i, "quantization_bits", -1))
689                    }
690                    SequentialAttributeEncoderType::Integer
691                    | SequentialAttributeEncoderType::Generic => None,
692                };
693                attributes.push(EncodedAttributeInfo {
694                    source_attribute_id: i,
695                    attribute_type: att.attribute_type(),
696                    data_type: att.data_type(),
697                    num_components: att.num_components(),
698                    normalized: att.normalized(),
699                    unique_id: att.unique_id(),
700                    num_encoded_values: att.size(),
701                    encoder_type,
702                    quantization_bits,
703                    prediction,
704                    position_min: None,
705                    position_max: None,
706                });
707            }
708            return Ok(attributes);
709        }
710
711        Ok(Vec::new())
712    }
713
714    fn encode_metadata(&self, buffer: &mut EncoderBuffer) -> Status {
715        if let Some(metadata) = self
716            .point_cloud
717            .as_ref()
718            .and_then(|point_cloud| point_cloud.metadata())
719            .filter(|metadata| !metadata.is_empty())
720        {
721            metadata.encode(buffer)?;
722        }
723        Ok(())
724    }
725
726    fn encode_header(&self, buffer: &mut EncoderBuffer, method: i32) -> Status {
727        let (mut major, mut minor) = self.options.get_version();
728        if major == 0 && minor == 0 {
729            (major, minor) = DEFAULT_POINT_CLOUD_VERSION;
730        }
731        let has_metadata = self
732            .point_cloud
733            .as_ref()
734            .and_then(|point_cloud| point_cloud.metadata())
735            .is_some_and(|metadata| !metadata.is_empty());
736
737        if has_metadata && !has_header_flags(major, minor) {
738            return Err(DracoError::unsupported_version(
739                "Metadata requires Draco bitstream version 1.3 or newer".to_string(),
740            ));
741        }
742
743        #[cfg(not(feature = "legacy_bitstream_encode"))]
744        match self.options.get_prediction_scheme() {
745            2 | 3 => {
746                return Err(DracoError::unsupported_feature(
747                    "legacy prediction schemes require the legacy_bitstream_encode feature"
748                        .to_string(),
749                ));
750            }
751            _ => {}
752        }
753
754        buffer.encode_data(b"DRACO");
755
756        buffer.encode_u8(major);
757        buffer.encode_u8(minor);
758        buffer.set_version(major, minor);
759
760        buffer.encode_u8(self.get_geometry_type() as u8);
761        buffer.encode_u8(method as u8);
762
763        // The flags field is part of the header for every version this crate
764        // encodes: upstream `PointCloudEncoder::EncodeHeader` writes it
765        // unconditionally as far back as 1.0.0, and both decoders read it
766        // unconditionally. Writing it only from 1.3 left a stream that was two
767        // bytes short of what its own decoder expects, so an explicit
768        // `set_version(1, 0)` produced a `.drc` nothing could read.
769        let flags = if has_metadata { METADATA_FLAG_MASK } else { 0 };
770        buffer.encode_u16(flags);
771        Ok(())
772    }
773
774    /// Returns the geometry type produced by this encoder.
775    pub fn get_geometry_type(&self) -> EncodedGeometryType {
776        EncodedGeometryType::PointCloud
777    }
778}