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}