Skip to main content

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}