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