draco_core/mesh_encoder.rs
1use crate::attribute_quantization_transform::AttributeQuantizationTransform;
2use crate::attribute_transform::AttributeTransform;
3use crate::compression_config::EncodedGeometryType;
4use crate::compression_config::MeshEncodingMethod;
5use crate::corner_table::CornerTable;
6use crate::draco_types::DataType;
7use crate::encoder_buffer::EncoderBuffer;
8use crate::encoder_options::EncoderOptions;
9use crate::geometry_attribute::{GeometryAttributeType, PointAttribute};
10use crate::geometry_indices::{FaceIndex, PointIndex, INVALID_ATTRIBUTE_VALUE_INDEX};
11use crate::mesh::Mesh;
12use crate::mesh_edgebreaker_encoder::{
13 select_edgebreaker_traversal, EdgebreakerAttributeConnectivity, EdgebreakerTraversal,
14 MeshEdgebreakerEncoder,
15};
16use crate::metadata::METADATA_FLAG_MASK;
17use crate::point_cloud::PointCloud;
18use crate::point_cloud_encoder::GeometryEncoder;
19use crate::prediction_scheme::{
20 EntryToPointIdMap, PredictionSchemeMethod, PredictionSchemeTransformType,
21};
22use crate::sequential_attribute_encoder::{
23 select_sequential_encoder, SequentialAttributeEncoder, SequentialAttributeEncoderType,
24};
25use crate::sequential_integer_attribute_encoder::SequentialIntegerAttributeEncoder;
26use crate::sequential_normal_attribute_encoder::SequentialNormalAttributeEncoder;
27use crate::status::{DracoError, Status};
28use crate::version::{
29 has_header_flags, uses_varint_encoding, uses_varint_unique_id, DEFAULT_MESH_VERSION,
30};
31
32/// Picks EdgeBreaker or sequential connectivity, as C++ `ExpertEncoder` does.
33///
34/// Shared by `encode_header` and by the version validation that runs before it,
35/// so the version a stream is checked against is the one it is written with.
36/// The two used to derive it separately, which is how a check can pass for a
37/// coder the encoder then does not use.
38fn select_mesh_encoding_method(options: &EncoderOptions) -> i32 {
39 // C++ default: EdgeBreaker unless speed is 10, which asks for sequential.
40 match options.get_global_int("encoding_method", -1) {
41 -1 if options.get_speed() == 10 => 0,
42 -1 => 1,
43 1 => 1,
44 _ => 0,
45 }
46}
47
48/// `(min, max)` per-component position bounds, each present when computable.
49type PositionBounds = (Option<Vec<f64>>, Option<Vec<f64>>);
50
51/// Encoder for Draco triangle mesh bitstreams.
52///
53/// A `MeshEncoder` takes a [`Mesh`] plus [`EncoderOptions`] and writes a
54/// self-contained `.drc` bitstream (header, optional metadata, connectivity,
55/// and attributes) into an [`EncoderBuffer`]. The encoding method (EdgeBreaker or
56/// sequential), prediction schemes, and quantization are selected from the
57/// options, mirroring the C++ `MeshEncoder`/`ExpertEncoder` configuration.
58///
59/// [`encode`](MeshEncoder::encode) produces the bitstream and nothing else.
60/// A caller who also wants per-attribute and per-face details of what the
61/// encode did uses [`encode_with_info`](MeshEncoder::encode_with_info), which
62/// derives them; they are not a byproduct of encoding and are not computed
63/// for callers who do not ask.
64///
65/// # Examples
66///
67/// Build a single-triangle mesh, encode it, and decode it back:
68///
69/// ```
70/// use draco_core::{
71/// DataType, DecoderBuffer, EncoderBuffer, EncoderOptions, FaceIndex,
72/// GeometryAttributeType, Mesh, MeshDecoder, MeshEncoder, PointAttribute,
73/// };
74///
75/// // One triangle with a float32 position attribute (3 vertices).
76/// let mut mesh = Mesh::new();
77/// let mut position = PointAttribute::new();
78/// position.init(GeometryAttributeType::Position, 3, DataType::Float32, false, 3);
79/// let coords: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
80/// for (i, value) in coords.iter().enumerate() {
81/// position.buffer_mut().write(i * 4, &value.to_le_bytes());
82/// }
83/// mesh.add_attribute(position);
84/// mesh.set_num_faces(1);
85/// mesh.set_face(FaceIndex(0), [0u32.into(), 1u32.into(), 2u32.into()]);
86///
87/// // Encode to a Draco bitstream.
88/// let mut encoder = MeshEncoder::new();
89/// encoder.set_mesh(mesh);
90/// let mut buffer = EncoderBuffer::new();
91/// encoder.encode(&EncoderOptions::new(), &mut buffer)?;
92///
93/// // Decode it back.
94/// let mut decoded = Mesh::new();
95/// MeshDecoder::new().decode(&mut DecoderBuffer::new(buffer.data()), &mut decoded)?;
96/// assert_eq!(decoded.num_faces(), 1);
97/// # Ok::<(), draco_core::DracoError>(())
98/// ```
99pub struct MeshEncoder {
100 mesh: Option<Mesh>,
101 options: EncoderOptions,
102 num_encoded_faces: usize,
103 corner_table: Option<CornerTable>,
104 point_ids: Vec<PointIndex>,
105 data_to_corner_map: Option<Vec<u32>>,
106 vertex_to_data_map: Option<Vec<i32>>,
107 edgebreaker_attribute_connectivity: Vec<EdgebreakerAttributeConnectivity>,
108 active_corner_table: Option<CornerTable>,
109 active_data_to_corner_map: Option<Vec<u32>>,
110 active_vertex_to_data_map: Option<Vec<i32>>,
111 /// Depth-first order for the non-position attribute groups, present only
112 /// when the position group uses a different one (speed 0).
113 #[allow(clippy::type_complexity)]
114 attribute_traversal: Option<(Vec<PointIndex>, Vec<u32>, Vec<i32>)>,
115 /// The parents: attributes other schemes predict from, each with the
116 /// portable copy it is read through. See [`ParentAttributes`].
117 parent_attributes: ParentAttributes,
118 /// Kept past `encode_edgebreaker_connectivity` for its corner order, which
119 /// an attribute with interior seams needs to walk its own corner table.
120 edgebreaker_encoder: Option<MeshEdgebreakerEncoder>,
121 method: i32,
122 /// Maps point indices to vertex indices in the corner table.
123 /// Used when position-based deduplication is enabled.
124 /// Whether we're using single connectivity (all attributes share same corner table).
125 use_single_connectivity: bool,
126 /// Prediction choices made by the attribute encoders, keyed by attribute
127 /// id. Collected as encoding runs because the encoders are built at their
128 /// use site and dropped there, and only they know what they settled on.
129 attribute_predictions: Vec<(i32, PredictionSchemeMethod, PredictionSchemeTransformType)>,
130 /// The quantization parameters each attribute was encoded with, keyed by
131 /// attribute id. Kept for the same reason as `attribute_predictions`: the
132 /// encoded-mesh-info pass runs after the attribute encoders are gone and
133 /// would otherwise recompute these, and recomputing means a second full
134 /// min/max sweep of the attribute -- a pass the reference never makes.
135 attribute_quantization: Vec<(i32, AttributeQuantizationTransform)>,
136}
137
138/// Geometry shape, encoder choices and attribute metadata produced by a
139/// successful mesh encode.
140///
141/// The encoder decides several things the caller does not state: the
142/// connectivity coder, the EdgeBreaker traversal, whether attributes share one
143/// connectivity, and a prediction scheme per attribute. Everything it resolved
144/// is reported here, so "what did this encode actually do" is answerable
145/// without re-deriving the selection rules or parsing the stream back.
146#[derive(Debug, Clone, PartialEq)]
147#[non_exhaustive]
148pub struct EncodedMeshInfo {
149 /// Numeric Draco mesh encoding method used for the output.
150 pub encoding_method: i32,
151 /// Bitstream version written, after the default was substituted for an
152 /// unset one.
153 pub bitstream_version: (u8, u8),
154 /// EdgeBreaker traversal written, or `None` for sequential connectivity.
155 pub traversal: Option<EdgebreakerTraversal>,
156 /// Speed the choices above were made at, after `encoding_speed` and
157 /// `decoding_speed` were resolved into one value.
158 pub speed: i32,
159 /// Whether every attribute shared the position's connectivity. When false,
160 /// attributes with seams were encoded against their own corner tables.
161 pub single_connectivity: bool,
162 /// Number of faces encoded into the bitstream.
163 pub num_encoded_faces: usize,
164 /// Number of points encoded into the bitstream.
165 pub num_encoded_points: usize,
166 /// Per-attribute information captured during encoding.
167 pub attributes: Vec<EncodedAttributeInfo>,
168}
169
170/// Attribute metadata produced by a successful mesh encode.
171#[derive(Debug, Clone, PartialEq)]
172#[non_exhaustive]
173pub struct EncodedAttributeInfo {
174 /// Source attribute id in the input mesh.
175 pub source_attribute_id: i32,
176 /// Semantic type of the encoded attribute.
177 pub attribute_type: GeometryAttributeType,
178 /// Scalar data type of the encoded attribute.
179 pub data_type: DataType,
180 /// Number of scalar components per encoded value.
181 pub num_components: u8,
182 /// Whether integer values are normalized.
183 pub normalized: bool,
184 /// Draco unique id assigned to the attribute.
185 pub unique_id: u32,
186 /// Number of unique values encoded for the attribute.
187 pub num_encoded_values: usize,
188 /// Per-attribute encoder the values went through, which is what decides
189 /// whether the two fields below are populated.
190 pub encoder_type: SequentialAttributeEncoderType,
191 /// Quantization bits applied, or `None` when the attribute was not
192 /// quantized. A `quantization_bits` option set on an integer or generic
193 /// attribute is ignored by the encoder and reported as `None` here.
194 pub quantization_bits: Option<i32>,
195 /// Prediction scheme and transform the encoder settled on, or `None` when
196 /// the attribute never reached the integer path. This is the resolved
197 /// choice, not the request: several schemes fall back to `Difference` when
198 /// the attribute or the mesh cannot support them.
199 pub prediction: Option<(PredictionSchemeMethod, PredictionSchemeTransformType)>,
200 /// Minimum position components when known for position attributes.
201 pub position_min: Option<Vec<f64>>,
202 /// Maximum position components when known for position attributes.
203 pub position_max: Option<Vec<f64>>,
204}
205
206/// The parents, and the copies they are read through, in one place.
207///
208/// Upstream splits this across the controller: `MarkParentAttribute` marks an
209/// attribute another scheme predicts from, `is_parent_encoder()` reads the
210/// mark when the point map is rebuilt, and `GetPortableAttribute` answers for
211/// the copy. Here the mark and the copy are one entry: [`Self::register`]
212/// takes the copy as an argument and presence in the map *is* the mark, so a
213/// parent without a registered copy is unrepresentable rather than a missed
214/// registration that answers as success. Which attributes are parents is
215/// decided by [`Self::is_prediction_parent`] and nowhere else.
216#[derive(Default)]
217struct ParentAttributes {
218 entries: Vec<(i32, PointAttribute)>,
219}
220
221impl ParentAttributes {
222 fn new() -> Self {
223 Self::default()
224 }
225
226 fn clear(&mut self) {
227 self.entries.clear();
228 }
229
230 /// Whether other schemes predict from `att`.
231 ///
232 /// Every parent-reading scheme declares a `Position` parent, and the
233 /// schemes are in play below speed 4 or when asked for by number -- the
234 /// conditions `position_is_a_prediction_parent` spells out. This is the
235 /// one derivation; the encode paths register from it and never re-derive.
236 #[cfg(feature = "encoder")]
237 fn is_prediction_parent(
238 att: &PointAttribute,
239 point_cloud: &PointCloud,
240 options: &EncoderOptions,
241 ) -> bool {
242 att.attribute_type() == GeometryAttributeType::Position
243 && position_is_a_prediction_parent(point_cloud, options)
244 }
245
246 /// Marks `att_id` a parent and gives it its portable copy, in one call.
247 ///
248 /// The copy is half of what a parent is: registering without one is not
249 /// expressible, and the copy arrives with the point map already rebuilt
250 /// into encoding order, which is what a predictor reading
251 /// `mapped_index(point_id)` needs.
252 fn register(&mut self, att_id: i32, copy: PointAttribute) {
253 match self.entries.iter_mut().find(|(id, _)| *id == att_id) {
254 Some((_, existing)) => *existing = copy,
255 None => self.entries.push((att_id, copy)),
256 }
257 }
258
259 /// The registered copy of `att_id`, when it is a parent.
260 ///
261 /// Upstream's `GetPortableAttribute` falls back to the attribute itself
262 /// when its encoder made no portable form. That fallback is not answered
263 /// here: a parent is its registered copy, and anything else is not one.
264 /// The binding sites that need the attribute itself hold it separately
265 /// and choose between the two where upstream's
266 /// `portable_attribute_ != nullptr ? portable : attribute()` sits, so the
267 /// choice is written where it is made rather than folded into the lookup.
268 fn get(&self, att_id: i32) -> Option<&PointAttribute> {
269 self.entries
270 .iter()
271 .find(|(id, _)| *id == att_id)
272 .map(|(_, att)| att)
273 }
274}
275
276impl GeometryEncoder for MeshEncoder {
277 fn point_cloud(&self) -> Option<&PointCloud> {
278 self.mesh.as_ref().map(|m| m as &PointCloud)
279 }
280
281 fn mesh(&self) -> Option<&Mesh> {
282 self.mesh.as_ref()
283 }
284
285 fn corner_table(&self) -> Option<&CornerTable> {
286 self.active_corner_table
287 .as_ref()
288 .or(self.corner_table.as_ref())
289 }
290
291 fn options(&self) -> &EncoderOptions {
292 &self.options
293 }
294
295 fn get_geometry_type(&self) -> EncodedGeometryType {
296 EncodedGeometryType::TriangularMesh
297 }
298
299 fn get_encoding_method(&self) -> Option<i32> {
300 Some(self.method)
301 }
302
303 fn get_data_to_corner_map(&self) -> Option<&[u32]> {
304 self.active_data_to_corner_map
305 .as_deref()
306 .or(self.data_to_corner_map.as_deref())
307 }
308
309 fn get_vertex_to_data_map(&self) -> Option<&[i32]> {
310 self.active_vertex_to_data_map
311 .as_deref()
312 .or(self.vertex_to_data_map.as_deref())
313 }
314
315 fn get_portable_attribute(&self, att_id: i32) -> Option<&PointAttribute> {
316 self.parent_attributes.get(att_id)
317 }
318}
319
320/// Rebuilds a portable attribute's point map, as upstream does in
321/// `SequentialIntegerAttributeEncoder::TransformAttributeToPortableFormat`.
322///
323/// The values were written in encoding order, but a prediction scheme reads its
324/// parent as `mapped_index(point_id)`; without this the lookup returns whichever
325/// vertex happens to sit at that index in the traversal, and encoder and decoder
326/// predict from different positions.
327#[cfg(feature = "encoder")]
328fn rebuild_parent_point_map(
329 attribute: &PointAttribute,
330 portable: &mut PointAttribute,
331 point_ids: &[PointIndex],
332 num_points: usize,
333) -> Status {
334 let mut value_to_value = vec![0u32; attribute.size().max(1)];
335 for (entry, &point_id) in point_ids.iter().enumerate() {
336 let src = attribute.mapped_index(point_id);
337 if (src.0 as usize) < value_to_value.len() {
338 value_to_value[src.0 as usize] = entry as u32;
339 }
340 }
341 portable.set_explicit_mapping(num_points);
342 for point in 0..num_points {
343 let src = attribute.mapped_index(PointIndex(point as u32));
344 let entry = value_to_value
345 .get(src.0 as usize)
346 .copied()
347 .unwrap_or_default();
348 portable.try_set_point_map_entry(
349 PointIndex(point as u32),
350 crate::geometry_indices::AttributeValueIndex(entry),
351 )?;
352 }
353 Ok(())
354}
355
356/// Whether a prediction scheme that reads the position as its parent will be
357/// used, so the position needs a portable form for the predictor to read.
358///
359/// Speed is upstream's own condition -- the tex-coords-portable and
360/// geometric-normal schemes are the two that declare a parent, and
361/// `SelectPredictionMethod` picks neither at speed 4 or above. An explicit
362/// `prediction_scheme` option does not go through that selection, though, so
363/// asking for one by number at any speed has to count as well: otherwise the
364/// scheme is built and the parent it reads is the original attribute, whose
365/// value order and point map are not what the decoder reconstructs.
366#[cfg(feature = "encoder")]
367fn position_is_a_prediction_parent(point_cloud: &PointCloud, options: &EncoderOptions) -> bool {
368 if options.get_speed() < 4 {
369 return true;
370 }
371 (0..point_cloud.num_attributes())
372 .any(|att_id| matches!(options.get_attribute_prediction_scheme(att_id), 3 | 5 | 6))
373}
374
375/// The portable form of an already-integral attribute: its values converted to
376/// `i32` in encoding order, which is the shape the decoder reconstructs.
377///
378/// Upstream builds one for *every* integer attribute --
379/// `SequentialIntegerAttributeEncoder::PrepareValues` calls
380/// `PreparePortableAttribute` before it looks at quantization at all -- and a
381/// prediction scheme reaching for a parent gets that, never the original. This
382/// port only built one where a quantization transform produced it, so a scheme
383/// predicting from an integer position read the original instead: its own value
384/// count and its own point map, both of which a deduplicated or seamed mesh
385/// makes different from what the decoder will hold. Encoder and decoder then
386/// predicted from different positions and disagreed about which entries carry
387/// an orientation bit, which the decoder reports as running out of them.
388#[cfg(feature = "encoder")]
389fn integral_portable_attribute(
390 attribute: &PointAttribute,
391 point_ids: &[PointIndex],
392) -> Result<PointAttribute, DracoError> {
393 let num_components = attribute.num_components();
394 let data_type = attribute.data_type();
395 let byte_stride = attribute.byte_stride() as usize;
396 let component_size = data_type.byte_length();
397
398 let mut portable = PointAttribute::default();
399 portable.try_init(
400 attribute.attribute_type(),
401 num_components,
402 crate::draco_types::DataType::Int32,
403 false,
404 point_ids.len(),
405 )?;
406
407 for (entry, &point_id) in point_ids.iter().enumerate() {
408 let src = attribute.mapped_index(point_id).0 as usize * byte_stride;
409 for component in 0..num_components as usize {
410 let value = crate::sequential_integer_attribute_encoder::read_value_as_i32(
411 attribute.buffer(),
412 src + component * component_size,
413 data_type,
414 );
415 let offset = (entry * num_components as usize + component) * 4;
416 portable.buffer_mut().write(offset, &value.to_le_bytes());
417 }
418 }
419 Ok(portable)
420}
421
422impl MeshEncoder {
423 /// Creates an encoder without an assigned mesh.
424 pub fn new() -> Self {
425 Self {
426 mesh: None,
427 options: EncoderOptions::default(),
428 num_encoded_faces: 0,
429 corner_table: None,
430 point_ids: Vec::new(),
431 data_to_corner_map: None,
432 vertex_to_data_map: None,
433 edgebreaker_attribute_connectivity: Vec::new(),
434 active_corner_table: None,
435 active_data_to_corner_map: None,
436 active_vertex_to_data_map: None,
437 attribute_traversal: None,
438 parent_attributes: ParentAttributes::new(),
439 edgebreaker_encoder: None,
440 method: 0,
441 use_single_connectivity: false,
442 attribute_predictions: Vec::new(),
443 attribute_quantization: Vec::new(),
444 }
445 }
446
447 /// Assigns the mesh to encode.
448 pub fn set_mesh(&mut self, mesh: Mesh) {
449 self.mesh = Some(mesh);
450 }
451
452 /// Drops everything the previous encode derived from its mesh.
453 ///
454 /// An encoder is reusable - `set_mesh` then `encode`, twice - and each
455 /// encode caches connectivity for the attribute stage to read back:
456 /// a corner table, a point order, corner and vertex maps, per-attribute
457 /// seam connectivity. Only some of that is rewritten by every path. The
458 /// sequential connectivity branch does not build a corner table, so after
459 /// an EdgeBreaker encode it inherited the previous mesh's one and wrote
460 /// attributes against topology the stream does not describe: encoding an
461 /// attributed mesh with EdgeBreaker and then a plain mesh sequentially
462 /// with the same encoder produced a stream this crate's own decoder
463 /// rejects. Resetting in one place is the fix that does not depend on
464 /// every future path remembering to.
465 fn reset_derived_state(&mut self) {
466 self.parent_attributes.clear();
467 self.edgebreaker_encoder = None;
468 self.num_encoded_faces = 0;
469 self.corner_table = None;
470 self.point_ids.clear();
471 self.data_to_corner_map = None;
472 self.vertex_to_data_map = None;
473 self.edgebreaker_attribute_connectivity.clear();
474 self.active_corner_table = None;
475 self.active_data_to_corner_map = None;
476 self.active_vertex_to_data_map = None;
477 self.attribute_traversal = None;
478 self.method = 0;
479 self.use_single_connectivity = false;
480 self.attribute_predictions.clear();
481 self.attribute_quantization.clear();
482 }
483
484 /// Returns the assigned mesh, if any.
485 pub fn mesh(&self) -> Option<&Mesh> {
486 self.mesh.as_ref()
487 }
488
489 /// Returns the number of faces encoded by the last successful encode.
490 pub fn num_encoded_faces(&self) -> usize {
491 self.num_encoded_faces
492 }
493
494 /// Returns the corner table built during the last mesh encode, if any.
495 pub fn corner_table(&self) -> Option<&CornerTable> {
496 self.corner_table.as_ref()
497 }
498
499 /// Encodes the assigned mesh into an output buffer.
500 ///
501 /// A mesh must have been provided with [`set_mesh`](MeshEncoder::set_mesh)
502 /// first. On success the bitstream is appended to `out_buffer` and nothing
503 /// else is computed; use
504 /// [`encode_with_info`](MeshEncoder::encode_with_info) to also get a
505 /// description of the encode.
506 ///
507 /// # Errors
508 ///
509 /// Returns an error if no mesh was set, if the requested encoding method or
510 /// options are unsupported, or if attribute encoding fails.
511 pub fn encode(&mut self, options: &EncoderOptions, out_buffer: &mut EncoderBuffer) -> Status {
512 self.options = options.clone();
513 self.reset_derived_state();
514
515 if self.mesh.is_none() {
516 return Err(DracoError::general("Mesh not set".to_string()));
517 }
518 crate::point_cloud_encoder::validate_encodable_attributes(self.mesh.as_ref().unwrap())?;
519 let (major, minor) = self.options.get_version();
520 let target = if select_mesh_encoding_method(&self.options) == 1 {
521 crate::version::EncodeTarget::MeshEdgebreaker
522 } else {
523 crate::version::EncodeTarget::MeshSequential
524 };
525 crate::version::validate_encodable_version(major, minor, target)?;
526 Self::validate_face_indices(self.mesh.as_ref().unwrap())?;
527 self.validate_predictive_traversal()?;
528 self.validate_prediction_schemes(self.mesh.as_ref().unwrap())?;
529 self.validate_attribute_versions(self.mesh.as_ref().unwrap())?;
530
531 // 1. Encode Header
532 self.encode_header(out_buffer)?;
533 self.encode_metadata(out_buffer)?;
534
535 // 2. Encode geometry data (connectivity + attributes)
536 self.encode_geometry_data(out_buffer)?;
537
538 Ok(())
539 }
540
541 /// Encodes the assigned mesh and describes what the encode did.
542 ///
543 /// The description is derived from the encode rather than produced by it,
544 /// and deriving it costs a sweep of every position for its bounds plus a
545 /// copy of the encoded point order per attribute. So it is the caller who
546 /// decides whether that work happens: [`encode`](MeshEncoder::encode) never
547 /// does it, and this does it exactly once, here, where it was asked for.
548 ///
549 /// # Errors
550 ///
551 /// The same errors as [`encode`](MeshEncoder::encode), plus a failure to
552 /// derive the description. The bitstream in `out_buffer` is complete and
553 /// valid in that last case; only the description is missing.
554 pub fn encode_with_info(
555 &mut self,
556 options: &EncoderOptions,
557 out_buffer: &mut EncoderBuffer,
558 ) -> Result<EncodedMeshInfo, DracoError> {
559 self.encode(options, out_buffer)?;
560 self.build_encoded_mesh_info()
561 }
562
563 fn encode_metadata(&self, buffer: &mut EncoderBuffer) -> Status {
564 if let Some(metadata) = self
565 .mesh
566 .as_ref()
567 .and_then(|mesh| mesh.metadata())
568 .filter(|metadata| !metadata.is_empty())
569 {
570 metadata.encode(buffer)?;
571 }
572 Ok(())
573 }
574
575 /// Rejects a tex-coord prediction scheme forced onto an attribute that is
576 /// not a texture coordinate.
577 ///
578 /// Both tex-coord predictors work on two components and predict from the
579 /// position, so the encoder builds one for any attribute that presents two
580 /// components. A normal does, once the octahedron transform has folded it
581 /// from three - so a scheme meant for UVs was accepted for normals and
582 /// wrote values the normal decoder cannot read back. Three-component
583 /// attributes were already refused, which is why only normals slipped
584 /// through.
585 fn validate_prediction_schemes(&self, mesh: &Mesh) -> Status {
586 const TEX_COORDS_DEPRECATED: i32 = 3;
587 const TEX_COORDS_PORTABLE: i32 = 5;
588
589 for att_id in 0..mesh.num_attributes() {
590 let scheme = self.options.get_attribute_prediction_scheme(att_id);
591 if !matches!(scheme, TEX_COORDS_DEPRECATED | TEX_COORDS_PORTABLE) {
592 continue;
593 }
594 let attribute_type = mesh.attribute(att_id).attribute_type();
595 if attribute_type != GeometryAttributeType::TexCoord {
596 return Err(DracoError::general(format!(
597 "Prediction scheme {scheme} predicts texture coordinates and cannot be used \
598 for attribute {att_id}, which is a {attribute_type:?}"
599 )));
600 }
601 }
602 Ok(())
603 }
604
605 /// Whether attribute `att_id` already wrote its quantization parameters
606 /// ahead of its values, so the trailing pass must not write them again.
607 ///
608 /// Asks the same function the attribute encoder asks, so the parameters are
609 /// written exactly once whichever side of the 2.0 boundary the target is.
610 fn quantization_parameters_are_inline(&self, att_id: i32) -> bool {
611 #[cfg(feature = "legacy_bitstream_encode")]
612 {
613 let Some(mesh) = self.mesh.as_ref() else {
614 return false;
615 };
616 crate::sequential_integer_attribute_encoder::uses_inline_quantization_parameters(
617 mesh.attribute(att_id),
618 &self.options,
619 att_id,
620 )
621 }
622 #[cfg(not(feature = "legacy_bitstream_encode"))]
623 {
624 let _ = att_id;
625 false
626 }
627 }
628
629 /// The per-attribute encoder this encode will build, which is what decides
630 /// whether an attribute is subject to the version gates below.
631 ///
632 /// An attribute only meets a prediction scheme or a quantization transform
633 /// if it reaches the integer path; a float attribute with no quantization
634 /// goes to the generic encoder, where a requested prediction scheme is
635 /// simply never consulted. Asking the same function the encoder asks keeps
636 /// the two from disagreeing about which attributes a refusal covers.
637 fn attribute_encoder_type(&self, mesh: &Mesh, att_id: i32) -> SequentialAttributeEncoderType {
638 let quantization_bits = self
639 .options
640 .get_attribute_int(att_id, "quantization_bits", -1);
641 select_sequential_encoder(mesh.attribute(att_id), quantization_bits)
642 }
643
644 /// Rejects attribute coding a pre-2.2 target has no layout for *in this
645 /// build*.
646 ///
647 /// Every pre-2.2 attribute layout is written behind `legacy_bitstream_encode`
648 /// -- the quantization parameters that go inline below 2.0, and the rANS
649 /// size prefixes and mode bytes the prediction schemes carry below 2.2. With
650 /// the feature on there is nothing to refuse. With it off those writes are
651 /// compiled out, so an encode that reaches them silently produces a stream
652 /// this crate's own decoder cannot read, and the refusal takes their place.
653 ///
654 /// EdgeBreaker is not covered here because `encode_header` already refuses
655 /// every pre-2.2 EdgeBreaker target when the feature is off. What is left is
656 /// the sequential mesh at 1.3, which has no such gate.
657 #[cfg(not(feature = "legacy_bitstream_encode"))]
658 fn validate_attribute_versions(&self, mesh: &Mesh) -> Status {
659 let (mut major, mut minor) = self.options.get_version();
660 if major == 0 && minor == 0 {
661 (major, minor) = DEFAULT_MESH_VERSION;
662 }
663 if !crate::version::version_less_than(major, minor, (2, 2)) {
664 return Ok(());
665 }
666
667 for att_id in 0..mesh.num_attributes() {
668 // A generic attribute is copied out raw and meets neither a
669 // transform nor a prediction scheme, so no legacy layout applies.
670 if self.attribute_encoder_type(mesh, att_id) == SequentialAttributeEncoderType::Generic
671 {
672 continue;
673 }
674 return Err(DracoError::unsupported_version(format!(
675 "Attribute {att_id} needs the pre-2.2 layout for bitstream version \
676 {major}.{minor}, which requires the legacy_bitstream_encode feature"
677 )));
678 }
679 Ok(())
680 }
681
682 /// With the legacy writer compiled in, every claimed version has a layout,
683 /// so there is nothing to refuse.
684 #[cfg(feature = "legacy_bitstream_encode")]
685 fn validate_attribute_versions(&self, _mesh: &Mesh) -> Status {
686 Ok(())
687 }
688
689 /// Rejects the legacy predictive traversal on a version that cannot carry
690 /// it.
691 ///
692 /// `force_predictive_traversal` round-trips pre-0.10.0 connectivity and
693 /// only belongs in a target version below 2.0, which the encoder's own
694 /// comment said and nothing checked. Set on a current-version encode, it
695 /// produced a type-1 traversal inside a 2.x stream - which the decoder
696 /// refuses, since 2.x connectivity has no predictive traversal to read.
697 fn validate_predictive_traversal(&self) -> Status {
698 if self.options.get_global_int("force_predictive_traversal", 0) == 0 {
699 return Ok(());
700 }
701 let (mut major, mut minor) = self.options.get_version();
702 if major == 0 && minor == 0 {
703 (major, minor) = DEFAULT_MESH_VERSION;
704 }
705 if !crate::version::version_less_than(major, minor, (2, 0)) {
706 return Err(DracoError::unsupported_feature(format!(
707 "force_predictive_traversal requires a target bitstream version below 2.0, \
708 not {major}.{minor}"
709 )));
710 }
711 Ok(())
712 }
713
714 /// Rejects a face that references a point the mesh does not have.
715 ///
716 /// The index buffer is the other half of caller-supplied geometry, and
717 /// nothing between a file and this encoder re-checks it against the point
718 /// count. Both connectivity paths then use face indices to index
719 /// point-sized arrays directly - `point_to_vertex[face[j]]` in the corner
720 /// table build is the shortest route to it - so an out-of-range index is a
721 /// panic rather than an encode failure. One pass here answers for every
722 /// such use.
723 fn validate_face_indices(mesh: &Mesh) -> Status {
724 let num_points = mesh.num_points();
725 for face_id in 0..mesh.num_faces() {
726 let face = mesh.face(FaceIndex(face_id as u32));
727 for index in face {
728 if index.0 as usize >= num_points {
729 return Err(DracoError::general(format!(
730 "Face {face_id} references point {} but the mesh has {num_points} points",
731 index.0
732 )));
733 }
734 }
735 }
736 Ok(())
737 }
738
739 fn encode_header(&self, buffer: &mut EncoderBuffer) -> Status {
740 let (mut major, mut minor) = self.options.get_version();
741 if major == 0 && minor == 0 {
742 // Default to latest mesh version
743 (major, minor) = DEFAULT_MESH_VERSION;
744 }
745 let has_metadata = self
746 .mesh
747 .as_ref()
748 .and_then(|mesh| mesh.metadata())
749 .is_some_and(|metadata| !metadata.is_empty());
750
751 if has_metadata && !has_header_flags(major, minor) {
752 return Err(DracoError::unsupported_version(
753 "Metadata requires Draco bitstream version 1.3 or newer".to_string(),
754 ));
755 }
756
757 let method = select_mesh_encoding_method(&self.options);
758
759 #[cfg(not(feature = "legacy_bitstream_encode"))]
760 if method == 1 {
761 let bitstream_version = crate::version::bitstream_version(major, minor);
762 if bitstream_version < 0x0202 {
763 return Err(DracoError::unsupported_version(
764 "EdgeBreaker mesh encoding before bitstream 2.2 requires the \
765 legacy_bitstream_encode feature"
766 .to_string(),
767 ));
768 }
769 if self.options.get_global_int("force_predictive_traversal", 0) != 0 {
770 return Err(DracoError::unsupported_feature(
771 "force_predictive_traversal requires the legacy_bitstream_encode feature"
772 .to_string(),
773 ));
774 }
775 }
776 #[cfg(not(feature = "legacy_bitstream_encode"))]
777 match self.options.get_prediction_scheme() {
778 2 | 3 => {
779 return Err(DracoError::unsupported_feature(
780 "legacy prediction schemes require the legacy_bitstream_encode feature"
781 .to_string(),
782 ));
783 }
784 _ => {}
785 }
786
787 buffer.encode_data(b"DRACO");
788
789 buffer.encode_u8(major);
790 buffer.encode_u8(minor);
791 buffer.set_version(major, minor);
792 buffer.encode_u8(self.get_geometry_type() as u8);
793 buffer.encode_u8(method as u8);
794
795 // The flags field is always present in the binary header (the decoder reads
796 // it unconditionally); only the metadata bit gains meaning at v1.3+, which
797 // is guarded by the metadata check above. Emitting it only for >= 1.3 left
798 // pre-1.3 streams two bytes short, misaligning the rest of the stream.
799 let flags = if has_metadata { METADATA_FLAG_MASK } else { 0 };
800 buffer.encode_u16(flags);
801 Ok(())
802 }
803
804 fn encode_geometry_data(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
805 // First encode connectivity
806 self.encode_connectivity(out_buffer)?;
807
808 // Check if we should store the number of encoded faces
809 if self
810 .options
811 .get_global_int("store_number_of_encoded_faces", 0)
812 != 0
813 {
814 self.compute_number_of_encoded_faces();
815 }
816
817 // Then encode attributes
818 self.encode_attributes(out_buffer)?;
819
820 Ok(())
821 }
822
823 fn encode_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
824 let mesh = self
825 .mesh
826 .as_ref()
827 .expect("mesh must be set before encoding");
828
829 // Determine encoding method FIRST (before building corner table)
830 let method_int = self.options.get_global_int("encoding_method", -1);
831 let method = if method_int == -1 {
832 if self.options.get_speed() == 10 {
833 MeshEncodingMethod::MeshSequentialEncoding
834 } else {
835 MeshEncodingMethod::MeshEdgebreakerEncoding
836 }
837 } else if method_int == 1 {
838 MeshEncodingMethod::MeshEdgebreakerEncoding
839 } else {
840 MeshEncodingMethod::MeshSequentialEncoding
841 };
842 self.method = if method == MeshEncodingMethod::MeshEdgebreakerEncoding {
843 1
844 } else {
845 0
846 };
847
848 // C++ behavior: use_single_connectivity_ when speed >= 6
849 // When false (speed < 6), use position attribute to deduplicate vertices
850 let speed = self.options.get_speed();
851 // Check if split_mesh_on_seams is explicitly set, otherwise use speed-based default
852 let split_on_seams_explicit = self.options.get_global_int("split_mesh_on_seams", -1);
853 let use_single_connectivity = if split_on_seams_explicit >= 0 {
854 split_on_seams_explicit != 0
855 } else {
856 speed >= 6
857 };
858
859 // Only build corner table if needed (not for sequential encoding)
860 if method == MeshEncodingMethod::MeshEdgebreakerEncoding {
861 let faces = if use_single_connectivity {
862 // CreateCornerTableFromAllAttributes: use point indices directly
863 let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
864 .map(|i| {
865 let face = mesh.face(FaceIndex(i as u32));
866 [
867 crate::geometry_indices::VertexIndex(face[0].0),
868 crate::geometry_indices::VertexIndex(face[1].0),
869 crate::geometry_indices::VertexIndex(face[2].0),
870 ]
871 })
872 .collect();
873 faces
874 } else {
875 // CreateCornerTableFromPositionAttribute: use position attribute to deduplicate
876 self.create_corner_table_from_position_attribute(mesh)
877 };
878
879 // Initialize corner table for the mesh
880 let mut corner_table = CornerTable::new(0);
881 corner_table.init(&faces);
882
883 // A mesh whose every face is degenerate has no connectivity to
884 // traverse: `point_ids` comes back empty, and everything downstream
885 // that assumes at least one encoded point panics rather than
886 // failing cleanly. C++ rejects the same input outright --
887 // `MeshEdgebreakerEncoderImpl::Init` checks
888 // `num_faces() == NumDegeneratedFaces()` before doing anything else.
889 if corner_table.num_faces() > 0
890 && corner_table.num_faces() == corner_table.num_degenerated_faces()
891 {
892 return Err(DracoError::general(
893 "All triangles are degenerate.".to_string(),
894 ));
895 }
896
897 self.corner_table = Some(corner_table);
898 self.edgebreaker_attribute_connectivity.clear();
899 if !use_single_connectivity {
900 if let Some(ref ct) = self.corner_table {
901 for i in 0..mesh.num_attributes() {
902 let att = mesh.attribute(i);
903 if att.attribute_type() != GeometryAttributeType::Position {
904 self.edgebreaker_attribute_connectivity
905 .push(EdgebreakerAttributeConnectivity::build(mesh, ct, i));
906 }
907 }
908 }
909 }
910 } else {
911 // Sequential encoding: no corner table needed.
912 self.edgebreaker_attribute_connectivity.clear();
913 }
914 self.use_single_connectivity = use_single_connectivity;
915
916 match method {
917 MeshEncodingMethod::MeshSequentialEncoding => {
918 self.encode_sequential_connectivity(out_buffer)
919 }
920 MeshEncodingMethod::MeshEdgebreakerEncoding => {
921 self.encode_edgebreaker_connectivity(out_buffer)
922 }
923 }
924 }
925
926 fn encode_edgebreaker_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
927 let mesh = self
928 .mesh
929 .as_ref()
930 .expect("mesh must be set before encoding");
931 let corner_table = self
932 .corner_table
933 .as_ref()
934 .expect("corner_table must be set before edgebreaker encoding");
935
936 let mut encoder = MeshEdgebreakerEncoder::new(mesh.num_faces(), mesh.num_points());
937 // Opt-in legacy predictive (type-1) traversal, for round-tripping the
938 // pre-0.10.0 connectivity. Requires a < 2.0 target version.
939 #[cfg(feature = "legacy_bitstream_encode")]
940 encoder.set_force_predictive(
941 self.options.get_global_int("force_predictive_traversal", 0) == 1,
942 );
943 let (point_ids, data_to_corner_map, vertex_to_data_map) = encoder.encode_connectivity(
944 mesh,
945 corner_table,
946 &self.edgebreaker_attribute_connectivity,
947 out_buffer,
948 self.options.get_speed() as usize,
949 self.use_single_connectivity,
950 )?;
951 #[cfg(feature = "debug_logs")]
952 {
953 debug_log!("DEBUG: encode_edgebreaker_connectivity: point_ids.len()={}, data_to_corner_map.len()={}, vertex_to_data_map.len()={}",
954 point_ids.len(), data_to_corner_map.len(), vertex_to_data_map.len());
955 }
956 // At speed 0 the position walks the mesh by max prediction degree while
957 // every other attribute stays depth first, so the two orders part ways
958 // and the non-position groups need their own. At any other speed the
959 // position order already is the depth-first one.
960 //
961 // Whether there is a non-position group is `edgebreaker_attribute_connectivity`
962 // being non-empty, not `mesh.num_attributes() > 1`: a mesh can carry a
963 // single attribute that is not Position (no separate Position attribute
964 // registered at all, connectivity coming only from the face list), and
965 // then `num_attributes()` is 1 while that one attribute still needs its
966 // own traversal. Counting attributes undercounts exactly that mesh.
967 self.attribute_traversal = if self.options.get_speed() == 0
968 && !self.edgebreaker_attribute_connectivity.is_empty()
969 {
970 Some(encoder.generate_depth_first_traversal(mesh, corner_table))
971 } else {
972 None
973 };
974
975 self.point_ids = point_ids;
976
977 // Draco stores corner mapping in attribute (data) order.
978 self.data_to_corner_map = Some(data_to_corner_map);
979 self.vertex_to_data_map = Some(vertex_to_data_map);
980
981 // Held for the corner order it carries: an attribute with interior
982 // seams walks its own corner table seeded from that order, and this is
983 // the last point at which it exists.
984 self.edgebreaker_encoder = Some(encoder);
985
986 Ok(())
987 }
988
989 /// Creates the faces array using the position attribute to deduplicate
990 /// vertices, mimicking C++ CreateCornerTableFromPositionAttribute: each
991 /// face carries the attribute value indices its points map to.
992 fn create_corner_table_from_position_attribute(
993 &self,
994 mesh: &Mesh,
995 ) -> Vec<[crate::geometry_indices::VertexIndex; 3]> {
996 use crate::geometry_attribute::GeometryAttributeType;
997
998 let pos_att_id = mesh.named_attribute_id(GeometryAttributeType::Position);
999 if pos_att_id < 0 {
1000 // No position attribute, fall back to identity mapping
1001 let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
1002 .map(|i| {
1003 let face = mesh.face(FaceIndex(i as u32));
1004 [
1005 crate::geometry_indices::VertexIndex(face[0].0),
1006 crate::geometry_indices::VertexIndex(face[1].0),
1007 crate::geometry_indices::VertexIndex(face[2].0),
1008 ]
1009 })
1010 .collect();
1011 return faces;
1012 }
1013
1014 let pos_att = mesh.attribute(pos_att_id);
1015 let _buffer = pos_att.buffer();
1016 let num_components = pos_att.num_components() as usize;
1017 let _byte_stride = match pos_att.data_type() {
1018 crate::draco_types::DataType::Float32 => num_components * 4,
1019 crate::draco_types::DataType::Float64 => num_components * 8,
1020 crate::draco_types::DataType::Int8 | crate::draco_types::DataType::Uint8 => {
1021 num_components
1022 }
1023 crate::draco_types::DataType::Int16 | crate::draco_types::DataType::Uint16 => {
1024 num_components * 2
1025 }
1026 crate::draco_types::DataType::Int32 | crate::draco_types::DataType::Uint32 => {
1027 num_components * 4
1028 }
1029 crate::draco_types::DataType::Int64 | crate::draco_types::DataType::Uint64 => {
1030 num_components * 8
1031 }
1032 _ => num_components * 4, // Default to 4 bytes per component
1033 };
1034
1035 // Use attribute mapped indices directly to build point->vertex map. This mirrors
1036 // C++ CreateCornerTableFromAttribute which uses att->mapped_index(face[j]).
1037 let mut point_to_vertex: Vec<u32> = vec![0; mesh.num_points()];
1038 for i in 0..mesh.num_points() {
1039 let pt = PointIndex(i as u32);
1040 let val_idx = pos_att.mapped_index(pt);
1041 point_to_vertex[i] = val_idx.0;
1042 }
1043
1044 // Build faces using attribute mapped indices (exact same mapping as C++).
1045 let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
1046 .map(|i| {
1047 let face = mesh.face(FaceIndex(i as u32));
1048 [
1049 crate::geometry_indices::VertexIndex(point_to_vertex[face[0].0 as usize]),
1050 crate::geometry_indices::VertexIndex(point_to_vertex[face[1].0 as usize]),
1051 crate::geometry_indices::VertexIndex(point_to_vertex[face[2].0 as usize]),
1052 ]
1053 })
1054 .collect();
1055
1056 #[cfg(feature = "debug_logs")]
1057 {
1058 debug_log!(
1059 "Rust created faces (first 12): {:?}",
1060 faces
1061 .iter()
1062 .take(12)
1063 .map(|f| [f[0].0, f[1].0, f[2].0])
1064 .collect::<Vec<_>>()
1065 );
1066 debug_log!(
1067 "Rust point_to_vertex (first 25): {:?}",
1068 point_to_vertex.iter().take(25).cloned().collect::<Vec<_>>()
1069 );
1070 }
1071 faces
1072 }
1073
1074 fn encode_sequential_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
1075 let mesh = self
1076 .mesh
1077 .as_ref()
1078 .expect("mesh must be set before encoding");
1079
1080 // Encode the number of faces and points
1081 // Use the buffer's version (set in encode_header) for version checks
1082 let major = out_buffer.version_major();
1083 let minor = out_buffer.version_minor();
1084 // 2.2, not 2.0. `uses_varint_encoding` ignores its `minor` argument and
1085 // flips at the major, while the decoder (and upstream
1086 // `MeshSequentialDecoder`) reads these as varints only from 2.2. A
1087 // sequential mesh written at 2.0 or 2.1 was therefore unreadable. Those
1088 // versions are no longer claimed for sequential meshes, so this is
1089 // upstream parity rather than a live fix - but a predicate that ignores
1090 // half its input is a trap, and this is the second bug it caused.
1091 let counts_are_varint = crate::version::version_at_least(major, minor, (2, 2));
1092 if !counts_are_varint {
1093 out_buffer.encode_u32(mesh.num_faces() as u32);
1094 out_buffer.encode_u32(mesh.num_points() as u32);
1095 } else {
1096 out_buffer.encode_varint(mesh.num_faces() as u64);
1097 out_buffer.encode_varint(mesh.num_points() as u64);
1098 }
1099
1100 if mesh.num_faces() > 0 && mesh.num_points() > 0 {
1101 out_buffer.encode_u8(1); // Raw connectivity
1102 if mesh.num_points() < 256 {
1103 for face_id in 0..mesh.num_faces() {
1104 let face = mesh.face(FaceIndex(face_id as u32));
1105 for i in 0..3 {
1106 out_buffer.encode_u8(face[i].0 as u8);
1107 }
1108 }
1109 } else if mesh.num_points() < 65536 {
1110 for face_id in 0..mesh.num_faces() {
1111 let face = mesh.face(FaceIndex(face_id as u32));
1112 for i in 0..3 {
1113 out_buffer.encode_u16(face[i].0 as u16);
1114 }
1115 }
1116 } else if counts_are_varint && mesh.num_points() < (1 << 21) {
1117 // Varint indices when the points fit in 21 bits, as upstream
1118 // does - but only from 2.2, which is where the decoder starts
1119 // reading them that way. This branch had no version gate at
1120 // all, so every sequential mesh below 2.2 with 65536 or more
1121 // points was written unreadable; 1.3 is a claimed version, so
1122 // this one is a live fix, not just parity.
1123 for face_id in 0..mesh.num_faces() {
1124 let face = mesh.face(FaceIndex(face_id as u32));
1125 for i in 0..3 {
1126 out_buffer.encode_varint(face[i].0 as u64);
1127 }
1128 }
1129 } else {
1130 // Default: use u32 for very large meshes
1131 for face_id in 0..mesh.num_faces() {
1132 let face = mesh.face(FaceIndex(face_id as u32));
1133 for i in 0..3 {
1134 out_buffer.encode_u32(face[i].0);
1135 }
1136 }
1137 }
1138 }
1139
1140 // Identity permutation for sequential encoding
1141 self.point_ids = (0..mesh.num_points())
1142 .map(|i| PointIndex(i as u32))
1143 .collect();
1144
1145 Ok(())
1146 }
1147
1148 /// Whether the position attribute is walked by max prediction degree
1149 /// rather than depth first.
1150 ///
1151 /// This is the predicate behind the `traversal_method` byte, and it has to
1152 /// be the same one `MeshEdgebreakerEncoder::position_uses_prediction_degree`
1153 /// walks by: the byte tells the decoder which order the position values are
1154 /// in, and a decoder that reorders them differently reads every value onto
1155 /// the wrong vertex. Upstream takes the prediction degree back when one
1156 /// connectivity is shared by more than one attribute -- see
1157 /// `mesh_edgebreaker_encoder_impl.cc:165-175` -- so the walk does too, and
1158 /// so must the byte.
1159 ///
1160 /// The third condition the walk applies, that the target bitstream can
1161 /// express the choice at all, needs no counterpart here: the byte itself
1162 /// arrived in 1.2, and every caller writes it only for 1.2 and above.
1163 fn position_traversal_is_prediction_degree(&self, mesh: &Mesh) -> bool {
1164 self.options.get_speed() == 0
1165 && !(self.use_single_connectivity && mesh.num_attributes() > 1)
1166 }
1167
1168 fn encode_attributes(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
1169 // NOTE: Unlike the decoder, the encoder does NOT need to apply UpdatePointToAttributeIndexMapping
1170 // because the attribute still has identity mapping. The encoder uses the point_ids array
1171 // (from edgebreaker traversal) to determine the order in which to process points, and
1172 // mapped_index with identity mapping just returns the point index directly.
1173
1174 let method_int = self.options.get_global_int("encoding_method", -1);
1175 // Match C++ behavior: if encoding_method is not set (-1),
1176 // use Edgebreaker for all options except speed == 10
1177 let is_edgebreaker = if method_int == -1 {
1178 self.options.get_speed() != 10
1179 } else {
1180 method_int == 1
1181 };
1182
1183 if is_edgebreaker && !self.use_single_connectivity {
1184 return self.encode_edgebreaker_attributes_split(out_buffer);
1185 }
1186
1187 // Encode number of attribute decoders (u8).
1188 // For both sequential and edgebreaker with single-connectivity mode:
1189 // there's only ONE attribute encoder containing ALL attributes.
1190 // This matches C++ behavior when use_single_connectivity_ = true (speed >= 6).
1191 let num_attributes = self
1192 .point_cloud()
1193 .expect("point_cloud set")
1194 .num_attributes();
1195 let num_encoders = if num_attributes > 0 { 1 } else { 0 };
1196 // Use the buffer's version (set in encode_header) for version checks.
1197 let major = out_buffer.version_major();
1198 let minor = out_buffer.version_minor();
1199
1200 out_buffer.encode_u8(num_encoders as u8);
1201
1202 // Phase 1: attributes decoder identifiers.
1203 // For single-encoder mode: one encoder with att_data_id = -1 (uses position connectivity)
1204 if num_encoders > 0 && is_edgebreaker {
1205 // att_data_id (i8), encoder_type (u8), traversal_method (u8)
1206 // -1 means use position connectivity (single connectivity mode)
1207 out_buffer.encode_u8((-1i8) as u8); // att_data_id = -1
1208 out_buffer.encode_u8(0); // element_type = MESH_VERTEX_ATTRIBUTE
1209
1210 // Traversal method was added in bitstream 1.2. Older streams
1211 // default to DEPTH_FIRST on decode and must not carry the byte.
1212 if crate::version::bitstream_version(major, minor) >= 0x0102 {
1213 // This group carries att_data_id -1, so it is the position
1214 // group, and the byte is whatever the position walk turns out
1215 // to be.
1216 // Scoped so the `mesh` borrow cannot reach the portable
1217 // registrations in the value loop below.
1218 let traversal_method: u8 = {
1219 let mesh = self
1220 .mesh
1221 .as_ref()
1222 .expect("mesh must be set before encoding");
1223 if self.position_traversal_is_prediction_degree(mesh) {
1224 1
1225 } else {
1226 0
1227 }
1228 };
1229 out_buffer.encode_u8(traversal_method);
1230 }
1231 }
1232 // For sequential, nothing is written in phase 1 (EncodeAttributesEncoderIdentifier does nothing)
1233
1234 let mut decoder_types: Vec<u8> = Vec::with_capacity(num_attributes as usize);
1235
1236 // Phase 2: Encode attribute encoder data
1237 // Both sequential and edgebreaker now use single-encoder mode:
1238 // - Write num_attrs = total attributes
1239 // - Write all attribute metadata
1240 // - Write all decoder types
1241
1242 if num_encoders > 0 {
1243 // Single encoder with all attributes (single-connectivity mode for edgebreaker)
1244 // Write num_attrs = total number of attributes
1245 if !uses_varint_encoding(major, minor) {
1246 out_buffer.encode_u32(num_attributes as u32);
1247 } else {
1248 out_buffer.encode_varint(num_attributes as u64);
1249 }
1250
1251 // Write all attribute metadata first
1252 for i in 0..num_attributes {
1253 let att = self.point_cloud().expect("point_cloud set").attribute(i);
1254
1255 #[cfg(feature = "debug_logs")]
1256 {
1257 debug_log!("DEBUG: Encoder encoding attribute {} metadata. Type: {:?}, Components: {}, Data: {:?}", i, att.attribute_type(), att.num_components(), att.data_type());
1258 }
1259 out_buffer.encode_u8(att.attribute_type() as u8);
1260 out_buffer.encode_u8(att.data_type() as u8);
1261 out_buffer.encode_u8(att.num_components());
1262 out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
1263
1264 if !uses_varint_unique_id(major, minor) {
1265 out_buffer.encode_u16(att.unique_id() as u16);
1266 } else {
1267 out_buffer.encode_varint(att.unique_id() as u64);
1268 }
1269 }
1270
1271 // Write all decoder types after all metadata (SequentialAttributeEncodersController pattern)
1272 for i in 0..num_attributes {
1273 let att = self.point_cloud().expect("point_cloud set").attribute(i);
1274 let quantization_bits = self.options.get_attribute_int(i, "quantization_bits", -1);
1275 let decoder_type = select_sequential_encoder(att, quantization_bits) as u8;
1276 out_buffer.encode_u8(decoder_type);
1277 decoder_types.push(decoder_type);
1278 }
1279 }
1280
1281 // Phase 3: Encode attribute values (all attributes first)
1282 // C++ order: all EncodePortableAttribute calls, then all EncodeDataNeededByPortableTransform calls
1283
1284 // Store transforms and encoders for later use in transform data encoding
1285 let mut quantization_transforms: Vec<Option<AttributeQuantizationTransform>> = Vec::new();
1286 let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> = Vec::new();
1287 // Collected here rather than written straight to `self`, which is
1288 // borrowed as the `GeometryEncoder` the attribute encoders predict
1289 // against for as long as this loop runs.
1290 let mut predictions = Vec::new();
1291
1292 // First pass: encode all attribute VALUES
1293 for i in 0..num_attributes {
1294 let att = self.point_cloud().expect("point_cloud set").attribute(i);
1295 let decoder_type = decoder_types[i as usize];
1296 let quantization_bits = self.options.get_attribute_int(i, "quantization_bits", -1);
1297
1298 match decoder_type {
1299 3 => {
1300 // Normal attribute with octahedral encoding
1301 let mut encoder = SequentialNormalAttributeEncoder::new();
1302 encoder
1303 .init(
1304 self.point_cloud().expect("point_cloud set"),
1305 i,
1306 &self.options,
1307 )
1308 .map_err(|e| {
1309 DracoError::general(format!("Failed to init normal encoder: {e}"))
1310 })?;
1311 encoder.encode_values(
1312 self.point_cloud().expect("point_cloud set"),
1313 &self.point_ids,
1314 out_buffer,
1315 &self.options,
1316 self,
1317 )?;
1318 if let Some((method, transform)) = encoder.selected_prediction() {
1319 predictions.push((i, method, transform));
1320 }
1321 normal_encoders.push(Some(encoder));
1322 quantization_transforms.push(None);
1323 }
1324 2 => {
1325 // Quantized attribute (mapping already applied at start of encode_attributes)
1326 let mut q_transform = AttributeQuantizationTransform::new();
1327 q_transform
1328 .compute_parameters(att, quantization_bits)
1329 .map_err(|e| {
1330 DracoError::general(format!(
1331 "Failed to compute quantization parameters: {e}"
1332 ))
1333 })?;
1334 let mut portable = PointAttribute::default();
1335 q_transform
1336 .transform_attribute(
1337 att,
1338 EntryToPointIdMap::from_point_indices(&self.point_ids),
1339 &mut portable,
1340 )
1341 .map_err(|e| {
1342 DracoError::general(format!("Failed to quantize attribute: {e}"))
1343 })?;
1344
1345 // A quantized position is the parent the portable schemes
1346 // read, and what they read must be the quantized values
1347 // with the map rebuilt into encoding order -- what the
1348 // decoder reconstructs. Registration is what makes the
1349 // attribute a parent: without it there is no copy for
1350 // `get_portable_attribute` to answer, and the binding is
1351 // left with the attribute itself.
1352 if ParentAttributes::is_prediction_parent(
1353 att,
1354 self.point_cloud().expect("point_cloud set"),
1355 &self.options,
1356 ) {
1357 rebuild_parent_point_map(
1358 att,
1359 &mut portable,
1360 &self.point_ids,
1361 self.point_cloud().expect("point_cloud set").num_points(),
1362 )?;
1363 self.parent_attributes.register(i, portable.clone());
1364 }
1365
1366 let mut att_encoder = SequentialIntegerAttributeEncoder::new();
1367 att_encoder.init(i);
1368 att_encoder.encode_values(
1369 self.point_cloud().expect("point_cloud set"),
1370 &self.point_ids,
1371 out_buffer,
1372 &self.options,
1373 self,
1374 Some(&portable),
1375 true,
1376 )?;
1377 if let Some((method, transform)) = att_encoder.selected_prediction() {
1378 predictions.push((i, method, transform));
1379 }
1380
1381 self.attribute_quantization.push((i, q_transform.clone()));
1382 quantization_transforms.push(Some(q_transform));
1383 normal_encoders.push(None);
1384 }
1385 1 => {
1386 // Integer attribute
1387 // An integral position still needs a portable form for the
1388 // portable schemes' parent reads, for the reason
1389 // `integral_portable_attribute` gives: the predictor must
1390 // read what the decoder reconstructs.
1391 if ParentAttributes::is_prediction_parent(
1392 att,
1393 self.point_cloud().expect("point_cloud set"),
1394 &self.options,
1395 ) {
1396 let mut portable = integral_portable_attribute(att, &self.point_ids)?;
1397 rebuild_parent_point_map(
1398 att,
1399 &mut portable,
1400 &self.point_ids,
1401 self.point_cloud().expect("point_cloud set").num_points(),
1402 )?;
1403 self.parent_attributes.register(i, portable);
1404 }
1405
1406 let mut att_encoder = SequentialIntegerAttributeEncoder::new();
1407 att_encoder.init(i);
1408 att_encoder.encode_values(
1409 self.point_cloud().expect("point_cloud set"),
1410 &self.point_ids,
1411 out_buffer,
1412 &self.options,
1413 self,
1414 None,
1415 true,
1416 )?;
1417 if let Some((method, transform)) = att_encoder.selected_prediction() {
1418 predictions.push((i, method, transform));
1419 }
1420 quantization_transforms.push(None);
1421 normal_encoders.push(None);
1422 }
1423 0 => {
1424 // Generic/float attribute
1425 let mut att_encoder = SequentialAttributeEncoder::new();
1426 att_encoder.init(i);
1427 att_encoder.encode_values(
1428 self.point_cloud().expect("point_cloud set"),
1429 &self.point_ids,
1430 out_buffer,
1431 )?;
1432 quantization_transforms.push(None);
1433 normal_encoders.push(None);
1434 }
1435 _ => {
1436 return Err(DracoError::general(format!(
1437 "Unsupported encoder type {}",
1438 decoder_type
1439 )));
1440 }
1441 }
1442 }
1443
1444 // Second pass: encode all TRANSFORM DATA
1445 for i in 0..num_attributes {
1446 let decoder_type = decoder_types[i as usize];
1447
1448 match decoder_type {
1449 3 => {
1450 // Normal attribute - encode octahedral transform data
1451 let bitstream_version = crate::version::bitstream_version(major, minor);
1452 if bitstream_version != 0 && bitstream_version < 0x0200 {
1453 continue;
1454 }
1455 if let Some(ref encoder) = normal_encoders[i as usize] {
1456 encoder
1457 .encode_data_needed_by_portable_transform(out_buffer)
1458 .map_err(|err| {
1459 DracoError::general(format!(
1460 "Failed to encode normal transform data: {err}"
1461 ))
1462 })?;
1463 }
1464 }
1465 2 => {
1466 // Quantized attribute - encode quantization parameters,
1467 // unless the target version already carried them inline
1468 // ahead of the values.
1469 if self.quantization_parameters_are_inline(i) {
1470 continue;
1471 }
1472 if let Some(ref q_transform) = quantization_transforms[i as usize] {
1473 q_transform.encode_parameters(out_buffer).map_err(|e| {
1474 DracoError::general(format!(
1475 "Failed to encode quantization parameters: {e}"
1476 ))
1477 })?;
1478 }
1479 }
1480 1 | 0 => {
1481 // No transform data for integer/generic attributes
1482 }
1483 _ => {}
1484 }
1485 }
1486
1487 self.attribute_predictions.extend(predictions);
1488 Ok(())
1489 }
1490
1491 fn encode_edgebreaker_attributes_split(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
1492 let mesh = self
1493 .mesh
1494 .as_ref()
1495 .expect("mesh must be set before encoding");
1496 let mut groups: Vec<(i8, Vec<i32>)> = Vec::new();
1497 let mut position_attrs = Vec::new();
1498 for i in 0..mesh.num_attributes() {
1499 if mesh.attribute(i).attribute_type() == GeometryAttributeType::Position {
1500 position_attrs.push(i);
1501 }
1502 }
1503 if !position_attrs.is_empty() {
1504 groups.push((-1, position_attrs));
1505 }
1506 for (data_id, attr_conn) in self.edgebreaker_attribute_connectivity.iter().enumerate() {
1507 groups.push((data_id as i8, vec![attr_conn.attribute_id]));
1508 }
1509
1510 // The group count is one byte in the bitstream, so a mesh needing more
1511 // groups than that cannot be described. Truncating wrote a stream that
1512 // decodes as a different mesh: 261 groups became 5, and the decoder
1513 // read the sixth group's bytes as attribute data. Measured at the
1514 // boundary rather than assumed - 256 groups is the first count that
1515 // breaks, and everything below it round-trips, including the counts
1516 // where the per-group `i8` data id goes negative.
1517 if groups.len() > u8::MAX as usize {
1518 return Err(DracoError::general(format!(
1519 "Mesh needs {} attribute groups but the bitstream field holds {}",
1520 groups.len(),
1521 u8::MAX
1522 )));
1523 }
1524 out_buffer.encode_u8(groups.len() as u8);
1525
1526 let major = out_buffer.version_major();
1527 let minor = out_buffer.version_minor();
1528 let writes_traversal_method = crate::version::bitstream_version(major, minor) >= 0x0102;
1529 // Prediction degree is the position group's traversal alone. Every
1530 // other group is walked depth first, whatever the speed -- upstream
1531 // guards on the attribute being POSITION, and the groups here carry
1532 // att_data_id -1 for exactly that one. Declaring it for the rest
1533 // mislabels a stream whose values were written in depth-first order.
1534 let position_prediction_degree = self.position_traversal_is_prediction_degree(mesh);
1535 for (att_data_id, _) in &groups {
1536 out_buffer.encode_u8(*att_data_id as u8);
1537 let element_type = if *att_data_id >= 0
1538 && !self.edgebreaker_attribute_connectivity[*att_data_id as usize].no_interior_seams
1539 {
1540 1 // MESH_CORNER_ATTRIBUTE
1541 } else {
1542 0 // MESH_VERTEX_ATTRIBUTE
1543 };
1544 out_buffer.encode_u8(element_type);
1545 if writes_traversal_method {
1546 let is_position_group = *att_data_id < 0;
1547 let traversal_method: u8 = if position_prediction_degree && is_position_group {
1548 1
1549 } else {
1550 0
1551 };
1552 out_buffer.encode_u8(traversal_method);
1553 }
1554 }
1555
1556 let mut decoder_types_by_group: Vec<Vec<u8>> = Vec::with_capacity(groups.len());
1557
1558 for (_, attr_ids) in &groups {
1559 if !uses_varint_encoding(major, minor) {
1560 out_buffer.encode_u32(attr_ids.len() as u32);
1561 } else {
1562 out_buffer.encode_varint(attr_ids.len() as u64);
1563 }
1564
1565 for &att_id in attr_ids {
1566 let att = mesh.attribute(att_id);
1567 out_buffer.encode_u8(att.attribute_type() as u8);
1568 out_buffer.encode_u8(att.data_type() as u8);
1569 out_buffer.encode_u8(att.num_components());
1570 out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
1571 if !uses_varint_unique_id(major, minor) {
1572 out_buffer.encode_u16(att.unique_id() as u16);
1573 } else {
1574 out_buffer.encode_varint(att.unique_id() as u64);
1575 }
1576 }
1577
1578 let mut decoder_types = Vec::with_capacity(attr_ids.len());
1579 for &att_id in attr_ids {
1580 let decoder_type = self.decoder_type_for_attribute(att_id);
1581 out_buffer.encode_u8(decoder_type);
1582 decoder_types.push(decoder_type);
1583 }
1584 decoder_types_by_group.push(decoder_types);
1585 }
1586
1587 for (group_i, (att_data_id, attr_ids)) in groups.iter().enumerate() {
1588 let point_ids = if *att_data_id >= 0 {
1589 self.prepare_active_attribute_connectivity(*att_data_id as usize)?
1590 } else {
1591 self.active_corner_table = None;
1592 self.active_data_to_corner_map = None;
1593 self.active_vertex_to_data_map = None;
1594 self.point_ids.clone()
1595 };
1596
1597 self.encode_attribute_group_values(
1598 attr_ids,
1599 &decoder_types_by_group[group_i],
1600 &point_ids,
1601 out_buffer,
1602 )?;
1603 }
1604
1605 self.active_corner_table = None;
1606 self.active_data_to_corner_map = None;
1607 self.active_vertex_to_data_map = None;
1608 Ok(())
1609 }
1610
1611 fn decoder_type_for_attribute(&self, att_id: i32) -> u8 {
1612 let mesh = self
1613 .mesh
1614 .as_ref()
1615 .expect("mesh must be set before encoding");
1616 let att = mesh.attribute(att_id);
1617 let quantization_bits = self
1618 .options
1619 .get_attribute_int(att_id, "quantization_bits", -1);
1620 select_sequential_encoder(att, quantization_bits) as u8
1621 }
1622
1623 fn prepare_active_attribute_connectivity(
1624 &mut self,
1625 data_id: usize,
1626 ) -> Result<Vec<PointIndex>, DracoError> {
1627 let mesh = self
1628 .mesh
1629 .as_ref()
1630 .expect("mesh must be set before encoding");
1631 let base_ct = self
1632 .corner_table
1633 .as_ref()
1634 .ok_or_else(|| DracoError::general("corner_table must be set".to_string()))?;
1635 let attr_conn = self
1636 .edgebreaker_attribute_connectivity
1637 .get(data_id)
1638 .ok_or_else(|| DracoError::general("Invalid attribute connectivity id".to_string()))?;
1639
1640 if attr_conn.no_interior_seams {
1641 // Same corner table as the position, but not necessarily the same
1642 // walk over it: `attribute_traversal` is set when the position took
1643 // the max-prediction-degree order and this attribute must not.
1644 self.active_corner_table = None;
1645 if let Some((point_ids, data_to_corner_map, vertex_to_data_map)) =
1646 self.attribute_traversal.clone()
1647 {
1648 self.active_data_to_corner_map = Some(data_to_corner_map);
1649 self.active_vertex_to_data_map = Some(vertex_to_data_map);
1650 return Ok(point_ids);
1651 }
1652 self.active_data_to_corner_map = None;
1653 self.active_vertex_to_data_map = None;
1654 return Ok(self.point_ids.clone());
1655 }
1656
1657 // Same seam-cut-and-recompute the decoder runs on its own seam bits
1658 // (`mesh_decoder.rs::make_attribute_corner_table`) -- one function
1659 // instead of the two hand-rolled copies this used to be.
1660 let (attr_ct, _is_vertex_on_seam) =
1661 crate::mesh_attribute_corner_table::cut_seam_edges_and_recompute_vertices(
1662 base_ct,
1663 &attr_conn.seam_edges,
1664 )?;
1665
1666 // Walk the attribute's own table depth first, seeded by the edgebreaker
1667 // corner order, as upstream does with
1668 // `DepthFirstTraverser<MeshAttributeCornerTable>` and
1669 // `SetCornerOrder(processed_connectivity_corners_)`.
1670 //
1671 // Enumerating `vertex_corners` instead, as this used to, yields the
1672 // identity permutation of attribute-vertex indices -- `vertex_corners[v]`
1673 // has vertex `v` by construction -- which is not an encoding order at
1674 // all. The decoder walks the table it rebuilds from the seam bits, so
1675 // the values came back attached to the wrong points.
1676 let Some(encoder) = self.edgebreaker_encoder.as_ref() else {
1677 return Err(DracoError::general(
1678 "Attribute seams need the edgebreaker corner order".to_string(),
1679 ));
1680 };
1681 let (point_ids, data_to_corner_map, vertex_to_data_map) =
1682 encoder.generate_depth_first_traversal(mesh, &attr_ct);
1683
1684 self.active_corner_table = Some(attr_ct);
1685 self.active_data_to_corner_map = Some(data_to_corner_map);
1686 self.active_vertex_to_data_map = Some(vertex_to_data_map);
1687 Ok(point_ids)
1688 }
1689
1690 fn encode_attribute_group_values(
1691 &mut self,
1692 attr_ids: &[i32],
1693 decoder_types: &[u8],
1694 point_ids: &[PointIndex],
1695 out_buffer: &mut EncoderBuffer,
1696 ) -> Status {
1697 // Three passes over the group, one per step of C++
1698 // SequentialAttributeEncodersController: transform every attribute to its
1699 // portable form, encode them all, then encode the data their transforms
1700 // need. Each pass is marked below.
1701 //
1702 // Pass one, TransformAttributesToPortableFormat. It has to finish before
1703 // any attribute is encoded: a prediction scheme that reads a parent needs
1704 // the parent's portable values, and in a single pass the parent would not
1705 // exist yet for anything encoded ahead of it.
1706 let mut quantization_transforms: Vec<Option<AttributeQuantizationTransform>> = Vec::new();
1707 // The copy each type-2 attribute encodes from, kept for pass two. A
1708 // parent's copy is not here: it is registered on `self`, where later
1709 // groups' predictors reach it, and pass two reads it there.
1710 let mut own_portables: Vec<(i32, PointAttribute)> = Vec::new();
1711 {
1712 let mesh = self
1713 .mesh
1714 .as_ref()
1715 .expect("mesh must be set before encoding");
1716 // Parent registrations, collected rather than written straight to
1717 // `self`, which is borrowed as the mesh for as long as this loop
1718 // runs.
1719 let mut parent_registrations: Vec<(i32, PointAttribute)> = Vec::new();
1720 let mut quantized: Vec<(i32, AttributeQuantizationTransform)> = Vec::new();
1721 for (local_i, &att_id) in attr_ids.iter().enumerate() {
1722 let att = mesh.attribute(att_id);
1723 let is_parent_attribute =
1724 ParentAttributes::is_prediction_parent(att, mesh, &self.options);
1725 if decoder_types[local_i] != 2 {
1726 // An already-integral parent still needs a portable form,
1727 // for the reason `integral_portable_attribute` gives: the
1728 // predictor must read what the decoder will reconstruct,
1729 // not the original the mesh was handed. Only a parent, as
1730 // upstream only rebuilds the map under `is_parent_encoder`.
1731 if is_parent_attribute && decoder_types[local_i] == 1 {
1732 let mut portable = integral_portable_attribute(att, point_ids)?;
1733 // The same rebuild the quantized arm below does, and for
1734 // the same reason: the values are in encoding order and
1735 // a predictor reads its parent as
1736 // `mapped_index(point_id)`. Without it the map stays the
1737 // identity, the encoder reads the entry sitting at the
1738 // point's own index, and the decoder -- whose parent
1739 // carries the rebuilt map -- reads a different one.
1740 rebuild_parent_point_map(att, &mut portable, point_ids, mesh.num_points())?;
1741 parent_registrations.push((att_id, portable));
1742 }
1743 quantization_transforms.push(None);
1744 continue;
1745 }
1746 let quantization_bits =
1747 self.options
1748 .get_attribute_int(att_id, "quantization_bits", -1);
1749 let mut q_transform = AttributeQuantizationTransform::new();
1750 q_transform
1751 .compute_parameters(att, quantization_bits)
1752 .map_err(|e| {
1753 DracoError::general(format!(
1754 "Failed to compute quantization parameters: {e}"
1755 ))
1756 })?;
1757 let mut portable = PointAttribute::default();
1758 q_transform
1759 .transform_attribute(
1760 att,
1761 EntryToPointIdMap::from_point_indices(point_ids),
1762 &mut portable,
1763 )
1764 .map_err(|e| {
1765 DracoError::general(format!("Failed to quantize attribute: {e}"))
1766 })?;
1767
1768 // Only a parent needs the rebuilt map, which is the guard
1769 // upstream spells `is_parent_encoder()`. What declares a parent
1770 // is `position_is_a_prediction_parent` above -- not the speed
1771 // alone, which is what this used to say.
1772 if is_parent_attribute {
1773 rebuild_parent_point_map(att, &mut portable, point_ids, mesh.num_points())?;
1774 parent_registrations.push((att_id, portable));
1775 } else {
1776 own_portables.push((att_id, portable));
1777 }
1778 quantized.push((att_id, q_transform.clone()));
1779 quantization_transforms.push(Some(q_transform));
1780 }
1781 // Accumulated across groups, not replaced: attributes are encoded one
1782 // group at a time and the position lives in its own, so replacing
1783 // here would take the position's portable values away from every
1784 // later group's predictors.
1785 self.attribute_quantization.extend(quantized);
1786 for (att_id, portable) in parent_registrations {
1787 self.parent_attributes.register(att_id, portable);
1788 }
1789 }
1790
1791 // Pass two, EncodePortableAttributes: the values themselves, in attribute
1792 // order.
1793 let mesh = self
1794 .mesh
1795 .as_ref()
1796 .expect("mesh must be set before encoding");
1797 let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> = Vec::new();
1798 // See the sibling collection in `encode_attributes`: `self` is the
1799 // `GeometryEncoder` the attribute encoders borrow for the whole loop.
1800 let mut predictions = Vec::new();
1801
1802 for (local_i, &att_id) in attr_ids.iter().enumerate() {
1803 let att = mesh.attribute(att_id);
1804 let decoder_type = decoder_types[local_i];
1805 let _ = att;
1806
1807 match decoder_type {
1808 3 => {
1809 let mut encoder = SequentialNormalAttributeEncoder::new();
1810 encoder
1811 .init(
1812 self.point_cloud().expect("point_cloud set"),
1813 att_id,
1814 &self.options,
1815 )
1816 .map_err(|e| {
1817 DracoError::general(format!("Failed to init normal encoder: {e}"))
1818 })?;
1819 encoder.encode_values(
1820 self.point_cloud().expect("point_cloud set"),
1821 point_ids,
1822 out_buffer,
1823 &self.options,
1824 self,
1825 )?;
1826 if let Some((method, transform)) = encoder.selected_prediction() {
1827 predictions.push((att_id, method, transform));
1828 }
1829 normal_encoders.push(Some(encoder));
1830 }
1831 2 => {
1832 // The attribute's own copy: a parent's is registered on
1833 // `self`, anything else type-2 sits in this group's own
1834 // collection from pass one.
1835 let portable = self
1836 .parent_attributes
1837 .get(att_id)
1838 .or_else(|| {
1839 own_portables
1840 .iter()
1841 .find(|(id, _)| *id == att_id)
1842 .map(|(_, att)| att)
1843 })
1844 .ok_or_else(|| {
1845 DracoError::general(format!("Missing portable attribute for {att_id}"))
1846 })?;
1847
1848 let mut att_encoder = SequentialIntegerAttributeEncoder::new();
1849 att_encoder.init(att_id);
1850 att_encoder.encode_values(
1851 mesh as &PointCloud,
1852 point_ids,
1853 out_buffer,
1854 &self.options,
1855 self,
1856 Some(portable),
1857 true,
1858 )?;
1859 if let Some((method, transform)) = att_encoder.selected_prediction() {
1860 predictions.push((att_id, method, transform));
1861 }
1862 normal_encoders.push(None);
1863 }
1864 1 => {
1865 let mut att_encoder = SequentialIntegerAttributeEncoder::new();
1866 att_encoder.init(att_id);
1867 att_encoder.encode_values(
1868 mesh as &PointCloud,
1869 point_ids,
1870 out_buffer,
1871 &self.options,
1872 self,
1873 None,
1874 true,
1875 )?;
1876 if let Some((method, transform)) = att_encoder.selected_prediction() {
1877 predictions.push((att_id, method, transform));
1878 }
1879 normal_encoders.push(None);
1880 }
1881 0 => {
1882 let mut att_encoder = SequentialAttributeEncoder::new();
1883 att_encoder.init(att_id);
1884 att_encoder.encode_values(mesh as &PointCloud, point_ids, out_buffer)?;
1885 normal_encoders.push(None);
1886 }
1887 _ => {
1888 return Err(DracoError::general(format!(
1889 "Unsupported encoder type {}",
1890 decoder_type
1891 )));
1892 }
1893 }
1894 }
1895
1896 // Pass three, EncodeDataNeededByPortableTransforms: the parameters a
1897 // decoder needs to undo each transform -- quantization ranges, and the
1898 // octahedron's bit count. Separate from pass two because upstream emits
1899 // every attribute's values first and only then every attribute's
1900 // transform data, so the two cannot be interleaved.
1901 for (local_i, &decoder_type) in decoder_types.iter().enumerate() {
1902 match decoder_type {
1903 3 => {
1904 let major = out_buffer.version_major();
1905 let minor = out_buffer.version_minor();
1906 let bitstream_version = crate::version::bitstream_version(major, minor);
1907 if bitstream_version != 0 && bitstream_version < 0x0200 {
1908 continue;
1909 }
1910 if let Some(ref encoder) = normal_encoders[local_i] {
1911 encoder
1912 .encode_data_needed_by_portable_transform(out_buffer)
1913 .map_err(|err| {
1914 DracoError::general(format!(
1915 "Failed to encode normal transform data: {err}"
1916 ))
1917 })?;
1918 }
1919 }
1920 2 => {
1921 if self.quantization_parameters_are_inline(attr_ids[local_i]) {
1922 continue;
1923 }
1924 if let Some(ref q_transform) = quantization_transforms[local_i] {
1925 q_transform.encode_parameters(out_buffer).map_err(|e| {
1926 DracoError::general(format!(
1927 "Failed to encode quantization parameters: {e}"
1928 ))
1929 })?;
1930 }
1931 }
1932 1 | 0 => {}
1933 _ => {}
1934 }
1935 }
1936
1937 self.attribute_predictions.extend(predictions);
1938 Ok(())
1939 }
1940
1941 fn compute_number_of_encoded_faces(&mut self) {
1942 if let Some(ref mesh) = self.mesh {
1943 self.num_encoded_faces = mesh.num_faces();
1944 }
1945 }
1946
1947 fn build_encoded_mesh_info(&mut self) -> Result<EncodedMeshInfo, DracoError> {
1948 let num_attributes = self
1949 .mesh
1950 .as_ref()
1951 .expect("mesh must be set before encoding")
1952 .num_attributes();
1953 let mut attributes = Vec::with_capacity(num_attributes as usize);
1954 let mut encoded_num_points = self.point_ids.len();
1955
1956 for att_id in 0..num_attributes {
1957 let point_ids = self.encoded_point_ids_for_attribute(att_id)?;
1958 let num_encoded_values = point_ids.len();
1959 encoded_num_points = encoded_num_points.max(num_encoded_values);
1960
1961 let (position_min, position_max) =
1962 self.position_bounds_for_attribute(att_id, &point_ids)?;
1963 let mesh = self
1964 .mesh
1965 .as_ref()
1966 .expect("mesh must be set before encoding");
1967 let encoder_type = self.attribute_encoder_type(mesh, att_id);
1968 let quantization_bits = match encoder_type {
1969 SequentialAttributeEncoderType::Quantization
1970 | SequentialAttributeEncoderType::Normals => Some(self.options.get_attribute_int(
1971 att_id,
1972 "quantization_bits",
1973 -1,
1974 )),
1975 // A `quantization_bits` option on an integer or generic
1976 // attribute never reaches a transform, so reporting it would
1977 // describe the request rather than the encode.
1978 SequentialAttributeEncoderType::Integer
1979 | SequentialAttributeEncoderType::Generic => None,
1980 };
1981 let prediction = self
1982 .attribute_predictions
1983 .iter()
1984 .find(|(id, _, _)| *id == att_id)
1985 .map(|&(_, method, transform)| (method, transform));
1986 let att = mesh.attribute(att_id);
1987 attributes.push(EncodedAttributeInfo {
1988 source_attribute_id: att_id,
1989 attribute_type: att.attribute_type(),
1990 data_type: att.data_type(),
1991 num_components: att.num_components(),
1992 normalized: att.normalized(),
1993 unique_id: att.unique_id(),
1994 num_encoded_values,
1995 encoder_type,
1996 quantization_bits,
1997 prediction,
1998 position_min,
1999 position_max,
2000 });
2001 }
2002
2003 let (source_num_points, num_faces) = self
2004 .mesh
2005 .as_ref()
2006 .map(|mesh| (mesh.num_points(), mesh.num_faces()))
2007 .expect("mesh must be set before encoding");
2008 if self.method == 0 {
2009 encoded_num_points = source_num_points;
2010 } else {
2011 encoded_num_points = self.encoded_num_points_for_mesh(encoded_num_points)?;
2012 }
2013
2014 self.active_corner_table = None;
2015 self.active_data_to_corner_map = None;
2016 self.active_vertex_to_data_map = None;
2017
2018 let (mut major, mut minor) = self.options.get_version();
2019 if major == 0 && minor == 0 {
2020 (major, minor) = DEFAULT_MESH_VERSION;
2021 }
2022 let traversal = (self.method == 1).then(|| {
2023 select_edgebreaker_traversal(
2024 self.options.get_speed() as usize,
2025 num_faces,
2026 self.options.get_global_int("force_predictive_traversal", 0) == 1,
2027 )
2028 });
2029 Ok(EncodedMeshInfo {
2030 encoding_method: self.method,
2031 bitstream_version: (major, minor),
2032 traversal,
2033 speed: self.options.get_speed(),
2034 single_connectivity: self.use_single_connectivity,
2035 num_encoded_faces: num_faces,
2036 num_encoded_points: encoded_num_points,
2037 attributes,
2038 })
2039 }
2040
2041 fn encoded_point_ids_for_attribute(
2042 &mut self,
2043 att_id: i32,
2044 ) -> Result<Vec<PointIndex>, DracoError> {
2045 if self.method == 0 || self.use_single_connectivity {
2046 return Ok(self.point_ids.clone());
2047 }
2048
2049 if let Some(data_id) = self
2050 .edgebreaker_attribute_connectivity
2051 .iter()
2052 .position(|connectivity| connectivity.attribute_id == att_id)
2053 {
2054 return self.prepare_active_attribute_connectivity(data_id);
2055 }
2056
2057 Ok(self.point_ids.clone())
2058 }
2059
2060 fn encoded_num_points_for_mesh(&mut self, base_num_points: usize) -> Result<usize, DracoError> {
2061 if self.method == 0 || self.use_single_connectivity {
2062 return Ok(base_num_points);
2063 }
2064
2065 let mut num_points = base_num_points;
2066 for data_id in 0..self.edgebreaker_attribute_connectivity.len() {
2067 if self.edgebreaker_attribute_connectivity[data_id].no_interior_seams {
2068 continue;
2069 }
2070 let point_ids = self.prepare_active_attribute_connectivity(data_id)?;
2071 num_points = num_points.max(point_ids.len());
2072 }
2073 self.active_corner_table = None;
2074 self.active_data_to_corner_map = None;
2075 self.active_vertex_to_data_map = None;
2076 Ok(num_points)
2077 }
2078
2079 fn position_bounds_for_attribute(
2080 &self,
2081 att_id: i32,
2082 point_ids: &[PointIndex],
2083 ) -> Result<PositionBounds, DracoError> {
2084 let mesh = self
2085 .mesh
2086 .as_ref()
2087 .expect("mesh must be set before encoding");
2088 let att = mesh.attribute(att_id);
2089 if att.attribute_type() != GeometryAttributeType::Position {
2090 return Ok((None, None));
2091 }
2092 if att.num_components() != 3 || att.data_type() != DataType::Float32 {
2093 return Ok((None, None));
2094 }
2095
2096 if self.decoder_type_for_attribute(att_id) == 2 {
2097 let quantization_bits = self
2098 .options
2099 .get_attribute_int(att_id, "quantization_bits", -1);
2100 // The encode has already computed these for this attribute, and
2101 // computing them again means sweeping every value for its minimum
2102 // a second time. Recompute only if the attribute was quantized by
2103 // some path that did not record it, so this reports the same
2104 // bounds either way.
2105 let recorded = self
2106 .attribute_quantization
2107 .iter()
2108 .find(|(id, _)| *id == att_id)
2109 .map(|(_, transform)| transform.clone());
2110 let q_transform = match recorded {
2111 Some(transform) => transform,
2112 None => {
2113 let mut transform = AttributeQuantizationTransform::new();
2114 transform
2115 .compute_parameters(att, quantization_bits)
2116 .map_err(|e| {
2117 DracoError::general(format!(
2118 "Failed to compute position quantization parameters: {e}"
2119 ))
2120 })?;
2121 transform
2122 }
2123 };
2124
2125 // These are the bounds of the attribute as the decoder will see it,
2126 // so each extreme goes through the same quantize/dequantize round
2127 // trip the encoded values do. The round trip is monotonic per
2128 // component, so the extremes of the round-tripped values are the
2129 // round-tripped extremes -- folding the original and transforming
2130 // six scalars gives the same answer as building the portable and
2131 // dequantized attributes to fold the result, without two full
2132 // passes over every point and the two attributes they allocate.
2133 // `quantization_round_trip_monotonic_test` pins that property.
2134 let (min, max) = Self::position_bounds_from_attribute(att, point_ids)?;
2135 let (Some(min), Some(max)) = (min, max) else {
2136 return Ok((None, None));
2137 };
2138 let round_trip = |bound: Vec<f64>| -> Result<Vec<f64>, DracoError> {
2139 bound
2140 .into_iter()
2141 .enumerate()
2142 .map(|(component, value)| {
2143 q_transform
2144 .round_trip_component(component, value as f32)
2145 .map(f64::from)
2146 .map_err(|e| {
2147 DracoError::general(format!(
2148 "Failed to quantize position bounds for encoded mesh info: {e}"
2149 ))
2150 })
2151 })
2152 .collect()
2153 };
2154 return Ok((Some(round_trip(min)?), Some(round_trip(max)?)));
2155 }
2156
2157 Self::position_bounds_from_attribute(att, point_ids)
2158 }
2159
2160 fn position_bounds_from_attribute(
2161 att: &PointAttribute,
2162 point_ids: &[PointIndex],
2163 ) -> Result<PositionBounds, DracoError> {
2164 let count = if point_ids.is_empty() {
2165 att.size()
2166 } else {
2167 point_ids.len()
2168 };
2169 if count == 0 {
2170 return Ok((None, None));
2171 }
2172
2173 let stride = usize::try_from(att.byte_stride()).map_err(|_| {
2174 DracoError::general("Position attribute has invalid byte stride".to_string())
2175 })?;
2176 let bytes = att.buffer().data();
2177 let mut min = [f32::INFINITY; 3];
2178 let mut max = [f32::NEG_INFINITY; 3];
2179
2180 for i in 0..count {
2181 let point = if point_ids.is_empty() {
2182 PointIndex(i as u32)
2183 } else {
2184 point_ids[i]
2185 };
2186 let value_index = att.mapped_index(point);
2187 if value_index == INVALID_ATTRIBUTE_VALUE_INDEX {
2188 return Err(DracoError::general(
2189 "Position attribute point map contains an invalid entry".to_string(),
2190 ));
2191 }
2192
2193 // A point's three components are twelve contiguous bytes, so one
2194 // slice of a fixed size answers what three offset computations and
2195 // three bounds checks answered per point before.
2196 const POSITION_BYTES: usize = 3 * 4;
2197 let value_offset = (value_index.0 as usize)
2198 .checked_mul(stride)
2199 .ok_or_else(|| {
2200 DracoError::general("Position attribute offset overflow".to_string())
2201 })?;
2202 let end = value_offset.checked_add(POSITION_BYTES).ok_or_else(|| {
2203 DracoError::general("Position attribute offset overflow".to_string())
2204 })?;
2205 let Some(point_bytes) = bytes.get(value_offset..end) else {
2206 return Err(DracoError::general(
2207 "Position attribute buffer is shorter than metadata".to_string(),
2208 ));
2209 };
2210 for component in 0..3 {
2211 let at = component * 4;
2212 let value = f32::from_le_bytes([
2213 point_bytes[at],
2214 point_bytes[at + 1],
2215 point_bytes[at + 2],
2216 point_bytes[at + 3],
2217 ]);
2218 min[component] = min[component].min(value);
2219 max[component] = max[component].max(value);
2220 }
2221 }
2222
2223 Ok((
2224 Some(min.into_iter().map(f64::from).collect()),
2225 Some(max.into_iter().map(f64::from).collect()),
2226 ))
2227 }
2228}
2229
2230impl Default for MeshEncoder {
2231 fn default() -> Self {
2232 Self::new()
2233 }
2234}