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_indices::PointIndex;
6use crate::kd_tree_attributes_encoder::KdTreeAttributesEncoder;
7use crate::mesh::Mesh;
8use crate::metadata::METADATA_FLAG_MASK;
9use crate::point_cloud::PointCloud;
10use crate::sequential_attribute_encoder::{
11    select_sequential_encoder, SequentialAttributeEncoderType,
12};
13use crate::sequential_integer_attribute_encoder::SequentialIntegerAttributeEncoder;
14use crate::sequential_normal_attribute_encoder::SequentialNormalAttributeEncoder;
15use crate::status::{DracoError, Status};
16use crate::version::{
17    has_header_flags, uses_varint_encoding, uses_varint_unique_id, DEFAULT_POINT_CLOUD_VERSION,
18};
19
20use crate::corner_table::CornerTable;
21
22/// Picks sequential or KD-tree encoding, as C++ `ExpertEncoder::EncodeToBuffer`
23/// does for a point cloud.
24///
25/// The default matters: with no explicit method and the default speed of 5, a
26/// point cloud whose attributes are all eligible is encoded with the **KD-tree**
27/// method, not the sequential one. Defaulting to sequential produces a different
28/// method byte and an entirely different payload from the reference encoder for
29/// the same input.
30///
31/// Note the asymmetry upstream has and this keeps: the `speed == 10` shortcut is
32/// guarded on the method being unset, so an explicitly requested KD-tree encode
33/// still takes that path at speed 10.
34fn select_encoding_method(
35    point_cloud: &PointCloud,
36    options: &EncoderOptions,
37) -> Result<i32, DracoError> {
38    const SEQUENTIAL: i32 = 0;
39    const KD_TREE: i32 = 1;
40
41    let requested = options.get_encoding_method();
42    if requested == Some(SEQUENTIAL) {
43        return Ok(SEQUENTIAL);
44    }
45    if requested.is_none() && options.get_speed() == 10 {
46        return Ok(SEQUENTIAL);
47    }
48
49    // Every attribute must be an integer type, or a float that something has
50    // asked to quantize -- the KD-tree coder works on integers alone.
51    let mut kd_tree_possible = true;
52    for att_id in 0..point_cloud.num_attributes() {
53        let attribute = point_cloud.attribute(att_id);
54        let data_type = attribute.data_type();
55        if !matches!(
56            data_type,
57            DataType::Float32
58                | DataType::Uint32
59                | DataType::Uint16
60                | DataType::Uint8
61                | DataType::Int32
62                | DataType::Int16
63                | DataType::Int8
64        ) {
65            kd_tree_possible = false;
66        }
67        if kd_tree_possible
68            && data_type == DataType::Float32
69            && options.get_attribute_int(att_id, "quantization_bits", -1) <= 0
70        {
71            kd_tree_possible = false; // Quantization not enabled.
72        }
73        if !kd_tree_possible {
74            break;
75        }
76    }
77
78    if kd_tree_possible {
79        return Ok(KD_TREE);
80    }
81    if requested == Some(KD_TREE) {
82        return Err(DracoError::DracoError(
83            "Invalid encoding method.".to_string(),
84        ));
85    }
86    Ok(SEQUENTIAL)
87}
88
89/// Geometry context used by attribute encoders and prediction selection.
90pub trait GeometryEncoder {
91    /// Returns point-cloud geometry when available.
92    fn point_cloud(&self) -> Option<&PointCloud>;
93    /// Returns mesh geometry when available.
94    fn mesh(&self) -> Option<&Mesh>;
95    /// Returns mesh corner-table topology when available.
96    fn corner_table(&self) -> Option<&CornerTable>;
97    /// Returns the active encoder options.
98    fn options(&self) -> &EncoderOptions;
99    /// Returns the encoded geometry type.
100    fn get_geometry_type(&self) -> EncodedGeometryType;
101    /// Returns the forced encoding method, if one is active.
102    fn get_encoding_method(&self) -> Option<i32> {
103        None
104    }
105    /// Returns a data-to-corner map for mesh attribute prediction, if present.
106    fn get_data_to_corner_map(&self) -> Option<&[u32]> {
107        None
108    }
109    /// Returns a vertex-to-data map for mesh attribute prediction, if present.
110    fn get_vertex_to_data_map(&self) -> Option<&[i32]> {
111        None
112    }
113    /// Returns the portable (quantized) form of an attribute, once the encoder
114    /// has transformed it. Prediction schemes that read a parent attribute --
115    /// tex coords and geometric normals both predict from the position -- must
116    /// use this and not the original floats, because the decoder only ever has
117    /// the portable values to predict from. Counterpart of C++
118    /// `PointCloudEncoder::GetPortableAttribute`.
119    fn get_portable_attribute(
120        &self,
121        _att_id: i32,
122    ) -> Option<&crate::geometry_attribute::PointAttribute> {
123        None
124    }
125}
126
127/// Encoder for Draco point cloud bitstreams.
128///
129/// A `PointCloudEncoder` takes a [`PointCloud`] plus [`EncoderOptions`] and
130/// writes a `.drc` bitstream into an [`EncoderBuffer`]. Depending on the options it uses
131/// either KD-tree or sequential attribute encoding, matching C++ Draco's
132/// `PointCloudEncoder` selection.
133///
134/// # Examples
135///
136/// ```
137/// use draco_core::{
138///     DataType, DecoderBuffer, EncoderBuffer, EncoderOptions, GeometryAttributeType,
139///     PointAttribute, PointCloud, PointCloudDecoder, PointCloudEncoder,
140/// };
141///
142/// // Three points with float32 positions.
143/// let mut pc = PointCloud::new();
144/// let mut position = PointAttribute::new();
145/// position.init(GeometryAttributeType::Position, 3, DataType::Float32, false, 3);
146/// let coords: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
147/// for (i, value) in coords.iter().enumerate() {
148///     position.buffer_mut().write(i * 4, &value.to_le_bytes());
149/// }
150/// pc.add_attribute(position);
151///
152/// // Encode, then decode it back.
153/// let mut encoder = PointCloudEncoder::new();
154/// encoder.set_point_cloud(pc);
155/// let mut buffer = EncoderBuffer::new();
156/// encoder.encode(&EncoderOptions::new(), &mut buffer)?;
157///
158/// let mut decoded = PointCloud::new();
159/// PointCloudDecoder::new().decode(&mut DecoderBuffer::new(buffer.data()), &mut decoded)?;
160/// assert_eq!(decoded.num_points(), 3);
161/// # Ok::<(), draco_core::DracoError>(())
162/// ```
163pub struct PointCloudEncoder {
164    point_cloud: Option<PointCloud>,
165    options: EncoderOptions,
166}
167
168impl GeometryEncoder for PointCloudEncoder {
169    fn point_cloud(&self) -> Option<&PointCloud> {
170        self.point_cloud.as_ref()
171    }
172
173    fn mesh(&self) -> Option<&Mesh> {
174        None
175    }
176
177    fn corner_table(&self) -> Option<&CornerTable> {
178        None
179    }
180
181    fn options(&self) -> &EncoderOptions {
182        &self.options
183    }
184
185    fn get_geometry_type(&self) -> EncodedGeometryType {
186        EncodedGeometryType::PointCloud
187    }
188}
189
190impl Default for PointCloudEncoder {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196impl PointCloudEncoder {
197    /// Creates an encoder without an assigned point cloud.
198    pub fn new() -> Self {
199        Self {
200            point_cloud: None,
201            options: EncoderOptions::default(),
202        }
203    }
204
205    /// Returns the point cloud assigned to this encoder, if any.
206    pub fn point_cloud(&self) -> Option<&PointCloud> {
207        self.point_cloud.as_ref()
208    }
209
210    /// Assigns the point cloud to encode.
211    pub fn set_point_cloud(&mut self, pc: PointCloud) {
212        self.point_cloud = Some(pc);
213    }
214
215    /// Encodes the assigned point cloud into an output buffer.
216    ///
217    /// A point cloud must have been provided with
218    /// [`set_point_cloud`](PointCloudEncoder::set_point_cloud) first.
219    ///
220    /// # Errors
221    ///
222    /// Returns an error if no point cloud was set, the options are
223    /// unsupported, or attribute encoding fails.
224    pub fn encode(&mut self, options: &EncoderOptions, out_buffer: &mut EncoderBuffer) -> Status {
225        self.options = options.clone();
226
227        if self.point_cloud.is_none() {
228            return Err(DracoError::DracoError("Point cloud not set".to_string()));
229        }
230        let pc = self.point_cloud.as_ref().unwrap();
231
232        let method = select_encoding_method(pc, &self.options)?;
233
234        // 1. Encode Header
235        self.encode_header(out_buffer, method)?;
236        self.encode_metadata(out_buffer)?;
237
238        if method == 1 {
239            // KD-Tree Encoding (Draco v2.3)
240
241            // Encode Geometry Data (Num points)
242            // Note: Draco point cloud encodes num_points as fixed u32 for both
243            // sequential and KD-tree, NOT as varint (matching decoder).
244            out_buffer.encode_u32(pc.num_points() as u32);
245
246            // No attributes, no encoder. Upstream calls
247            // GenerateAttributesEncoder once per attribute, so a cloud without
248            // any never creates one and writes a count of zero. Building one
249            // regardless would seed it with attribute id 0 and index a point
250            // cloud that has none.
251            if pc.num_attributes() == 0 {
252                out_buffer.encode_u8(0);
253                return Ok(());
254            }
255
256            // Generate Attributes Encoders
257            // For now, we put all attributes into a single KdTreeAttributesEncoder
258            let mut att_encoder = KdTreeAttributesEncoder::new(0);
259            for i in 1..pc.num_attributes() {
260                att_encoder.add_attribute_id(i);
261            }
262
263            // Encode number of attribute encoders
264            out_buffer.encode_u8(1); // We have only 1 encoder
265
266            // Init (Transform attributes to portable format)
267            if !att_encoder.transform_attributes_to_portable_format(pc, &self.options) {
268                return Err(DracoError::DracoError(
269                    "Failed to transform attributes".to_string(),
270                ));
271            }
272
273            // Note: KD-tree encoding does NOT write an encoder type identifier byte.
274            // This is different from sequential encoding where each attribute has a decoder type.
275            // The decoder knows to use KdTreeAttributesDecoder because the encoding method
276            // in the header is 1 (KD-tree).
277
278            // Encode Attributes Encoder Data (Metadata)
279            if !att_encoder.encode_attributes_encoder_data(pc, out_buffer) {
280                return Err(DracoError::DracoError(
281                    "Failed to encode attribute metadata".to_string(),
282                ));
283            }
284
285            // Encode Attributes (Portable Data)
286            if !att_encoder.encode_attributes(pc, &self.options, out_buffer) {
287                return Err(DracoError::DracoError(
288                    "Failed to encode attributes".to_string(),
289                ));
290            }
291
292            // Encode Attributes Transform Data
293            if !att_encoder.encode_data_needed_by_portable_transforms(out_buffer) {
294                return Err(DracoError::DracoError(
295                    "Failed to encode attribute transform data".to_string(),
296                ));
297            }
298        } else {
299            // Sequential Encoding (Draco v1.3)
300            //
301            // C++ Structure:
302            // 1. num_points (u32)
303            // 2. num_attribute_encoders (u8)
304            // 3. For each encoder: encoder_identifier (none for sequential - skipped in v1.3)
305            // 4. For each encoder: EncodeAttributesEncoderData
306            //    - num_attributes_in_encoder (varint for v2+, u32 for v1.x)
307            //    - for each attribute: type, data_type, num_components, normalized, unique_id
308            // 5. For each attribute: decoder_type (u8)
309            // 6. For each attribute: encoded data
310
311            let num_points = pc.num_points();
312            let num_attributes = pc.num_attributes();
313            let point_ids: Vec<PointIndex> =
314                (0..num_points).map(|i| PointIndex(i as u32)).collect();
315
316            // Draco bitstream < 2.0 encodes number of points as a fixed u32.
317            out_buffer.encode_u32(num_points as u32);
318
319            // Number of attribute encoders
320            // For empty point clouds (0 attributes), we write 0 encoders
321            if num_attributes == 0 {
322                out_buffer.encode_u8(0);
323                return Ok(());
324            }
325
326            // For non-empty point clouds, use 1 encoder for all attributes
327            out_buffer.encode_u8(1);
328
329            // Encode attributes encoder data:
330            // Use the buffer's version (set in encode_header) for version checks
331            let major = out_buffer.version_major();
332            let minor = out_buffer.version_minor();
333            if !uses_varint_encoding(major, minor) {
334                out_buffer.encode_u32(num_attributes as u32);
335            } else {
336                out_buffer.encode_varint(num_attributes as u64);
337            }
338
339            // For each attribute, encode metadata
340            for i in 0..num_attributes {
341                let att = pc.attribute(i);
342                out_buffer.encode_u8(att.attribute_type() as u8);
343                out_buffer.encode_u8(att.data_type() as u8);
344                out_buffer.encode_u8(att.num_components());
345                out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
346
347                if !uses_varint_unique_id(major, minor) {
348                    out_buffer.encode_u16(att.unique_id() as u16);
349                } else {
350                    out_buffer.encode_varint(att.unique_id() as u64);
351                }
352            }
353
354            // One identifier byte per attribute, naming the encoder that writes
355            // it. Picked once here and dispatched on below, so the byte cannot
356            // disagree with the encoder that actually runs.
357            let encoder_types: Vec<SequentialAttributeEncoderType> = (0..num_attributes)
358                .map(|i| {
359                    let quantization_bits =
360                        self.options.get_attribute_int(i, "quantization_bits", -1);
361                    select_sequential_encoder(pc.attribute(i), quantization_bits)
362                })
363                .collect();
364            for &encoder_type in &encoder_types {
365                out_buffer.encode_u8(encoder_type as u8);
366            }
367
368            // Encoding follows C++ order:
369            // 1. EncodePortableAttributes (encode_values for each attribute)
370            // 2. EncodeDataNeededByPortableTransforms (transform params for each attribute)
371
372            // Store encoders so we can call encode_data_needed_by_portable_transform later
373            let mut integer_encoders: Vec<Option<SequentialIntegerAttributeEncoder>> =
374                Vec::with_capacity(num_attributes as usize);
375            let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> =
376                Vec::with_capacity(num_attributes as usize);
377
378            // First pass: encode all values
379            for i in 0..num_attributes {
380                let att = pc.attribute(i);
381
382                match encoder_types[i as usize] {
383                    SequentialAttributeEncoderType::Normals => {
384                        let mut att_encoder = SequentialNormalAttributeEncoder::new();
385                        if !att_encoder.init(pc, i, &self.options) {
386                            return Err(DracoError::DracoError(format!(
387                                "Failed to init normal attribute encoder {}",
388                                i
389                            )));
390                        }
391
392                        if !att_encoder.encode_values(
393                            pc,
394                            &point_ids,
395                            out_buffer,
396                            &self.options,
397                            self,
398                        ) {
399                            return Err(DracoError::DracoError(format!(
400                                "Failed to encode attribute {}",
401                                i
402                            )));
403                        }
404
405                        integer_encoders.push(None);
406                        normal_encoders.push(Some(att_encoder));
407                        continue;
408                    }
409                    SequentialAttributeEncoderType::Quantization
410                    | SequentialAttributeEncoderType::Integer => {
411                        let mut att_encoder = SequentialIntegerAttributeEncoder::new();
412                        att_encoder.init(i);
413
414                        if !att_encoder.encode_values(
415                            pc,
416                            &point_ids,
417                            out_buffer,
418                            &self.options,
419                            self,
420                            None,
421                            false,
422                        ) {
423                            return Err(DracoError::DracoError(format!(
424                                "Failed to encode attribute {}",
425                                i
426                            )));
427                        }
428
429                        integer_encoders.push(Some(att_encoder));
430                    }
431                    SequentialAttributeEncoderType::Generic => {
432                        let entry_size = att.byte_stride() as usize;
433                        let data = att.buffer().data();
434                        for &point_id in &point_ids {
435                            let value_index = att.mapped_index(point_id).0 as usize;
436                            let offset = value_index.checked_mul(entry_size).ok_or_else(|| {
437                                DracoError::DracoError(
438                                    "Point cloud raw attribute offset overflow".to_string(),
439                                )
440                            })?;
441                            let end = offset.checked_add(entry_size).ok_or_else(|| {
442                                DracoError::DracoError(
443                                    "Point cloud raw attribute byte range overflow".to_string(),
444                                )
445                            })?;
446                            if end > data.len() {
447                                return Err(DracoError::DracoError(
448                                    "Point cloud raw attribute data out of bounds".to_string(),
449                                ));
450                            }
451                            out_buffer.encode_data(&data[offset..end]);
452                        }
453
454                        integer_encoders.push(None);
455                    }
456                }
457
458                normal_encoders.push(None);
459            }
460
461            // Second pass: encode transform parameters (EncodeDataNeededByPortableTransforms)
462            for i in 0..num_attributes as usize {
463                if encoder_types[i] == SequentialAttributeEncoderType::Normals {
464                    if let Some(ref att_encoder) = normal_encoders[i] {
465                        let (major, minor) = self.options.get_version();
466                        let bitstream_version = crate::version::bitstream_version(major, minor);
467                        if bitstream_version != 0 && bitstream_version < 0x0102 {
468                            continue;
469                        }
470                        if !att_encoder.encode_data_needed_by_portable_transform(out_buffer) {
471                            return Err(DracoError::DracoError(format!(
472                                "Failed to encode normal attribute transform data {}",
473                                i
474                            )));
475                        }
476                    }
477                } else if let Some(ref att_encoder) = integer_encoders[i] {
478                    if !att_encoder.encode_data_needed_by_portable_transform(out_buffer) {
479                        return Err(DracoError::DracoError(format!(
480                            "Failed to encode quantization transform data {}",
481                            i
482                        )));
483                    }
484                }
485            }
486        }
487
488        Ok(())
489    }
490
491    fn encode_metadata(&self, buffer: &mut EncoderBuffer) -> Status {
492        if let Some(metadata) = self
493            .point_cloud
494            .as_ref()
495            .and_then(|point_cloud| point_cloud.metadata())
496            .filter(|metadata| !metadata.is_empty())
497        {
498            metadata.encode(buffer)?;
499        }
500        Ok(())
501    }
502
503    fn encode_header(&self, buffer: &mut EncoderBuffer, method: i32) -> Status {
504        let (mut major, mut minor) = self.options.get_version();
505        if major == 0 && minor == 0 {
506            (major, minor) = DEFAULT_POINT_CLOUD_VERSION;
507        }
508        let has_metadata = self
509            .point_cloud
510            .as_ref()
511            .and_then(|point_cloud| point_cloud.metadata())
512            .is_some_and(|metadata| !metadata.is_empty());
513
514        if has_metadata && !has_header_flags(major, minor) {
515            return Err(DracoError::UnsupportedVersion(
516                "Metadata requires Draco bitstream version 1.3 or newer".to_string(),
517            ));
518        }
519
520        #[cfg(not(feature = "legacy_bitstream_encode"))]
521        match self.options.get_prediction_scheme() {
522            2 | 3 => {
523                return Err(DracoError::UnsupportedFeature(
524                    "legacy prediction schemes require the legacy_bitstream_encode feature"
525                        .to_string(),
526                ));
527            }
528            _ => {}
529        }
530
531        buffer.encode_data(b"DRACO");
532
533        buffer.encode_u8(major);
534        buffer.encode_u8(minor);
535        buffer.set_version(major, minor);
536
537        buffer.encode_u8(self.get_geometry_type() as u8);
538        buffer.encode_u8(method as u8);
539
540        if has_header_flags(major, minor) {
541            let flags = if has_metadata { METADATA_FLAG_MASK } else { 0 };
542            buffer.encode_u16(flags);
543        }
544        Ok(())
545    }
546
547    /// Returns the geometry type produced by this encoder.
548    pub fn get_geometry_type(&self) -> EncodedGeometryType {
549        EncodedGeometryType::PointCloud
550    }
551}