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