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::{EdgebreakerAttributeConnectivity, MeshEdgebreakerEncoder};
13use crate::metadata::METADATA_FLAG_MASK;
14use crate::point_cloud::PointCloud;
15use crate::point_cloud_encoder::GeometryEncoder;
16use crate::sequential_attribute_encoder::{select_sequential_encoder, SequentialAttributeEncoder};
17use crate::sequential_integer_attribute_encoder::SequentialIntegerAttributeEncoder;
18use crate::sequential_normal_attribute_encoder::SequentialNormalAttributeEncoder;
19use crate::status::{DracoError, Status};
20use crate::version::{
21    has_header_flags, uses_varint_encoding, uses_varint_unique_id, DEFAULT_MESH_VERSION,
22};
23
24/// `(min, max)` per-component position bounds, each present when computable.
25type PositionBounds = (Option<Vec<f64>>, Option<Vec<f64>>);
26
27/// Encoder for Draco triangle mesh bitstreams.
28///
29/// A `MeshEncoder` takes a [`Mesh`] plus [`EncoderOptions`] and writes a
30/// self-contained `.drc` bitstream (header, optional metadata, connectivity,
31/// and attributes) into an [`EncoderBuffer`]. The encoding method (EdgeBreaker or
32/// sequential), prediction schemes, and quantization are selected from the
33/// options, mirroring the C++ `MeshEncoder`/`ExpertEncoder` configuration.
34///
35/// After a successful [`encode`](MeshEncoder::encode), per-attribute and
36/// per-face details are available via
37/// [`encoded_mesh_info`](MeshEncoder::encoded_mesh_info).
38///
39/// # Examples
40///
41/// Build a single-triangle mesh, encode it, and decode it back:
42///
43/// ```
44/// use draco_core::{
45///     DataType, DecoderBuffer, EncoderBuffer, EncoderOptions, FaceIndex,
46///     GeometryAttributeType, Mesh, MeshDecoder, MeshEncoder, PointAttribute,
47/// };
48///
49/// // One triangle with a float32 position attribute (3 vertices).
50/// let mut mesh = Mesh::new();
51/// let mut position = PointAttribute::new();
52/// position.init(GeometryAttributeType::Position, 3, DataType::Float32, false, 3);
53/// let coords: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
54/// for (i, value) in coords.iter().enumerate() {
55///     position.buffer_mut().write(i * 4, &value.to_le_bytes());
56/// }
57/// mesh.add_attribute(position);
58/// mesh.set_num_faces(1);
59/// mesh.set_face(FaceIndex(0), [0u32.into(), 1u32.into(), 2u32.into()]);
60///
61/// // Encode to a Draco bitstream.
62/// let mut encoder = MeshEncoder::new();
63/// encoder.set_mesh(mesh);
64/// let mut buffer = EncoderBuffer::new();
65/// encoder.encode(&EncoderOptions::new(), &mut buffer)?;
66///
67/// // Decode it back.
68/// let mut decoded = Mesh::new();
69/// MeshDecoder::new().decode(&mut DecoderBuffer::new(buffer.data()), &mut decoded)?;
70/// assert_eq!(decoded.num_faces(), 1);
71/// # Ok::<(), draco_core::DracoError>(())
72/// ```
73pub struct MeshEncoder {
74    mesh: Option<Mesh>,
75    options: EncoderOptions,
76    num_encoded_faces: usize,
77    corner_table: Option<CornerTable>,
78    point_ids: Vec<PointIndex>,
79    data_to_corner_map: Option<Vec<u32>>,
80    vertex_to_data_map: Option<Vec<i32>>,
81    edgebreaker_attribute_connectivity: Vec<EdgebreakerAttributeConnectivity>,
82    active_corner_table: Option<CornerTable>,
83    active_data_to_corner_map: Option<Vec<u32>>,
84    active_vertex_to_data_map: Option<Vec<i32>>,
85    /// Depth-first order for the non-position attribute groups, present only
86    /// when the position group uses a different one (speed 0).
87    #[allow(clippy::type_complexity)]
88    attribute_traversal: Option<(Vec<PointIndex>, Vec<u32>, Vec<i32>)>,
89    /// Attributes in their portable (quantized) form, for the current group.
90    /// Prediction schemes read their parent attribute from here.
91    portable_attributes: Vec<(i32, PointAttribute)>,
92    /// Kept past `encode_edgebreaker_connectivity` for its corner order, which
93    /// an attribute with interior seams needs to walk its own corner table.
94    edgebreaker_encoder: Option<MeshEdgebreakerEncoder>,
95    method: i32,
96    /// Maps point indices to vertex indices in the corner table.
97    /// Used when position-based deduplication is enabled.
98    point_to_vertex_map: Option<Vec<u32>>,
99    /// Whether we're using single connectivity (all attributes share same corner table).
100    use_single_connectivity: bool,
101    encoded_mesh_info: Option<EncodedMeshInfo>,
102}
103
104/// Geometry shape and attribute metadata produced by a successful mesh encode.
105#[derive(Debug, Clone, PartialEq)]
106pub struct EncodedMeshInfo {
107    /// Numeric Draco mesh encoding method used for the output.
108    pub encoding_method: i32,
109    /// Number of faces encoded into the bitstream.
110    pub num_encoded_faces: usize,
111    /// Number of points encoded into the bitstream.
112    pub num_encoded_points: usize,
113    /// Per-attribute information captured during encoding.
114    pub attributes: Vec<EncodedAttributeInfo>,
115}
116
117/// Attribute metadata produced by a successful mesh encode.
118#[derive(Debug, Clone, PartialEq)]
119pub struct EncodedAttributeInfo {
120    /// Source attribute id in the input mesh.
121    pub source_attribute_id: i32,
122    /// Semantic type of the encoded attribute.
123    pub attribute_type: GeometryAttributeType,
124    /// Scalar data type of the encoded attribute.
125    pub data_type: DataType,
126    /// Number of scalar components per encoded value.
127    pub num_components: u8,
128    /// Whether integer values are normalized.
129    pub normalized: bool,
130    /// Draco unique id assigned to the attribute.
131    pub unique_id: u32,
132    /// Number of unique values encoded for the attribute.
133    pub num_encoded_values: usize,
134    /// Minimum position components when known for position attributes.
135    pub position_min: Option<Vec<f64>>,
136    /// Maximum position components when known for position attributes.
137    pub position_max: Option<Vec<f64>>,
138}
139
140impl GeometryEncoder for MeshEncoder {
141    fn point_cloud(&self) -> Option<&PointCloud> {
142        self.mesh.as_ref().map(|m| m as &PointCloud)
143    }
144
145    fn mesh(&self) -> Option<&Mesh> {
146        self.mesh.as_ref()
147    }
148
149    fn corner_table(&self) -> Option<&CornerTable> {
150        self.active_corner_table
151            .as_ref()
152            .or(self.corner_table.as_ref())
153    }
154
155    fn options(&self) -> &EncoderOptions {
156        &self.options
157    }
158
159    fn get_geometry_type(&self) -> EncodedGeometryType {
160        EncodedGeometryType::TriangularMesh
161    }
162
163    fn get_encoding_method(&self) -> Option<i32> {
164        Some(self.method)
165    }
166
167    fn get_data_to_corner_map(&self) -> Option<&[u32]> {
168        self.active_data_to_corner_map
169            .as_deref()
170            .or(self.data_to_corner_map.as_deref())
171    }
172
173    fn get_vertex_to_data_map(&self) -> Option<&[i32]> {
174        self.active_vertex_to_data_map
175            .as_deref()
176            .or(self.vertex_to_data_map.as_deref())
177    }
178
179    fn get_portable_attribute(&self, att_id: i32) -> Option<&PointAttribute> {
180        // Falls back to the attribute itself when it has no portable form, as
181        // SequentialAttributeEncoder::GetPortableAttribute does. An attribute
182        // that needs no transform -- an already-integral one, say -- is its own
183        // portable form upstream, and a predictor asking for it must get it
184        // rather than a null it would treat as a failure.
185        self.portable_attributes
186            .iter()
187            .find(|(id, _)| *id == att_id)
188            .map(|(_, att)| att)
189            .or_else(|| {
190                self.mesh
191                    .as_ref()
192                    .and_then(|mesh| mesh.try_attribute(att_id).ok())
193            })
194    }
195}
196
197impl MeshEncoder {
198    /// Creates an encoder without an assigned mesh.
199    pub fn new() -> Self {
200        Self {
201            mesh: None,
202            options: EncoderOptions::default(),
203            num_encoded_faces: 0,
204            corner_table: None,
205            point_ids: Vec::new(),
206            data_to_corner_map: None,
207            vertex_to_data_map: None,
208            edgebreaker_attribute_connectivity: Vec::new(),
209            active_corner_table: None,
210            active_data_to_corner_map: None,
211            active_vertex_to_data_map: None,
212            attribute_traversal: None,
213            portable_attributes: Vec::new(),
214            edgebreaker_encoder: None,
215            method: 0,
216            point_to_vertex_map: None,
217            use_single_connectivity: false,
218            encoded_mesh_info: None,
219        }
220    }
221
222    /// Assigns the mesh to encode.
223    pub fn set_mesh(&mut self, mesh: Mesh) {
224        self.mesh = Some(mesh);
225    }
226
227    /// Drops everything the previous encode derived from its mesh.
228    ///
229    /// An encoder is reusable - `set_mesh` then `encode`, twice - and each
230    /// encode caches connectivity for the attribute stage to read back:
231    /// a corner table, a point order, corner and vertex maps, per-attribute
232    /// seam connectivity. Only some of that is rewritten by every path. The
233    /// sequential connectivity branch does not build a corner table, so after
234    /// an EdgeBreaker encode it inherited the previous mesh's one and wrote
235    /// attributes against topology the stream does not describe: encoding an
236    /// attributed mesh with EdgeBreaker and then a plain mesh sequentially
237    /// with the same encoder produced a stream this crate's own decoder
238    /// rejects. Resetting in one place is the fix that does not depend on
239    /// every future path remembering to.
240    fn reset_derived_state(&mut self) {
241        self.encoded_mesh_info = None;
242        self.portable_attributes.clear();
243        self.edgebreaker_encoder = None;
244        self.num_encoded_faces = 0;
245        self.corner_table = None;
246        self.point_ids.clear();
247        self.data_to_corner_map = None;
248        self.vertex_to_data_map = None;
249        self.edgebreaker_attribute_connectivity.clear();
250        self.active_corner_table = None;
251        self.active_data_to_corner_map = None;
252        self.active_vertex_to_data_map = None;
253        self.attribute_traversal = None;
254        self.method = 0;
255        self.point_to_vertex_map = None;
256        self.use_single_connectivity = false;
257    }
258
259    /// Returns the assigned mesh, if any.
260    pub fn mesh(&self) -> Option<&Mesh> {
261        self.mesh.as_ref()
262    }
263
264    /// Returns the number of faces encoded by the last successful encode.
265    pub fn num_encoded_faces(&self) -> usize {
266        self.num_encoded_faces
267    }
268
269    /// Returns the corner table built during the last mesh encode, if any.
270    pub fn corner_table(&self) -> Option<&CornerTable> {
271        self.corner_table.as_ref()
272    }
273
274    /// Returns information captured during the last successful mesh encode.
275    pub fn encoded_mesh_info(&self) -> Option<&EncodedMeshInfo> {
276        self.encoded_mesh_info.as_ref()
277    }
278
279    /// Encodes the assigned mesh into an output buffer.
280    ///
281    /// A mesh must have been provided with [`set_mesh`](MeshEncoder::set_mesh)
282    /// first. On success the bitstream is appended to `out_buffer` and
283    /// [`encoded_mesh_info`](MeshEncoder::encoded_mesh_info) is populated.
284    ///
285    /// # Errors
286    ///
287    /// Returns an error if no mesh was set, if the requested encoding method or
288    /// options are unsupported, or if attribute encoding fails.
289    pub fn encode(&mut self, options: &EncoderOptions, out_buffer: &mut EncoderBuffer) -> Status {
290        self.options = options.clone();
291        self.reset_derived_state();
292
293        if self.mesh.is_none() {
294            return Err(DracoError::DracoError("Mesh not set".to_string()));
295        }
296        crate::point_cloud_encoder::validate_encodable_attributes(self.mesh.as_ref().unwrap())?;
297        let (major, minor) = self.options.get_version();
298        crate::version::validate_encodable_version(major, minor, DEFAULT_MESH_VERSION)?;
299        Self::validate_face_indices(self.mesh.as_ref().unwrap())?;
300        self.validate_predictive_traversal()?;
301
302        // 1. Encode Header
303        self.encode_header(out_buffer)?;
304        self.encode_metadata(out_buffer)?;
305
306        // 2. Encode geometry data (connectivity + attributes)
307        self.encode_geometry_data(out_buffer)?;
308
309        Ok(())
310    }
311
312    fn encode_metadata(&self, buffer: &mut EncoderBuffer) -> Status {
313        if let Some(metadata) = self
314            .mesh
315            .as_ref()
316            .and_then(|mesh| mesh.metadata())
317            .filter(|metadata| !metadata.is_empty())
318        {
319            metadata.encode(buffer)?;
320        }
321        Ok(())
322    }
323
324    /// Rejects the legacy predictive traversal on a version that cannot carry
325    /// it.
326    ///
327    /// `force_predictive_traversal` round-trips pre-0.10.0 connectivity and
328    /// only belongs in a target version below 2.0, which the encoder's own
329    /// comment said and nothing checked. Set on a current-version encode, it
330    /// produced a type-1 traversal inside a 2.x stream - which the decoder
331    /// refuses, since 2.x connectivity has no predictive traversal to read.
332    fn validate_predictive_traversal(&self) -> Status {
333        if self.options.get_global_int("force_predictive_traversal", 0) == 0 {
334            return Ok(());
335        }
336        let (mut major, mut minor) = self.options.get_version();
337        if major == 0 && minor == 0 {
338            (major, minor) = DEFAULT_MESH_VERSION;
339        }
340        if !crate::version::version_less_than(major, minor, (2, 0)) {
341            return Err(DracoError::UnsupportedFeature(format!(
342                "force_predictive_traversal requires a target bitstream version below 2.0, \
343                 not {major}.{minor}"
344            )));
345        }
346        Ok(())
347    }
348
349    /// Rejects a face that references a point the mesh does not have.
350    ///
351    /// The index buffer is the other half of caller-supplied geometry, and
352    /// nothing between a file and this encoder re-checks it against the point
353    /// count. Both connectivity paths then use face indices to index
354    /// point-sized arrays directly - `point_to_vertex[face[j]]` in the corner
355    /// table build is the shortest route to it - so an out-of-range index is a
356    /// panic rather than an encode failure. One pass here answers for every
357    /// such use.
358    fn validate_face_indices(mesh: &Mesh) -> Status {
359        let num_points = mesh.num_points();
360        for face_id in 0..mesh.num_faces() {
361            let face = mesh.face(FaceIndex(face_id as u32));
362            for index in face {
363                if index.0 as usize >= num_points {
364                    return Err(DracoError::DracoError(format!(
365                        "Face {face_id} references point {} but the mesh has {num_points} points",
366                        index.0
367                    )));
368                }
369            }
370        }
371        Ok(())
372    }
373
374    fn encode_header(&self, buffer: &mut EncoderBuffer) -> Status {
375        let (mut major, mut minor) = self.options.get_version();
376        if major == 0 && minor == 0 {
377            // Default to latest mesh version
378            (major, minor) = DEFAULT_MESH_VERSION;
379        }
380        let has_metadata = self
381            .mesh
382            .as_ref()
383            .and_then(|mesh| mesh.metadata())
384            .is_some_and(|metadata| !metadata.is_empty());
385
386        if has_metadata && !has_header_flags(major, minor) {
387            return Err(DracoError::UnsupportedVersion(
388                "Metadata requires Draco bitstream version 1.3 or newer".to_string(),
389            ));
390        }
391
392        // C++ default behavior: Edgebreaker if speed != 10, Sequential if speed == 10
393        let method_int = self.options.get_global_int("encoding_method", -1);
394        let method = if method_int == -1 {
395            if self.options.get_speed() == 10 {
396                0
397            } else {
398                1
399            }
400        } else if method_int == 1 {
401            1
402        } else {
403            0
404        };
405
406        #[cfg(not(feature = "legacy_bitstream_encode"))]
407        if method == 1 {
408            let bitstream_version = crate::version::bitstream_version(major, minor);
409            if bitstream_version < 0x0202 {
410                return Err(DracoError::UnsupportedVersion(
411                    "EdgeBreaker mesh encoding before bitstream 2.2 requires the \
412                     legacy_bitstream_encode feature"
413                        .to_string(),
414                ));
415            }
416            if self.options.get_global_int("force_predictive_traversal", 0) != 0 {
417                return Err(DracoError::UnsupportedFeature(
418                    "force_predictive_traversal requires the legacy_bitstream_encode feature"
419                        .to_string(),
420                ));
421            }
422        }
423        #[cfg(not(feature = "legacy_bitstream_encode"))]
424        match self.options.get_prediction_scheme() {
425            2 | 3 => {
426                return Err(DracoError::UnsupportedFeature(
427                    "legacy prediction schemes require the legacy_bitstream_encode feature"
428                        .to_string(),
429                ));
430            }
431            _ => {}
432        }
433
434        buffer.encode_data(b"DRACO");
435
436        buffer.encode_u8(major);
437        buffer.encode_u8(minor);
438        buffer.set_version(major, minor);
439        buffer.encode_u8(self.get_geometry_type() as u8);
440        buffer.encode_u8(method);
441
442        // The flags field is always present in the binary header (the decoder reads
443        // it unconditionally); only the metadata bit gains meaning at v1.3+, which
444        // is guarded by the metadata check above. Emitting it only for >= 1.3 left
445        // pre-1.3 streams two bytes short, misaligning the rest of the stream.
446        let flags = if has_metadata { METADATA_FLAG_MASK } else { 0 };
447        buffer.encode_u16(flags);
448        Ok(())
449    }
450
451    fn encode_geometry_data(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
452        // First encode connectivity
453        self.encode_connectivity(out_buffer)?;
454
455        // Check if we should store the number of encoded faces
456        if self
457            .options
458            .get_global_int("store_number_of_encoded_faces", 0)
459            != 0
460        {
461            self.compute_number_of_encoded_faces();
462        }
463
464        // Then encode attributes
465        self.encode_attributes(out_buffer)?;
466        self.build_encoded_mesh_info()?;
467
468        Ok(())
469    }
470
471    fn encode_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
472        let mesh = self
473            .mesh
474            .as_ref()
475            .expect("mesh must be set before encoding");
476
477        // Determine encoding method FIRST (before building corner table)
478        let method_int = self.options.get_global_int("encoding_method", -1);
479        let method = if method_int == -1 {
480            if self.options.get_speed() == 10 {
481                MeshEncodingMethod::MeshSequentialEncoding
482            } else {
483                MeshEncodingMethod::MeshEdgebreakerEncoding
484            }
485        } else if method_int == 1 {
486            MeshEncodingMethod::MeshEdgebreakerEncoding
487        } else {
488            MeshEncodingMethod::MeshSequentialEncoding
489        };
490        self.method = if method == MeshEncodingMethod::MeshEdgebreakerEncoding {
491            1
492        } else {
493            0
494        };
495
496        // C++ behavior: use_single_connectivity_ when speed >= 6
497        // When false (speed < 6), use position attribute to deduplicate vertices
498        let speed = self.options.get_speed();
499        // Check if split_mesh_on_seams is explicitly set, otherwise use speed-based default
500        let split_on_seams_explicit = self.options.get_global_int("split_mesh_on_seams", -1);
501        let use_single_connectivity = if split_on_seams_explicit >= 0 {
502            split_on_seams_explicit != 0
503        } else {
504            speed >= 6
505        };
506
507        // Only build corner table if needed (not for sequential encoding)
508        if method == MeshEncodingMethod::MeshEdgebreakerEncoding {
509            let (faces, point_to_vertex_map) = if use_single_connectivity {
510                // CreateCornerTableFromAllAttributes: use point indices directly
511                let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
512                    .map(|i| {
513                        let face = mesh.face(FaceIndex(i as u32));
514                        [
515                            crate::geometry_indices::VertexIndex(face[0].0),
516                            crate::geometry_indices::VertexIndex(face[1].0),
517                            crate::geometry_indices::VertexIndex(face[2].0),
518                        ]
519                    })
520                    .collect();
521                // Identity mapping
522                let point_to_vertex: Vec<u32> = (0..mesh.num_points() as u32).collect();
523                (faces, point_to_vertex)
524            } else {
525                // CreateCornerTableFromPositionAttribute: use position attribute to deduplicate
526                self.create_corner_table_from_position_attribute(mesh)
527            };
528
529            // Initialize corner table for the mesh
530            let mut corner_table = CornerTable::new(0);
531            corner_table.init(&faces);
532
533            // A mesh whose every face is degenerate has no connectivity to
534            // traverse: `point_ids` comes back empty, and everything downstream
535            // that assumes at least one encoded point panics rather than
536            // failing cleanly. C++ rejects the same input outright --
537            // `MeshEdgebreakerEncoderImpl::Init` checks
538            // `num_faces() == NumDegeneratedFaces()` before doing anything else.
539            if corner_table.num_faces() > 0
540                && corner_table.num_faces() == corner_table.num_degenerated_faces()
541            {
542                return Err(DracoError::DracoError(
543                    "All triangles are degenerate.".to_string(),
544                ));
545            }
546
547            self.corner_table = Some(corner_table);
548            self.point_to_vertex_map = Some(point_to_vertex_map);
549            self.edgebreaker_attribute_connectivity.clear();
550            if !use_single_connectivity {
551                if let Some(ref ct) = self.corner_table {
552                    for i in 0..mesh.num_attributes() {
553                        let att = mesh.attribute(i);
554                        if att.attribute_type() != GeometryAttributeType::Position {
555                            self.edgebreaker_attribute_connectivity
556                                .push(EdgebreakerAttributeConnectivity::build(mesh, ct, i));
557                        }
558                    }
559                }
560            }
561        } else {
562            // Sequential encoding: no corner table needed, use identity mapping
563            let point_to_vertex: Vec<u32> = (0..mesh.num_points() as u32).collect();
564            self.point_to_vertex_map = Some(point_to_vertex);
565            self.edgebreaker_attribute_connectivity.clear();
566        }
567        self.use_single_connectivity = use_single_connectivity;
568
569        match method {
570            MeshEncodingMethod::MeshSequentialEncoding => {
571                self.encode_sequential_connectivity(out_buffer)
572            }
573            MeshEncodingMethod::MeshEdgebreakerEncoding => {
574                self.encode_edgebreaker_connectivity(out_buffer)
575            }
576        }
577    }
578
579    fn encode_edgebreaker_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
580        let mesh = self
581            .mesh
582            .as_ref()
583            .expect("mesh must be set before encoding");
584        let corner_table = self
585            .corner_table
586            .as_ref()
587            .expect("corner_table must be set before edgebreaker encoding");
588
589        let mut encoder = MeshEdgebreakerEncoder::new(mesh.num_faces(), mesh.num_points());
590        // Opt-in legacy predictive (type-1) traversal, for round-tripping the
591        // pre-0.10.0 connectivity. Requires a < 2.0 target version.
592        #[cfg(feature = "legacy_bitstream_encode")]
593        encoder.set_force_predictive(
594            self.options.get_global_int("force_predictive_traversal", 0) == 1,
595        );
596        let (point_ids, data_to_corner_map, vertex_to_data_map) = encoder.encode_connectivity(
597            mesh,
598            corner_table,
599            &self.edgebreaker_attribute_connectivity,
600            out_buffer,
601            self.options.get_speed() as usize,
602            self.use_single_connectivity,
603        )?;
604        #[cfg(feature = "debug_logs")]
605        {
606            debug_log!("DEBUG: encode_edgebreaker_connectivity: point_ids.len()={}, data_to_corner_map.len()={}, vertex_to_data_map.len()={}",
607                 point_ids.len(), data_to_corner_map.len(), vertex_to_data_map.len());
608        }
609        // At speed 0 the position walks the mesh by max prediction degree while
610        // every other attribute stays depth first, so the two orders part ways
611        // and the non-position groups need their own. At any other speed the
612        // position order already is the depth-first one.
613        self.attribute_traversal = if self.options.get_speed() == 0 && mesh.num_attributes() > 1 {
614            Some(encoder.generate_depth_first_traversal(mesh, corner_table))
615        } else {
616            None
617        };
618
619        self.point_ids = point_ids;
620
621        // Draco stores corner mapping in attribute (data) order.
622        self.data_to_corner_map = Some(data_to_corner_map);
623        self.vertex_to_data_map = Some(vertex_to_data_map);
624
625        // Held for the corner order it carries: an attribute with interior
626        // seams walks its own corner table seeded from that order, and this is
627        // the last point at which it exists.
628        self.edgebreaker_encoder = Some(encoder);
629
630        Ok(())
631    }
632
633    /// Creates faces array using position attribute to deduplicate vertices.
634    /// This mimics C++ CreateCornerTableFromPositionAttribute.
635    /// Returns (faces, point_to_vertex_map) where:
636    /// - faces: vertex indices (deduplicated based on position values)
637    /// - point_to_vertex_map: maps each point index to its vertex index in the corner table
638    fn create_corner_table_from_position_attribute(
639        &self,
640        mesh: &Mesh,
641    ) -> (Vec<[crate::geometry_indices::VertexIndex; 3]>, Vec<u32>) {
642        use crate::geometry_attribute::GeometryAttributeType;
643
644        let pos_att_id = mesh.named_attribute_id(GeometryAttributeType::Position);
645        if pos_att_id < 0 {
646            // No position attribute, fall back to identity mapping
647            let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
648                .map(|i| {
649                    let face = mesh.face(FaceIndex(i as u32));
650                    [
651                        crate::geometry_indices::VertexIndex(face[0].0),
652                        crate::geometry_indices::VertexIndex(face[1].0),
653                        crate::geometry_indices::VertexIndex(face[2].0),
654                    ]
655                })
656                .collect();
657            let point_to_vertex: Vec<u32> = (0..mesh.num_points() as u32).collect();
658            return (faces, point_to_vertex);
659        }
660
661        let pos_att = mesh.attribute(pos_att_id);
662        let _buffer = pos_att.buffer();
663        let num_components = pos_att.num_components() as usize;
664        let _byte_stride = match pos_att.data_type() {
665            crate::draco_types::DataType::Float32 => num_components * 4,
666            crate::draco_types::DataType::Float64 => num_components * 8,
667            crate::draco_types::DataType::Int8 | crate::draco_types::DataType::Uint8 => {
668                num_components
669            }
670            crate::draco_types::DataType::Int16 | crate::draco_types::DataType::Uint16 => {
671                num_components * 2
672            }
673            crate::draco_types::DataType::Int32 | crate::draco_types::DataType::Uint32 => {
674                num_components * 4
675            }
676            crate::draco_types::DataType::Int64 | crate::draco_types::DataType::Uint64 => {
677                num_components * 8
678            }
679            _ => num_components * 4, // Default to 4 bytes per component
680        };
681
682        // Use attribute mapped indices directly to build point->vertex map. This mirrors
683        // C++ CreateCornerTableFromAttribute which uses att->mapped_index(face[j]).
684        let mut point_to_vertex: Vec<u32> = vec![0; mesh.num_points()];
685        for i in 0..mesh.num_points() {
686            let pt = PointIndex(i as u32);
687            let val_idx = pos_att.mapped_index(pt);
688            point_to_vertex[i] = val_idx.0;
689        }
690
691        // Build faces using attribute mapped indices (exact same mapping as C++).
692        let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
693            .map(|i| {
694                let face = mesh.face(FaceIndex(i as u32));
695                [
696                    crate::geometry_indices::VertexIndex(point_to_vertex[face[0].0 as usize]),
697                    crate::geometry_indices::VertexIndex(point_to_vertex[face[1].0 as usize]),
698                    crate::geometry_indices::VertexIndex(point_to_vertex[face[2].0 as usize]),
699                ]
700            })
701            .collect();
702
703        #[cfg(feature = "debug_logs")]
704        {
705            debug_log!(
706                "Rust created faces (first 12): {:?}",
707                faces
708                    .iter()
709                    .take(12)
710                    .map(|f| [f[0].0, f[1].0, f[2].0])
711                    .collect::<Vec<_>>()
712            );
713            debug_log!(
714                "Rust point_to_vertex (first 25): {:?}",
715                point_to_vertex.iter().take(25).cloned().collect::<Vec<_>>()
716            );
717        }
718        (faces, point_to_vertex)
719    }
720
721    fn encode_sequential_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
722        let mesh = self
723            .mesh
724            .as_ref()
725            .expect("mesh must be set before encoding");
726
727        // Encode the number of faces and points
728        // Use the buffer's version (set in encode_header) for version checks
729        let major = out_buffer.version_major();
730        let minor = out_buffer.version_minor();
731        if !uses_varint_encoding(major, minor) {
732            out_buffer.encode_u32(mesh.num_faces() as u32);
733            out_buffer.encode_u32(mesh.num_points() as u32);
734        } else {
735            out_buffer.encode_varint(mesh.num_faces() as u64);
736            out_buffer.encode_varint(mesh.num_points() as u64);
737        }
738
739        if mesh.num_faces() > 0 && mesh.num_points() > 0 {
740            out_buffer.encode_u8(1); // Raw connectivity
741            if mesh.num_points() < 256 {
742                for face_id in 0..mesh.num_faces() {
743                    let face = mesh.face(FaceIndex(face_id as u32));
744                    for i in 0..3 {
745                        out_buffer.encode_u8(face[i].0 as u8);
746                    }
747                }
748            } else if mesh.num_points() < 65536 {
749                for face_id in 0..mesh.num_faces() {
750                    let face = mesh.face(FaceIndex(face_id as u32));
751                    for i in 0..3 {
752                        out_buffer.encode_u16(face[i].0 as u16);
753                    }
754                }
755            } else if mesh.num_points() < (1 << 21) {
756                // Use varint encoding for indices when points fit in 21 bits
757                // This matches C++ behavior for better compression
758                for face_id in 0..mesh.num_faces() {
759                    let face = mesh.face(FaceIndex(face_id as u32));
760                    for i in 0..3 {
761                        out_buffer.encode_varint(face[i].0 as u64);
762                    }
763                }
764            } else {
765                // Default: use u32 for very large meshes
766                for face_id in 0..mesh.num_faces() {
767                    let face = mesh.face(FaceIndex(face_id as u32));
768                    for i in 0..3 {
769                        out_buffer.encode_u32(face[i].0);
770                    }
771                }
772            }
773        }
774
775        // Identity permutation for sequential encoding
776        self.point_ids = (0..mesh.num_points())
777            .map(|i| PointIndex(i as u32))
778            .collect();
779
780        Ok(())
781    }
782
783    fn encode_attributes(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
784        // NOTE: Unlike the decoder, the encoder does NOT need to apply UpdatePointToAttributeIndexMapping
785        // because the attribute still has identity mapping. The encoder uses the point_ids array
786        // (from edgebreaker traversal) to determine the order in which to process points, and
787        // mapped_index with identity mapping just returns the point index directly.
788
789        let mesh = self
790            .mesh
791            .as_ref()
792            .expect("mesh must be set before encoding");
793
794        let method_int = self.options.get_global_int("encoding_method", -1);
795        // Match C++ behavior: if encoding_method is not set (-1),
796        // use Edgebreaker for all options except speed == 10
797        let is_edgebreaker = if method_int == -1 {
798            self.options.get_speed() != 10
799        } else {
800            method_int == 1
801        };
802
803        if is_edgebreaker && !self.use_single_connectivity {
804            return self.encode_edgebreaker_attributes_split(out_buffer);
805        }
806
807        // Encode number of attribute decoders (u8).
808        // For both sequential and edgebreaker with single-connectivity mode:
809        // there's only ONE attribute encoder containing ALL attributes.
810        // This matches C++ behavior when use_single_connectivity_ = true (speed >= 6).
811        let num_attributes = mesh.num_attributes();
812        let num_encoders = if num_attributes > 0 { 1 } else { 0 };
813        // Use the buffer's version (set in encode_header) for version checks.
814        let major = out_buffer.version_major();
815        let minor = out_buffer.version_minor();
816
817        out_buffer.encode_u8(num_encoders as u8);
818
819        // Phase 1: attributes decoder identifiers.
820        // For single-encoder mode: one encoder with att_data_id = -1 (uses position connectivity)
821        if num_encoders > 0 && is_edgebreaker {
822            // att_data_id (i8), encoder_type (u8), traversal_method (u8)
823            // -1 means use position connectivity (single connectivity mode)
824            out_buffer.encode_u8((-1i8) as u8); // att_data_id = -1
825            out_buffer.encode_u8(0); // element_type = MESH_VERTEX_ATTRIBUTE
826
827            // Traversal method was added in bitstream 1.2. Older streams
828            // default to DEPTH_FIRST on decode and must not carry the byte.
829            if crate::version::bitstream_version(major, minor) >= 0x0102 {
830                // PREDICTION_DEGREE (1) for speed 0, DEPTH_FIRST (0) otherwise.
831                // This must match the traversal used in MeshEdgebreakerEncoder.
832                let encoding_speed = self.options.get_speed();
833                let traversal_method: u8 = if encoding_speed == 0 { 1 } else { 0 };
834                out_buffer.encode_u8(traversal_method);
835            }
836        }
837        // For sequential, nothing is written in phase 1 (EncodeAttributesEncoderIdentifier does nothing)
838
839        let mut decoder_types: Vec<u8> = Vec::with_capacity(mesh.num_attributes() as usize);
840
841        // Phase 2: Encode attribute encoder data
842        // Both sequential and edgebreaker now use single-encoder mode:
843        //   - Write num_attrs = total attributes
844        //   - Write all attribute metadata
845        //   - Write all decoder types
846
847        if num_encoders > 0 {
848            // Single encoder with all attributes (single-connectivity mode for edgebreaker)
849            // Write num_attrs = total number of attributes
850            if !uses_varint_encoding(major, minor) {
851                out_buffer.encode_u32(mesh.num_attributes() as u32);
852            } else {
853                out_buffer.encode_varint(mesh.num_attributes() as u64);
854            }
855
856            // Write all attribute metadata first
857            for i in 0..mesh.num_attributes() {
858                let att = mesh.attribute(i);
859
860                #[cfg(feature = "debug_logs")]
861                {
862                    debug_log!("DEBUG: Encoder encoding attribute {} metadata. Type: {:?}, Components: {}, Data: {:?}", i, att.attribute_type(), att.num_components(), att.data_type());
863                }
864                out_buffer.encode_u8(att.attribute_type() as u8);
865                out_buffer.encode_u8(att.data_type() as u8);
866                out_buffer.encode_u8(att.num_components());
867                out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
868
869                if !uses_varint_unique_id(major, minor) {
870                    out_buffer.encode_u16(att.unique_id() as u16);
871                } else {
872                    out_buffer.encode_varint(att.unique_id() as u64);
873                }
874            }
875
876            // Write all decoder types after all metadata (SequentialAttributeEncodersController pattern)
877            for i in 0..mesh.num_attributes() {
878                let att = mesh.attribute(i);
879                let quantization_bits = self.options.get_attribute_int(i, "quantization_bits", -1);
880                let decoder_type = select_sequential_encoder(att, quantization_bits) as u8;
881                out_buffer.encode_u8(decoder_type);
882                decoder_types.push(decoder_type);
883            }
884        }
885
886        // Phase 3: Encode attribute values (all attributes first)
887        // C++ order: all EncodePortableAttribute calls, then all EncodeDataNeededByPortableTransform calls
888
889        // Store transforms and encoders for later use in transform data encoding
890        let mut quantization_transforms: Vec<Option<AttributeQuantizationTransform>> = Vec::new();
891        let mut portable_attributes: Vec<Option<PointAttribute>> = Vec::new();
892        let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> = Vec::new();
893
894        // First pass: encode all attribute VALUES
895        for i in 0..mesh.num_attributes() {
896            let att = mesh.attribute(i);
897            let decoder_type = decoder_types[i as usize];
898            let quantization_bits = self.options.get_attribute_int(i, "quantization_bits", -1);
899
900            match decoder_type {
901                3 => {
902                    // Normal attribute with octahedral encoding
903                    let mut encoder = SequentialNormalAttributeEncoder::new();
904                    if !encoder.init(
905                        self.point_cloud().expect("point_cloud set"),
906                        i,
907                        &self.options,
908                    ) {
909                        return Err(DracoError::DracoError(
910                            "Failed to init normal encoder".to_string(),
911                        ));
912                    }
913                    if !encoder.encode_values(
914                        self.point_cloud().expect("point_cloud set"),
915                        &self.point_ids,
916                        out_buffer,
917                        &self.options,
918                        self,
919                    ) {
920                        return Err(DracoError::DracoError(
921                            "Failed to encode normal values".to_string(),
922                        ));
923                    }
924                    normal_encoders.push(Some(encoder));
925                    quantization_transforms.push(None);
926                    portable_attributes.push(None);
927                }
928                2 => {
929                    // Quantized attribute (mapping already applied at start of encode_attributes)
930                    let mut q_transform = AttributeQuantizationTransform::new();
931                    if !q_transform.compute_parameters(att, quantization_bits) {
932                        return Err(DracoError::DracoError(
933                            "Failed to compute quantization parameters".to_string(),
934                        ));
935                    }
936                    let mut portable = PointAttribute::default();
937                    if !q_transform.transform_attribute(att, &self.point_ids, &mut portable) {
938                        return Err(DracoError::DracoError(
939                            "Failed to quantize attribute".to_string(),
940                        ));
941                    }
942
943                    let mut att_encoder = SequentialIntegerAttributeEncoder::new();
944                    att_encoder.init(i);
945                    if !att_encoder.encode_values(
946                        mesh as &PointCloud,
947                        &self.point_ids,
948                        out_buffer,
949                        &self.options,
950                        self,
951                        Some(&portable),
952                        true,
953                    ) {
954                        return Err(DracoError::DracoError(format!(
955                            "Failed to encode attribute {}",
956                            i
957                        )));
958                    }
959
960                    quantization_transforms.push(Some(q_transform));
961                    portable_attributes.push(Some(portable));
962                    normal_encoders.push(None);
963                }
964                1 => {
965                    // Integer attribute
966                    let mut att_encoder = SequentialIntegerAttributeEncoder::new();
967                    att_encoder.init(i);
968                    if !att_encoder.encode_values(
969                        mesh as &PointCloud,
970                        &self.point_ids,
971                        out_buffer,
972                        &self.options,
973                        self,
974                        None,
975                        true,
976                    ) {
977                        return Err(DracoError::DracoError(format!(
978                            "Failed to encode attribute {}",
979                            i
980                        )));
981                    }
982                    quantization_transforms.push(None);
983                    portable_attributes.push(None);
984                    normal_encoders.push(None);
985                }
986                0 => {
987                    // Generic/float attribute
988                    let mut att_encoder = SequentialAttributeEncoder::new();
989                    att_encoder.init(i);
990                    if !att_encoder.encode_values(mesh as &PointCloud, &self.point_ids, out_buffer)
991                    {
992                        return Err(DracoError::DracoError(format!(
993                            "Failed to encode attribute {}",
994                            i
995                        )));
996                    }
997                    quantization_transforms.push(None);
998                    portable_attributes.push(None);
999                    normal_encoders.push(None);
1000                }
1001                _ => {
1002                    return Err(DracoError::DracoError(format!(
1003                        "Unsupported encoder type {}",
1004                        decoder_type
1005                    )));
1006                }
1007            }
1008        }
1009
1010        // Second pass: encode all TRANSFORM DATA
1011        for i in 0..mesh.num_attributes() {
1012            let decoder_type = decoder_types[i as usize];
1013
1014            match decoder_type {
1015                3 => {
1016                    // Normal attribute - encode octahedral transform data
1017                    let bitstream_version = crate::version::bitstream_version(major, minor);
1018                    if bitstream_version != 0 && bitstream_version < 0x0200 {
1019                        continue;
1020                    }
1021                    if let Some(ref encoder) = normal_encoders[i as usize] {
1022                        if !encoder.encode_data_needed_by_portable_transform(out_buffer) {
1023                            return Err(DracoError::DracoError(
1024                                "Failed to encode normal transform data".to_string(),
1025                            ));
1026                        }
1027                    }
1028                }
1029                2 => {
1030                    // Quantized attribute - encode quantization parameters
1031                    if let Some(ref q_transform) = quantization_transforms[i as usize] {
1032                        if !q_transform.encode_parameters(out_buffer) {
1033                            return Err(DracoError::DracoError(
1034                                "Failed to encode quantization parameters".to_string(),
1035                            ));
1036                        }
1037                    }
1038                }
1039                1 | 0 => {
1040                    // No transform data for integer/generic attributes
1041                }
1042                _ => {}
1043            }
1044        }
1045
1046        Ok(())
1047    }
1048
1049    fn encode_edgebreaker_attributes_split(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
1050        let mesh = self
1051            .mesh
1052            .as_ref()
1053            .expect("mesh must be set before encoding");
1054        let mut groups: Vec<(i8, Vec<i32>)> = Vec::new();
1055        let mut position_attrs = Vec::new();
1056        for i in 0..mesh.num_attributes() {
1057            if mesh.attribute(i).attribute_type() == GeometryAttributeType::Position {
1058                position_attrs.push(i);
1059            }
1060        }
1061        if !position_attrs.is_empty() {
1062            groups.push((-1, position_attrs));
1063        }
1064        for (data_id, attr_conn) in self.edgebreaker_attribute_connectivity.iter().enumerate() {
1065            groups.push((data_id as i8, vec![attr_conn.attribute_id]));
1066        }
1067
1068        // The group count is one byte in the bitstream, so a mesh needing more
1069        // groups than that cannot be described. Truncating wrote a stream that
1070        // decodes as a different mesh: 261 groups became 5, and the decoder
1071        // read the sixth group's bytes as attribute data. Measured at the
1072        // boundary rather than assumed - 256 groups is the first count that
1073        // breaks, and everything below it round-trips, including the counts
1074        // where the per-group `i8` data id goes negative.
1075        if groups.len() > u8::MAX as usize {
1076            return Err(DracoError::DracoError(format!(
1077                "Mesh needs {} attribute groups but the bitstream field holds {}",
1078                groups.len(),
1079                u8::MAX
1080            )));
1081        }
1082        out_buffer.encode_u8(groups.len() as u8);
1083
1084        let major = out_buffer.version_major();
1085        let minor = out_buffer.version_minor();
1086        let writes_traversal_method = crate::version::bitstream_version(major, minor) >= 0x0102;
1087        // Prediction degree is the position group's traversal alone, and only at
1088        // speed 0. Every other group is walked depth first, whatever the speed --
1089        // upstream guards on the attribute being POSITION, and the groups here
1090        // carry att_data_id -1 for exactly that one. Declaring it for the rest
1091        // mislabels a stream whose values were written in depth-first order.
1092        let position_prediction_degree = self.options.get_speed() == 0
1093            && !(self.use_single_connectivity && mesh.num_attributes() > 1);
1094        for (att_data_id, _) in &groups {
1095            out_buffer.encode_u8(*att_data_id as u8);
1096            let element_type = if *att_data_id >= 0
1097                && !self.edgebreaker_attribute_connectivity[*att_data_id as usize].no_interior_seams
1098            {
1099                1 // MESH_CORNER_ATTRIBUTE
1100            } else {
1101                0 // MESH_VERTEX_ATTRIBUTE
1102            };
1103            out_buffer.encode_u8(element_type);
1104            if writes_traversal_method {
1105                let is_position_group = *att_data_id < 0;
1106                let traversal_method: u8 = if position_prediction_degree && is_position_group {
1107                    1
1108                } else {
1109                    0
1110                };
1111                out_buffer.encode_u8(traversal_method);
1112            }
1113        }
1114
1115        let mut decoder_types_by_group: Vec<Vec<u8>> = Vec::with_capacity(groups.len());
1116
1117        for (_, attr_ids) in &groups {
1118            if !uses_varint_encoding(major, minor) {
1119                out_buffer.encode_u32(attr_ids.len() as u32);
1120            } else {
1121                out_buffer.encode_varint(attr_ids.len() as u64);
1122            }
1123
1124            for &att_id in attr_ids {
1125                let att = mesh.attribute(att_id);
1126                out_buffer.encode_u8(att.attribute_type() as u8);
1127                out_buffer.encode_u8(att.data_type() as u8);
1128                out_buffer.encode_u8(att.num_components());
1129                out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
1130                if !uses_varint_unique_id(major, minor) {
1131                    out_buffer.encode_u16(att.unique_id() as u16);
1132                } else {
1133                    out_buffer.encode_varint(att.unique_id() as u64);
1134                }
1135            }
1136
1137            let mut decoder_types = Vec::with_capacity(attr_ids.len());
1138            for &att_id in attr_ids {
1139                let decoder_type = self.decoder_type_for_attribute(att_id);
1140                out_buffer.encode_u8(decoder_type);
1141                decoder_types.push(decoder_type);
1142            }
1143            decoder_types_by_group.push(decoder_types);
1144        }
1145
1146        for (group_i, (att_data_id, attr_ids)) in groups.iter().enumerate() {
1147            let point_ids = if *att_data_id >= 0 {
1148                self.prepare_active_attribute_connectivity(*att_data_id as usize)?
1149            } else {
1150                self.active_corner_table = None;
1151                self.active_data_to_corner_map = None;
1152                self.active_vertex_to_data_map = None;
1153                self.point_ids.clone()
1154            };
1155
1156            self.encode_attribute_group_values(
1157                attr_ids,
1158                &decoder_types_by_group[group_i],
1159                &point_ids,
1160                out_buffer,
1161            )?;
1162        }
1163
1164        self.active_corner_table = None;
1165        self.active_data_to_corner_map = None;
1166        self.active_vertex_to_data_map = None;
1167        Ok(())
1168    }
1169
1170    fn decoder_type_for_attribute(&self, att_id: i32) -> u8 {
1171        let mesh = self
1172            .mesh
1173            .as_ref()
1174            .expect("mesh must be set before encoding");
1175        let att = mesh.attribute(att_id);
1176        let quantization_bits = self
1177            .options
1178            .get_attribute_int(att_id, "quantization_bits", -1);
1179        select_sequential_encoder(att, quantization_bits) as u8
1180    }
1181
1182    fn prepare_active_attribute_connectivity(
1183        &mut self,
1184        data_id: usize,
1185    ) -> Result<Vec<PointIndex>, DracoError> {
1186        let mesh = self
1187            .mesh
1188            .as_ref()
1189            .expect("mesh must be set before encoding");
1190        let base_ct = self
1191            .corner_table
1192            .as_ref()
1193            .ok_or_else(|| DracoError::DracoError("corner_table must be set".to_string()))?;
1194        let attr_conn = self
1195            .edgebreaker_attribute_connectivity
1196            .get(data_id)
1197            .ok_or_else(|| {
1198                DracoError::DracoError("Invalid attribute connectivity id".to_string())
1199            })?;
1200
1201        if attr_conn.no_interior_seams {
1202            // Same corner table as the position, but not necessarily the same
1203            // walk over it: `attribute_traversal` is set when the position took
1204            // the max-prediction-degree order and this attribute must not.
1205            self.active_corner_table = None;
1206            if let Some((point_ids, data_to_corner_map, vertex_to_data_map)) =
1207                self.attribute_traversal.clone()
1208            {
1209                self.active_data_to_corner_map = Some(data_to_corner_map);
1210                self.active_vertex_to_data_map = Some(vertex_to_data_map);
1211                return Ok(point_ids);
1212            }
1213            self.active_data_to_corner_map = None;
1214            self.active_vertex_to_data_map = None;
1215            return Ok(self.point_ids.clone());
1216        }
1217
1218        let mut attr_ct = base_ct.clone();
1219        for c_idx in 0..attr_conn.seam_edges.len() {
1220            if !attr_conn.seam_edges[c_idx] {
1221                continue;
1222            }
1223            let c = crate::geometry_indices::CornerIndex(c_idx as u32);
1224            let opp = attr_ct.opposite(c);
1225            if opp != crate::geometry_indices::INVALID_CORNER_INDEX {
1226                attr_ct.set_opposite(c, crate::geometry_indices::INVALID_CORNER_INDEX);
1227                attr_ct.set_opposite(opp, crate::geometry_indices::INVALID_CORNER_INDEX);
1228            }
1229        }
1230        let base_num_vertices = attr_ct.num_vertices();
1231        if !attr_ct.compute_vertex_corners(base_num_vertices) {
1232            return Err(DracoError::DracoError(
1233                "Failed to compute attribute seam corner table".to_string(),
1234            ));
1235        }
1236
1237        // Walk the attribute's own table depth first, seeded by the edgebreaker
1238        // corner order, as upstream does with
1239        // `DepthFirstTraverser<MeshAttributeCornerTable>` and
1240        // `SetCornerOrder(processed_connectivity_corners_)`.
1241        //
1242        // Enumerating `vertex_corners` instead, as this used to, yields the
1243        // identity permutation of attribute-vertex indices -- `vertex_corners[v]`
1244        // has vertex `v` by construction -- which is not an encoding order at
1245        // all. The decoder walks the table it rebuilds from the seam bits, so
1246        // the values came back attached to the wrong points.
1247        let Some(encoder) = self.edgebreaker_encoder.as_ref() else {
1248            return Err(DracoError::DracoError(
1249                "Attribute seams need the edgebreaker corner order".to_string(),
1250            ));
1251        };
1252        let (point_ids, data_to_corner_map, vertex_to_data_map) =
1253            encoder.generate_depth_first_traversal(mesh, &attr_ct);
1254
1255        self.active_corner_table = Some(attr_ct);
1256        self.active_data_to_corner_map = Some(data_to_corner_map);
1257        self.active_vertex_to_data_map = Some(vertex_to_data_map);
1258        Ok(point_ids)
1259    }
1260
1261    fn encode_attribute_group_values(
1262        &mut self,
1263        attr_ids: &[i32],
1264        decoder_types: &[u8],
1265        point_ids: &[PointIndex],
1266        out_buffer: &mut EncoderBuffer,
1267    ) -> Status {
1268        // Three passes over the group, one per step of C++
1269        // SequentialAttributeEncodersController: transform every attribute to its
1270        // portable form, encode them all, then encode the data their transforms
1271        // need. Each pass is marked below.
1272        //
1273        // Pass one, TransformAttributesToPortableFormat. It has to finish before
1274        // any attribute is encoded: a prediction scheme that reads a parent needs
1275        // the parent's portable values, and in a single pass the parent would not
1276        // exist yet for anything encoded ahead of it.
1277        let mut quantization_transforms: Vec<Option<AttributeQuantizationTransform>> = Vec::new();
1278        {
1279            let mesh = self
1280                .mesh
1281                .as_ref()
1282                .expect("mesh must be set before encoding");
1283            let mut portables: Vec<(i32, PointAttribute)> = Vec::new();
1284            for (local_i, &att_id) in attr_ids.iter().enumerate() {
1285                if decoder_types[local_i] != 2 {
1286                    quantization_transforms.push(None);
1287                    continue;
1288                }
1289                let att = mesh.attribute(att_id);
1290                let is_parent_attribute = att.attribute_type() == GeometryAttributeType::Position
1291                    && self.options.get_speed() < 4;
1292                let quantization_bits =
1293                    self.options
1294                        .get_attribute_int(att_id, "quantization_bits", -1);
1295                let mut q_transform = AttributeQuantizationTransform::new();
1296                if !q_transform.compute_parameters(att, quantization_bits) {
1297                    return Err(DracoError::DracoError(
1298                        "Failed to compute quantization parameters".to_string(),
1299                    ));
1300                }
1301                let mut portable = PointAttribute::default();
1302                if !q_transform.transform_attribute(att, point_ids, &mut portable) {
1303                    return Err(DracoError::DracoError(
1304                        "Failed to quantize attribute".to_string(),
1305                    ));
1306                }
1307
1308                // Rebuild the portable attribute's point map, as upstream does
1309                // in SequentialIntegerAttributeEncoder::TransformAttributeToPortableFormat.
1310                // The values were written in encoding order, but a prediction
1311                // scheme reads its parent as `mapped_index(point_id)`; without
1312                // this the lookup returns whichever vertex happens to sit at
1313                // that index in the traversal, and encoder and decoder predict
1314                // from different positions.
1315                //
1316                // Upstream guards this with `is_parent_encoder()`. The two
1317                // schemes that declare a parent -- tex coords portable and
1318                // geometric normal -- both name the position and are both
1319                // selected only below speed 4; every other scheme declares
1320                // none. So this is the same guard, decided up front rather than
1321                // by a flag set during scheme construction.
1322                if is_parent_attribute {
1323                    let num_points = mesh.num_points();
1324                    let mut value_to_value = vec![0u32; att.size().max(1)];
1325                    for (entry, &point_id) in point_ids.iter().enumerate() {
1326                        let src = att.mapped_index(point_id);
1327                        if (src.0 as usize) < value_to_value.len() {
1328                            value_to_value[src.0 as usize] = entry as u32;
1329                        }
1330                    }
1331                    portable.set_explicit_mapping(num_points);
1332                    for point in 0..num_points {
1333                        let src = att.mapped_index(PointIndex(point as u32));
1334                        let entry = value_to_value
1335                            .get(src.0 as usize)
1336                            .copied()
1337                            .unwrap_or_default();
1338                        portable.try_set_point_map_entry(
1339                            PointIndex(point as u32),
1340                            crate::geometry_indices::AttributeValueIndex(entry),
1341                        )?;
1342                    }
1343                }
1344
1345                portables.push((att_id, portable));
1346                quantization_transforms.push(Some(q_transform));
1347            }
1348            // Accumulated across groups, not replaced: attributes are encoded one
1349            // group at a time and the position lives in its own, so replacing
1350            // here would take the position's portable values away from every
1351            // later group's predictors.
1352            for (att_id, portable) in portables {
1353                match self
1354                    .portable_attributes
1355                    .iter_mut()
1356                    .find(|(id, _)| *id == att_id)
1357                {
1358                    Some((_, existing)) => *existing = portable,
1359                    None => self.portable_attributes.push((att_id, portable)),
1360                }
1361            }
1362        }
1363
1364        // Pass two, EncodePortableAttributes: the values themselves, in attribute
1365        // order.
1366        let mesh = self
1367            .mesh
1368            .as_ref()
1369            .expect("mesh must be set before encoding");
1370        let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> = Vec::new();
1371
1372        for (local_i, &att_id) in attr_ids.iter().enumerate() {
1373            let att = mesh.attribute(att_id);
1374            let decoder_type = decoder_types[local_i];
1375            let _ = att;
1376
1377            match decoder_type {
1378                3 => {
1379                    let mut encoder = SequentialNormalAttributeEncoder::new();
1380                    if !encoder.init(
1381                        self.point_cloud().expect("point_cloud set"),
1382                        att_id,
1383                        &self.options,
1384                    ) {
1385                        return Err(DracoError::DracoError(
1386                            "Failed to init normal encoder".to_string(),
1387                        ));
1388                    }
1389                    if !encoder.encode_values(
1390                        self.point_cloud().expect("point_cloud set"),
1391                        point_ids,
1392                        out_buffer,
1393                        &self.options,
1394                        self,
1395                    ) {
1396                        return Err(DracoError::DracoError(
1397                            "Failed to encode normal values".to_string(),
1398                        ));
1399                    }
1400                    normal_encoders.push(Some(encoder));
1401                }
1402                2 => {
1403                    let portable = self
1404                        .portable_attributes
1405                        .iter()
1406                        .find(|(id, _)| *id == att_id)
1407                        .map(|(_, att)| att)
1408                        .ok_or_else(|| {
1409                            DracoError::DracoError(format!(
1410                                "Missing portable attribute for {att_id}"
1411                            ))
1412                        })?;
1413
1414                    let mut att_encoder = SequentialIntegerAttributeEncoder::new();
1415                    att_encoder.init(att_id);
1416                    if !att_encoder.encode_values(
1417                        mesh as &PointCloud,
1418                        point_ids,
1419                        out_buffer,
1420                        &self.options,
1421                        self,
1422                        Some(portable),
1423                        true,
1424                    ) {
1425                        return Err(DracoError::DracoError(format!(
1426                            "Failed to encode attribute {}",
1427                            att_id
1428                        )));
1429                    }
1430                    normal_encoders.push(None);
1431                }
1432                1 => {
1433                    let mut att_encoder = SequentialIntegerAttributeEncoder::new();
1434                    att_encoder.init(att_id);
1435                    if !att_encoder.encode_values(
1436                        mesh as &PointCloud,
1437                        point_ids,
1438                        out_buffer,
1439                        &self.options,
1440                        self,
1441                        None,
1442                        true,
1443                    ) {
1444                        return Err(DracoError::DracoError(format!(
1445                            "Failed to encode attribute {}",
1446                            att_id
1447                        )));
1448                    }
1449                    normal_encoders.push(None);
1450                }
1451                0 => {
1452                    let mut att_encoder = SequentialAttributeEncoder::new();
1453                    att_encoder.init(att_id);
1454                    if !att_encoder.encode_values(mesh as &PointCloud, point_ids, out_buffer) {
1455                        return Err(DracoError::DracoError(format!(
1456                            "Failed to encode attribute {}",
1457                            att_id
1458                        )));
1459                    }
1460                    normal_encoders.push(None);
1461                }
1462                _ => {
1463                    return Err(DracoError::DracoError(format!(
1464                        "Unsupported encoder type {}",
1465                        decoder_type
1466                    )));
1467                }
1468            }
1469        }
1470
1471        // Pass three, EncodeDataNeededByPortableTransforms: the parameters a
1472        // decoder needs to undo each transform -- quantization ranges, and the
1473        // octahedron's bit count. Separate from pass two because upstream emits
1474        // every attribute's values first and only then every attribute's
1475        // transform data, so the two cannot be interleaved.
1476        for (local_i, &decoder_type) in decoder_types.iter().enumerate() {
1477            match decoder_type {
1478                3 => {
1479                    let major = out_buffer.version_major();
1480                    let minor = out_buffer.version_minor();
1481                    let bitstream_version = crate::version::bitstream_version(major, minor);
1482                    if bitstream_version != 0 && bitstream_version < 0x0200 {
1483                        continue;
1484                    }
1485                    if let Some(ref encoder) = normal_encoders[local_i] {
1486                        if !encoder.encode_data_needed_by_portable_transform(out_buffer) {
1487                            return Err(DracoError::DracoError(
1488                                "Failed to encode normal transform data".to_string(),
1489                            ));
1490                        }
1491                    }
1492                }
1493                2 => {
1494                    if let Some(ref q_transform) = quantization_transforms[local_i] {
1495                        if !q_transform.encode_parameters(out_buffer) {
1496                            return Err(DracoError::DracoError(
1497                                "Failed to encode quantization parameters".to_string(),
1498                            ));
1499                        }
1500                    }
1501                }
1502                1 | 0 => {}
1503                _ => {}
1504            }
1505        }
1506
1507        Ok(())
1508    }
1509
1510    fn compute_number_of_encoded_faces(&mut self) {
1511        if let Some(ref mesh) = self.mesh {
1512            self.num_encoded_faces = mesh.num_faces();
1513        }
1514    }
1515
1516    fn build_encoded_mesh_info(&mut self) -> Status {
1517        let num_attributes = self
1518            .mesh
1519            .as_ref()
1520            .expect("mesh must be set before encoding")
1521            .num_attributes();
1522        let mut attributes = Vec::with_capacity(num_attributes as usize);
1523        let mut encoded_num_points = self.point_ids.len();
1524
1525        for att_id in 0..num_attributes {
1526            let point_ids = self.encoded_point_ids_for_attribute(att_id)?;
1527            let num_encoded_values = point_ids.len();
1528            encoded_num_points = encoded_num_points.max(num_encoded_values);
1529
1530            let (position_min, position_max) =
1531                self.position_bounds_for_attribute(att_id, &point_ids)?;
1532            let att = self
1533                .mesh
1534                .as_ref()
1535                .expect("mesh must be set before encoding")
1536                .attribute(att_id);
1537            attributes.push(EncodedAttributeInfo {
1538                source_attribute_id: att_id,
1539                attribute_type: att.attribute_type(),
1540                data_type: att.data_type(),
1541                num_components: att.num_components(),
1542                normalized: att.normalized(),
1543                unique_id: att.unique_id(),
1544                num_encoded_values,
1545                position_min,
1546                position_max,
1547            });
1548        }
1549
1550        let (source_num_points, num_faces) = self
1551            .mesh
1552            .as_ref()
1553            .map(|mesh| (mesh.num_points(), mesh.num_faces()))
1554            .expect("mesh must be set before encoding");
1555        if self.method == 0 {
1556            encoded_num_points = source_num_points;
1557        } else {
1558            encoded_num_points = self.encoded_num_points_for_mesh(encoded_num_points)?;
1559        }
1560
1561        self.active_corner_table = None;
1562        self.active_data_to_corner_map = None;
1563        self.active_vertex_to_data_map = None;
1564        self.encoded_mesh_info = Some(EncodedMeshInfo {
1565            encoding_method: self.method,
1566            num_encoded_faces: num_faces,
1567            num_encoded_points: encoded_num_points,
1568            attributes,
1569        });
1570        Ok(())
1571    }
1572
1573    fn encoded_point_ids_for_attribute(
1574        &mut self,
1575        att_id: i32,
1576    ) -> Result<Vec<PointIndex>, DracoError> {
1577        if self.method == 0 || self.use_single_connectivity {
1578            return Ok(self.point_ids.clone());
1579        }
1580
1581        if let Some(data_id) = self
1582            .edgebreaker_attribute_connectivity
1583            .iter()
1584            .position(|connectivity| connectivity.attribute_id == att_id)
1585        {
1586            return self.prepare_active_attribute_connectivity(data_id);
1587        }
1588
1589        Ok(self.point_ids.clone())
1590    }
1591
1592    fn encoded_num_points_for_mesh(&mut self, base_num_points: usize) -> Result<usize, DracoError> {
1593        if self.method == 0 || self.use_single_connectivity {
1594            return Ok(base_num_points);
1595        }
1596
1597        let mut num_points = base_num_points;
1598        for data_id in 0..self.edgebreaker_attribute_connectivity.len() {
1599            if self.edgebreaker_attribute_connectivity[data_id].no_interior_seams {
1600                continue;
1601            }
1602            let point_ids = self.prepare_active_attribute_connectivity(data_id)?;
1603            num_points = num_points.max(point_ids.len());
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(num_points)
1609    }
1610
1611    fn position_bounds_for_attribute(
1612        &self,
1613        att_id: i32,
1614        point_ids: &[PointIndex],
1615    ) -> Result<PositionBounds, DracoError> {
1616        let mesh = self
1617            .mesh
1618            .as_ref()
1619            .expect("mesh must be set before encoding");
1620        let att = mesh.attribute(att_id);
1621        if att.attribute_type() != GeometryAttributeType::Position {
1622            return Ok((None, None));
1623        }
1624        if att.num_components() != 3 || att.data_type() != DataType::Float32 {
1625            return Ok((None, None));
1626        }
1627
1628        if self.decoder_type_for_attribute(att_id) == 2 {
1629            let quantization_bits = self
1630                .options
1631                .get_attribute_int(att_id, "quantization_bits", -1);
1632            let mut q_transform = AttributeQuantizationTransform::new();
1633            if !q_transform.compute_parameters(att, quantization_bits) {
1634                return Err(DracoError::DracoError(
1635                    "Failed to compute position quantization parameters".to_string(),
1636                ));
1637            }
1638
1639            let mut portable = PointAttribute::default();
1640            if !q_transform.transform_attribute(att, point_ids, &mut portable) {
1641                return Err(DracoError::DracoError(
1642                    "Failed to quantize position attribute for encoded mesh info".to_string(),
1643                ));
1644            }
1645
1646            let mut dequantized = PointAttribute::new();
1647            dequantized.try_init(
1648                GeometryAttributeType::Position,
1649                3,
1650                DataType::Float32,
1651                false,
1652                portable.size(),
1653            )?;
1654            if !q_transform.inverse_transform_attribute(&portable, &mut dequantized) {
1655                return Err(DracoError::DracoError(
1656                    "Failed to dequantize position attribute for encoded mesh info".to_string(),
1657                ));
1658            }
1659
1660            return Self::position_bounds_from_attribute(&dequantized, &[]);
1661        }
1662
1663        Self::position_bounds_from_attribute(att, point_ids)
1664    }
1665
1666    fn position_bounds_from_attribute(
1667        att: &PointAttribute,
1668        point_ids: &[PointIndex],
1669    ) -> Result<PositionBounds, DracoError> {
1670        let count = if point_ids.is_empty() {
1671            att.size()
1672        } else {
1673            point_ids.len()
1674        };
1675        if count == 0 {
1676            return Ok((None, None));
1677        }
1678
1679        let stride = usize::try_from(att.byte_stride()).map_err(|_| {
1680            DracoError::DracoError("Position attribute has invalid byte stride".to_string())
1681        })?;
1682        let bytes = att.buffer().data();
1683        let mut min = [f32::INFINITY; 3];
1684        let mut max = [f32::NEG_INFINITY; 3];
1685
1686        for i in 0..count {
1687            let point = if point_ids.is_empty() {
1688                PointIndex(i as u32)
1689            } else {
1690                point_ids[i]
1691            };
1692            let value_index = att.mapped_index(point);
1693            if value_index == INVALID_ATTRIBUTE_VALUE_INDEX {
1694                return Err(DracoError::DracoError(
1695                    "Position attribute point map contains an invalid entry".to_string(),
1696                ));
1697            }
1698
1699            let value_offset = (value_index.0 as usize)
1700                .checked_mul(stride)
1701                .ok_or_else(|| {
1702                    DracoError::DracoError("Position attribute offset overflow".to_string())
1703                })?;
1704            for component in 0..3 {
1705                let offset = value_offset
1706                    .checked_add(component * DataType::Float32.byte_length())
1707                    .ok_or_else(|| {
1708                        DracoError::DracoError("Position attribute offset overflow".to_string())
1709                    })?;
1710                let end = offset
1711                    .checked_add(DataType::Float32.byte_length())
1712                    .ok_or_else(|| {
1713                        DracoError::DracoError("Position attribute offset overflow".to_string())
1714                    })?;
1715                let Some(component_bytes) = bytes.get(offset..end) else {
1716                    return Err(DracoError::DracoError(
1717                        "Position attribute buffer is shorter than metadata".to_string(),
1718                    ));
1719                };
1720                let value = f32::from_le_bytes([
1721                    component_bytes[0],
1722                    component_bytes[1],
1723                    component_bytes[2],
1724                    component_bytes[3],
1725                ]);
1726                min[component] = min[component].min(value);
1727                max[component] = max[component].max(value);
1728            }
1729        }
1730
1731        Ok((
1732            Some(min.into_iter().map(f64::from).collect()),
1733            Some(max.into_iter().map(f64::from).collect()),
1734        ))
1735    }
1736}
1737
1738impl Default for MeshEncoder {
1739    fn default() -> Self {
1740        Self::new()
1741    }
1742}