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