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