Skip to main content

draco_core/
point_cloud_encoder.rs

1use crate::compression_config::EncodedGeometryType;
2use crate::encoder_buffer::EncoderBuffer;
3use crate::encoder_options::EncoderOptions;
4use crate::geometry_attribute::GeometryAttributeType;
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_integer_attribute_encoder::SequentialIntegerAttributeEncoder;
11use crate::sequential_normal_attribute_encoder::SequentialNormalAttributeEncoder;
12use crate::status::{DracoError, Status};
13use crate::version::{
14    has_header_flags, uses_varint_encoding, uses_varint_unique_id,
15    DEFAULT_POINT_CLOUD_KD_TREE_VERSION, DEFAULT_POINT_CLOUD_SEQUENTIAL_VERSION,
16};
17
18use crate::corner_table::CornerTable;
19
20/// Geometry context used by attribute encoders and prediction selection.
21pub trait GeometryEncoder {
22    /// Returns point-cloud geometry when available.
23    fn point_cloud(&self) -> Option<&PointCloud>;
24    /// Returns mesh geometry when available.
25    fn mesh(&self) -> Option<&Mesh>;
26    /// Returns mesh corner-table topology when available.
27    fn corner_table(&self) -> Option<&CornerTable>;
28    /// Returns the active encoder options.
29    fn options(&self) -> &EncoderOptions;
30    /// Returns the encoded geometry type.
31    fn get_geometry_type(&self) -> EncodedGeometryType;
32    /// Returns the forced encoding method, if one is active.
33    fn get_encoding_method(&self) -> Option<i32> {
34        None
35    }
36    /// Returns a data-to-corner map for mesh attribute prediction, if present.
37    fn get_data_to_corner_map(&self) -> Option<&[u32]> {
38        None
39    }
40    /// Returns a vertex-to-data map for mesh attribute prediction, if present.
41    fn get_vertex_to_data_map(&self) -> Option<&[i32]> {
42        None
43    }
44}
45
46/// Encoder for Draco point cloud bitstreams.
47///
48/// A `PointCloudEncoder` takes a [`PointCloud`] plus [`EncoderOptions`] and
49/// writes a `.drc` bitstream into an [`EncoderBuffer`]. Depending on the options it uses
50/// either KD-tree or sequential attribute encoding, matching C++ Draco's
51/// `PointCloudEncoder` selection.
52///
53/// # Examples
54///
55/// ```
56/// use draco_core::{
57///     DataType, DecoderBuffer, EncoderBuffer, EncoderOptions, GeometryAttributeType,
58///     PointAttribute, PointCloud, PointCloudDecoder, PointCloudEncoder,
59/// };
60///
61/// // Three points with float32 positions.
62/// let mut pc = PointCloud::new();
63/// let mut position = PointAttribute::new();
64/// position.init(GeometryAttributeType::Position, 3, DataType::Float32, false, 3);
65/// let coords: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
66/// for (i, value) in coords.iter().enumerate() {
67///     position.buffer_mut().write(i * 4, &value.to_le_bytes());
68/// }
69/// pc.add_attribute(position);
70///
71/// // Encode, then decode it back.
72/// let mut encoder = PointCloudEncoder::new();
73/// encoder.set_point_cloud(pc);
74/// let mut buffer = EncoderBuffer::new();
75/// encoder.encode(&EncoderOptions::new(), &mut buffer)?;
76///
77/// let mut decoded = PointCloud::new();
78/// PointCloudDecoder::new().decode(&mut DecoderBuffer::new(buffer.data()), &mut decoded)?;
79/// assert_eq!(decoded.num_points(), 3);
80/// # Ok::<(), draco_core::DracoError>(())
81/// ```
82pub struct PointCloudEncoder {
83    point_cloud: Option<PointCloud>,
84    options: EncoderOptions,
85}
86
87impl GeometryEncoder for PointCloudEncoder {
88    fn point_cloud(&self) -> Option<&PointCloud> {
89        self.point_cloud.as_ref()
90    }
91
92    fn mesh(&self) -> Option<&Mesh> {
93        None
94    }
95
96    fn corner_table(&self) -> Option<&CornerTable> {
97        None
98    }
99
100    fn options(&self) -> &EncoderOptions {
101        &self.options
102    }
103
104    fn get_geometry_type(&self) -> EncodedGeometryType {
105        EncodedGeometryType::PointCloud
106    }
107}
108
109impl Default for PointCloudEncoder {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115impl PointCloudEncoder {
116    /// Creates an encoder without an assigned point cloud.
117    pub fn new() -> Self {
118        Self {
119            point_cloud: None,
120            options: EncoderOptions::default(),
121        }
122    }
123
124    /// Returns the point cloud assigned to this encoder, if any.
125    pub fn point_cloud(&self) -> Option<&PointCloud> {
126        self.point_cloud.as_ref()
127    }
128
129    /// Assigns the point cloud to encode.
130    pub fn set_point_cloud(&mut self, pc: PointCloud) {
131        self.point_cloud = Some(pc);
132    }
133
134    /// Encodes the assigned point cloud into an output buffer.
135    ///
136    /// A point cloud must have been provided with
137    /// [`set_point_cloud`](PointCloudEncoder::set_point_cloud) first.
138    ///
139    /// # Errors
140    ///
141    /// Returns an error if no point cloud was set, the options are
142    /// unsupported, or attribute encoding fails.
143    pub fn encode(&mut self, options: &EncoderOptions, out_buffer: &mut EncoderBuffer) -> Status {
144        self.options = options.clone();
145
146        if self.point_cloud.is_none() {
147            return Err(DracoError::DracoError("Point cloud not set".to_string()));
148        }
149        let pc = self.point_cloud.as_ref().unwrap();
150
151        let method = self.options.get_encoding_method().unwrap_or(0);
152
153        // 1. Encode Header
154        self.encode_header(out_buffer, method)?;
155        self.encode_metadata(out_buffer)?;
156
157        if method == 1 {
158            // KD-Tree Encoding (Draco v2.3)
159
160            // Encode Geometry Data (Num points)
161            // Note: Draco point cloud encodes num_points as fixed u32 for both
162            // sequential and KD-tree, NOT as varint (matching decoder).
163            out_buffer.encode_u32(pc.num_points() as u32);
164
165            // Generate Attributes Encoders
166            // For now, we put all attributes into a single KdTreeAttributesEncoder
167            let mut att_encoder = KdTreeAttributesEncoder::new(0);
168            for i in 1..pc.num_attributes() {
169                att_encoder.add_attribute_id(i);
170            }
171
172            // Encode number of attribute encoders
173            out_buffer.encode_u8(1); // We have only 1 encoder
174
175            // Init (Transform attributes to portable format)
176            if !att_encoder.transform_attributes_to_portable_format(pc, &self.options) {
177                return Err(DracoError::DracoError(
178                    "Failed to transform attributes".to_string(),
179                ));
180            }
181
182            // Note: KD-tree encoding does NOT write an encoder type identifier byte.
183            // This is different from sequential encoding where each attribute has a decoder type.
184            // The decoder knows to use KdTreeAttributesDecoder because the encoding method
185            // in the header is 1 (KD-tree).
186
187            // Encode Attributes Encoder Data (Metadata)
188            if !att_encoder.encode_attributes_encoder_data(pc, out_buffer) {
189                return Err(DracoError::DracoError(
190                    "Failed to encode attribute metadata".to_string(),
191                ));
192            }
193
194            // Encode Attributes (Portable Data)
195            if !att_encoder.encode_attributes(pc, &self.options, out_buffer) {
196                return Err(DracoError::DracoError(
197                    "Failed to encode attributes".to_string(),
198                ));
199            }
200
201            // Encode Attributes Transform Data
202            if !att_encoder.encode_data_needed_by_portable_transforms(out_buffer) {
203                return Err(DracoError::DracoError(
204                    "Failed to encode attribute transform data".to_string(),
205                ));
206            }
207        } else {
208            // Sequential Encoding (Draco v1.3)
209            //
210            // C++ Structure:
211            // 1. num_points (u32)
212            // 2. num_attribute_encoders (u8)
213            // 3. For each encoder: encoder_identifier (none for sequential - skipped in v1.3)
214            // 4. For each encoder: EncodeAttributesEncoderData
215            //    - num_attributes_in_encoder (varint for v2+, u32 for v1.x)
216            //    - for each attribute: type, data_type, num_components, normalized, unique_id
217            // 5. For each attribute: decoder_type (u8)
218            // 6. For each attribute: encoded data
219
220            let num_points = pc.num_points();
221            let num_attributes = pc.num_attributes();
222            let point_ids: Vec<PointIndex> =
223                (0..num_points).map(|i| PointIndex(i as u32)).collect();
224
225            // Draco bitstream < 2.0 encodes number of points as a fixed u32.
226            out_buffer.encode_u32(num_points as u32);
227
228            // Number of attribute encoders
229            // For empty point clouds (0 attributes), we write 0 encoders
230            if num_attributes == 0 {
231                out_buffer.encode_u8(0);
232                return Ok(());
233            }
234
235            // For non-empty point clouds, use 1 encoder for all attributes
236            out_buffer.encode_u8(1);
237
238            // Encode attributes encoder data:
239            // Use the buffer's version (set in encode_header) for version checks
240            let major = out_buffer.version_major();
241            let minor = out_buffer.version_minor();
242            if !uses_varint_encoding(major, minor) {
243                out_buffer.encode_u32(num_attributes as u32);
244            } else {
245                out_buffer.encode_varint(num_attributes as u64);
246            }
247
248            // For each attribute, encode metadata
249            for i in 0..num_attributes {
250                let att = pc.attribute(i);
251                out_buffer.encode_u8(att.attribute_type() as u8);
252                out_buffer.encode_u8(att.data_type() as u8);
253                out_buffer.encode_u8(att.num_components());
254                out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
255
256                if !uses_varint_unique_id(major, minor) {
257                    out_buffer.encode_u16(att.unique_id() as u16);
258                } else {
259                    out_buffer.encode_varint(att.unique_id() as u64);
260                }
261            }
262
263            // Encode decoder types for each attribute
264            // 0 = SEQUENTIAL_ATTRIBUTE_ENCODER_GENERIC
265            // 1 = SEQUENTIAL_ATTRIBUTE_ENCODER_INTEGER
266            // 2 = SEQUENTIAL_ATTRIBUTE_ENCODER_QUANTIZATION
267            // 3 = SEQUENTIAL_ATTRIBUTE_ENCODER_NORMALS
268            for i in 0..num_attributes {
269                let att = pc.attribute(i);
270                if att.attribute_type() == GeometryAttributeType::Normal {
271                    out_buffer.encode_u8(3); // NORMALS
272                } else {
273                    // Use QUANTIZATION if quantization is requested, otherwise GENERIC
274                    let quant_bits = self.options.get_attribute_int(i, "quantization_bits", 0);
275                    if quant_bits > 0 {
276                        out_buffer.encode_u8(2); // QUANTIZATION
277                    } else {
278                        out_buffer.encode_u8(0); // GENERIC
279                    }
280                }
281            }
282
283            // Encoding follows C++ order:
284            // 1. EncodePortableAttributes (encode_values for each attribute)
285            // 2. EncodeDataNeededByPortableTransforms (transform params for each attribute)
286
287            // Store encoders so we can call encode_data_needed_by_portable_transform later
288            let mut integer_encoders: Vec<Option<SequentialIntegerAttributeEncoder>> =
289                Vec::with_capacity(num_attributes as usize);
290            let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> =
291                Vec::with_capacity(num_attributes as usize);
292
293            // First pass: encode all values
294            for i in 0..num_attributes {
295                let att = pc.attribute(i);
296
297                if att.attribute_type() == GeometryAttributeType::Normal {
298                    let mut att_encoder = SequentialNormalAttributeEncoder::new();
299                    if !att_encoder.init(pc, i, &self.options) {
300                        return Err(DracoError::DracoError(format!(
301                            "Failed to init normal attribute encoder {}",
302                            i
303                        )));
304                    }
305
306                    if !att_encoder.encode_values(pc, &point_ids, out_buffer, &self.options, self) {
307                        return Err(DracoError::DracoError(format!(
308                            "Failed to encode attribute {}",
309                            i
310                        )));
311                    }
312
313                    integer_encoders.push(None);
314                    normal_encoders.push(Some(att_encoder));
315                } else {
316                    let quant_bits = self.options.get_attribute_int(i, "quantization_bits", 0);
317                    let uses_quantization = quant_bits > 0
318                        && (att.data_type() == crate::draco_types::DataType::Float32
319                            || att.data_type() == crate::draco_types::DataType::Float64);
320
321                    if uses_quantization {
322                        let mut att_encoder = SequentialIntegerAttributeEncoder::new();
323                        att_encoder.init(i);
324
325                        if !att_encoder.encode_values(
326                            pc,
327                            &point_ids,
328                            out_buffer,
329                            &self.options,
330                            self,
331                            None,
332                            false,
333                        ) {
334                            return Err(DracoError::DracoError(format!(
335                                "Failed to encode attribute {}",
336                                i
337                            )));
338                        }
339
340                        integer_encoders.push(Some(att_encoder));
341                    } else {
342                        let entry_size = att.byte_stride() as usize;
343                        let data = att.buffer().data();
344                        for &point_id in &point_ids {
345                            let value_index = att.mapped_index(point_id).0 as usize;
346                            let offset = value_index.checked_mul(entry_size).ok_or_else(|| {
347                                DracoError::DracoError(
348                                    "Point cloud raw attribute offset overflow".to_string(),
349                                )
350                            })?;
351                            let end = offset.checked_add(entry_size).ok_or_else(|| {
352                                DracoError::DracoError(
353                                    "Point cloud raw attribute byte range overflow".to_string(),
354                                )
355                            })?;
356                            if end > data.len() {
357                                return Err(DracoError::DracoError(
358                                    "Point cloud raw attribute data out of bounds".to_string(),
359                                ));
360                            }
361                            out_buffer.encode_data(&data[offset..end]);
362                        }
363
364                        integer_encoders.push(None);
365                    }
366
367                    normal_encoders.push(None);
368                }
369            }
370
371            // Second pass: encode transform parameters (EncodeDataNeededByPortableTransforms)
372            for i in 0..num_attributes as usize {
373                let att = pc.attribute(i as i32);
374
375                if att.attribute_type() == GeometryAttributeType::Normal {
376                    if let Some(ref att_encoder) = normal_encoders[i] {
377                        let (major, minor) = self.options.get_version();
378                        let bitstream_version = crate::version::bitstream_version(major, minor);
379                        if bitstream_version != 0 && bitstream_version < 0x0102 {
380                            continue;
381                        }
382                        if !att_encoder.encode_data_needed_by_portable_transform(out_buffer) {
383                            return Err(DracoError::DracoError(format!(
384                                "Failed to encode normal attribute transform data {}",
385                                i
386                            )));
387                        }
388                    }
389                } else if let Some(ref att_encoder) = integer_encoders[i] {
390                    if !att_encoder.encode_data_needed_by_portable_transform(out_buffer) {
391                        return Err(DracoError::DracoError(format!(
392                            "Failed to encode quantization transform data {}",
393                            i
394                        )));
395                    }
396                }
397            }
398        }
399
400        Ok(())
401    }
402
403    fn encode_metadata(&self, buffer: &mut EncoderBuffer) -> Status {
404        if let Some(metadata) = self
405            .point_cloud
406            .as_ref()
407            .and_then(|point_cloud| point_cloud.metadata())
408            .filter(|metadata| !metadata.is_empty())
409        {
410            metadata.encode(buffer)?;
411        }
412        Ok(())
413    }
414
415    fn encode_header(&self, buffer: &mut EncoderBuffer, method: i32) -> Status {
416        let (mut major, mut minor) = self.options.get_version();
417        if major == 0 && minor == 0 {
418            if method == 1 {
419                (major, minor) = DEFAULT_POINT_CLOUD_KD_TREE_VERSION;
420            } else {
421                (major, minor) = DEFAULT_POINT_CLOUD_SEQUENTIAL_VERSION;
422            }
423        }
424        let has_metadata = self
425            .point_cloud
426            .as_ref()
427            .and_then(|point_cloud| point_cloud.metadata())
428            .is_some_and(|metadata| !metadata.is_empty());
429
430        if has_metadata && !has_header_flags(major, minor) {
431            return Err(DracoError::UnsupportedVersion(
432                "Metadata requires Draco bitstream version 1.3 or newer".to_string(),
433            ));
434        }
435
436        #[cfg(not(feature = "legacy_bitstream_encode"))]
437        match self.options.get_prediction_scheme() {
438            2 | 3 => {
439                return Err(DracoError::UnsupportedFeature(
440                    "legacy prediction schemes require the legacy_bitstream_encode feature"
441                        .to_string(),
442                ));
443            }
444            _ => {}
445        }
446
447        buffer.encode_data(b"DRACO");
448
449        buffer.encode_u8(major);
450        buffer.encode_u8(minor);
451        buffer.set_version(major, minor);
452
453        buffer.encode_u8(self.get_geometry_type() as u8);
454        buffer.encode_u8(method as u8);
455
456        if has_header_flags(major, minor) {
457            let flags = if has_metadata { METADATA_FLAG_MASK } else { 0 };
458            buffer.encode_u16(flags);
459        }
460        Ok(())
461    }
462
463    /// Returns the geometry type produced by this encoder.
464    pub fn get_geometry_type(&self) -> EncodedGeometryType {
465        EncodedGeometryType::PointCloud
466    }
467}