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::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    method: i32,
86    /// Maps point indices to vertex indices in the corner table.
87    /// Used when position-based deduplication is enabled.
88    point_to_vertex_map: Option<Vec<u32>>,
89    /// Whether we're using single connectivity (all attributes share same corner table).
90    use_single_connectivity: bool,
91    encoded_mesh_info: Option<EncodedMeshInfo>,
92}
93
94/// Geometry shape and attribute metadata produced by a successful mesh encode.
95#[derive(Debug, Clone, PartialEq)]
96pub struct EncodedMeshInfo {
97    /// Numeric Draco mesh encoding method used for the output.
98    pub encoding_method: i32,
99    /// Number of faces encoded into the bitstream.
100    pub num_encoded_faces: usize,
101    /// Number of points encoded into the bitstream.
102    pub num_encoded_points: usize,
103    /// Per-attribute information captured during encoding.
104    pub attributes: Vec<EncodedAttributeInfo>,
105}
106
107/// Attribute metadata produced by a successful mesh encode.
108#[derive(Debug, Clone, PartialEq)]
109pub struct EncodedAttributeInfo {
110    /// Source attribute id in the input mesh.
111    pub source_attribute_id: i32,
112    /// Semantic type of the encoded attribute.
113    pub attribute_type: GeometryAttributeType,
114    /// Scalar data type of the encoded attribute.
115    pub data_type: DataType,
116    /// Number of scalar components per encoded value.
117    pub num_components: u8,
118    /// Whether integer values are normalized.
119    pub normalized: bool,
120    /// Draco unique id assigned to the attribute.
121    pub unique_id: u32,
122    /// Number of unique values encoded for the attribute.
123    pub num_encoded_values: usize,
124    /// Minimum position components when known for position attributes.
125    pub position_min: Option<Vec<f64>>,
126    /// Maximum position components when known for position attributes.
127    pub position_max: Option<Vec<f64>>,
128}
129
130impl GeometryEncoder for MeshEncoder {
131    fn point_cloud(&self) -> Option<&PointCloud> {
132        self.mesh.as_ref().map(|m| m as &PointCloud)
133    }
134
135    fn mesh(&self) -> Option<&Mesh> {
136        self.mesh.as_ref()
137    }
138
139    fn corner_table(&self) -> Option<&CornerTable> {
140        self.active_corner_table
141            .as_ref()
142            .or(self.corner_table.as_ref())
143    }
144
145    fn options(&self) -> &EncoderOptions {
146        &self.options
147    }
148
149    fn get_geometry_type(&self) -> EncodedGeometryType {
150        EncodedGeometryType::TriangularMesh
151    }
152
153    fn get_encoding_method(&self) -> Option<i32> {
154        Some(self.method)
155    }
156
157    fn get_data_to_corner_map(&self) -> Option<&[u32]> {
158        self.active_data_to_corner_map
159            .as_deref()
160            .or(self.data_to_corner_map.as_deref())
161    }
162
163    fn get_vertex_to_data_map(&self) -> Option<&[i32]> {
164        self.active_vertex_to_data_map
165            .as_deref()
166            .or(self.vertex_to_data_map.as_deref())
167    }
168}
169
170impl MeshEncoder {
171    /// Creates an encoder without an assigned mesh.
172    pub fn new() -> Self {
173        Self {
174            mesh: None,
175            options: EncoderOptions::default(),
176            num_encoded_faces: 0,
177            corner_table: None,
178            point_ids: Vec::new(),
179            data_to_corner_map: None,
180            vertex_to_data_map: None,
181            edgebreaker_attribute_connectivity: Vec::new(),
182            active_corner_table: None,
183            active_data_to_corner_map: None,
184            active_vertex_to_data_map: None,
185            method: 0,
186            point_to_vertex_map: None,
187            use_single_connectivity: false,
188            encoded_mesh_info: None,
189        }
190    }
191
192    /// Assigns the mesh to encode.
193    pub fn set_mesh(&mut self, mesh: Mesh) {
194        self.mesh = Some(mesh);
195    }
196
197    /// Returns the assigned mesh, if any.
198    pub fn mesh(&self) -> Option<&Mesh> {
199        self.mesh.as_ref()
200    }
201
202    /// Returns the number of faces encoded by the last successful encode.
203    pub fn num_encoded_faces(&self) -> usize {
204        self.num_encoded_faces
205    }
206
207    /// Returns the corner table built during the last mesh encode, if any.
208    pub fn corner_table(&self) -> Option<&CornerTable> {
209        self.corner_table.as_ref()
210    }
211
212    /// Returns information captured during the last successful mesh encode.
213    pub fn encoded_mesh_info(&self) -> Option<&EncodedMeshInfo> {
214        self.encoded_mesh_info.as_ref()
215    }
216
217    /// Encodes the assigned mesh into an output buffer.
218    ///
219    /// A mesh must have been provided with [`set_mesh`](MeshEncoder::set_mesh)
220    /// first. On success the bitstream is appended to `out_buffer` and
221    /// [`encoded_mesh_info`](MeshEncoder::encoded_mesh_info) is populated.
222    ///
223    /// # Errors
224    ///
225    /// Returns an error if no mesh was set, if the requested encoding method or
226    /// options are unsupported, or if attribute encoding fails.
227    pub fn encode(&mut self, options: &EncoderOptions, out_buffer: &mut EncoderBuffer) -> Status {
228        self.options = options.clone();
229        self.encoded_mesh_info = None;
230
231        if self.mesh.is_none() {
232            return Err(DracoError::DracoError("Mesh not set".to_string()));
233        }
234
235        // 1. Encode Header
236        self.encode_header(out_buffer)?;
237        self.encode_metadata(out_buffer)?;
238
239        // 2. Encode geometry data (connectivity + attributes)
240        self.encode_geometry_data(out_buffer)?;
241
242        Ok(())
243    }
244
245    fn encode_metadata(&self, buffer: &mut EncoderBuffer) -> Status {
246        if let Some(metadata) = self
247            .mesh
248            .as_ref()
249            .and_then(|mesh| mesh.metadata())
250            .filter(|metadata| !metadata.is_empty())
251        {
252            metadata.encode(buffer)?;
253        }
254        Ok(())
255    }
256
257    fn encode_header(&self, buffer: &mut EncoderBuffer) -> Status {
258        let (mut major, mut minor) = self.options.get_version();
259        if major == 0 && minor == 0 {
260            // Default to latest mesh version
261            (major, minor) = DEFAULT_MESH_VERSION;
262        }
263        let has_metadata = self
264            .mesh
265            .as_ref()
266            .and_then(|mesh| mesh.metadata())
267            .is_some_and(|metadata| !metadata.is_empty());
268
269        if has_metadata && !has_header_flags(major, minor) {
270            return Err(DracoError::UnsupportedVersion(
271                "Metadata requires Draco bitstream version 1.3 or newer".to_string(),
272            ));
273        }
274
275        // C++ default behavior: Edgebreaker if speed != 10, Sequential if speed == 10
276        let method_int = self.options.get_global_int("encoding_method", -1);
277        let method = if method_int == -1 {
278            if self.options.get_speed() == 10 {
279                0
280            } else {
281                1
282            }
283        } else if method_int == 1 {
284            1
285        } else {
286            0
287        };
288
289        #[cfg(not(feature = "legacy_bitstream_encode"))]
290        if method == 1 {
291            let bitstream_version = crate::version::bitstream_version(major, minor);
292            if bitstream_version < 0x0202 {
293                return Err(DracoError::UnsupportedVersion(
294                    "EdgeBreaker mesh encoding before bitstream 2.2 requires the \
295                     legacy_bitstream_encode feature"
296                        .to_string(),
297                ));
298            }
299            if self.options.get_global_int("force_predictive_traversal", 0) != 0 {
300                return Err(DracoError::UnsupportedFeature(
301                    "force_predictive_traversal requires the legacy_bitstream_encode feature"
302                        .to_string(),
303                ));
304            }
305        }
306        #[cfg(not(feature = "legacy_bitstream_encode"))]
307        match self.options.get_prediction_scheme() {
308            2 | 3 => {
309                return Err(DracoError::UnsupportedFeature(
310                    "legacy prediction schemes require the legacy_bitstream_encode feature"
311                        .to_string(),
312                ));
313            }
314            _ => {}
315        }
316
317        buffer.encode_data(b"DRACO");
318
319        buffer.encode_u8(major);
320        buffer.encode_u8(minor);
321        buffer.set_version(major, minor);
322        buffer.encode_u8(self.get_geometry_type() as u8);
323        buffer.encode_u8(method);
324
325        // The flags field is always present in the binary header (the decoder reads
326        // it unconditionally); only the metadata bit gains meaning at v1.3+, which
327        // is guarded by the metadata check above. Emitting it only for >= 1.3 left
328        // pre-1.3 streams two bytes short, misaligning the rest of the stream.
329        let flags = if has_metadata { METADATA_FLAG_MASK } else { 0 };
330        buffer.encode_u16(flags);
331        Ok(())
332    }
333
334    fn encode_geometry_data(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
335        // First encode connectivity
336        self.encode_connectivity(out_buffer)?;
337
338        // Check if we should store the number of encoded faces
339        if self
340            .options
341            .get_global_int("store_number_of_encoded_faces", 0)
342            != 0
343        {
344            self.compute_number_of_encoded_faces();
345        }
346
347        // Then encode attributes
348        self.encode_attributes(out_buffer)?;
349        self.build_encoded_mesh_info()?;
350
351        Ok(())
352    }
353
354    fn encode_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
355        let mesh = self
356            .mesh
357            .as_ref()
358            .expect("mesh must be set before encoding");
359
360        // Determine encoding method FIRST (before building corner table)
361        let method_int = self.options.get_global_int("encoding_method", -1);
362        let method = if method_int == -1 {
363            if self.options.get_speed() == 10 {
364                MeshEncodingMethod::MeshSequentialEncoding
365            } else {
366                MeshEncodingMethod::MeshEdgebreakerEncoding
367            }
368        } else if method_int == 1 {
369            MeshEncodingMethod::MeshEdgebreakerEncoding
370        } else {
371            MeshEncodingMethod::MeshSequentialEncoding
372        };
373        self.method = if method == MeshEncodingMethod::MeshEdgebreakerEncoding {
374            1
375        } else {
376            0
377        };
378
379        // C++ behavior: use_single_connectivity_ when speed >= 6
380        // When false (speed < 6), use position attribute to deduplicate vertices
381        let speed = self.options.get_speed();
382        // Check if split_mesh_on_seams is explicitly set, otherwise use speed-based default
383        let split_on_seams_explicit = self.options.get_global_int("split_mesh_on_seams", -1);
384        let use_single_connectivity = if split_on_seams_explicit >= 0 {
385            split_on_seams_explicit != 0
386        } else {
387            speed >= 6
388        };
389
390        // Only build corner table if needed (not for sequential encoding)
391        if method == MeshEncodingMethod::MeshEdgebreakerEncoding {
392            let (faces, point_to_vertex_map) = if use_single_connectivity {
393                // CreateCornerTableFromAllAttributes: use point indices directly
394                let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
395                    .map(|i| {
396                        let face = mesh.face(FaceIndex(i as u32));
397                        [
398                            crate::geometry_indices::VertexIndex(face[0].0),
399                            crate::geometry_indices::VertexIndex(face[1].0),
400                            crate::geometry_indices::VertexIndex(face[2].0),
401                        ]
402                    })
403                    .collect();
404                // Identity mapping
405                let point_to_vertex: Vec<u32> = (0..mesh.num_points() as u32).collect();
406                (faces, point_to_vertex)
407            } else {
408                // CreateCornerTableFromPositionAttribute: use position attribute to deduplicate
409                self.create_corner_table_from_position_attribute(mesh)
410            };
411
412            // Initialize corner table for the mesh
413            let mut corner_table = CornerTable::new(0);
414            corner_table.init(&faces);
415
416            self.corner_table = Some(corner_table);
417            self.point_to_vertex_map = Some(point_to_vertex_map);
418            self.edgebreaker_attribute_connectivity.clear();
419            if !use_single_connectivity {
420                if let Some(ref ct) = self.corner_table {
421                    for i in 0..mesh.num_attributes() {
422                        let att = mesh.attribute(i);
423                        if att.attribute_type() != GeometryAttributeType::Position {
424                            self.edgebreaker_attribute_connectivity
425                                .push(EdgebreakerAttributeConnectivity::build(mesh, ct, i));
426                        }
427                    }
428                }
429            }
430        } else {
431            // Sequential encoding: no corner table needed, use identity mapping
432            let point_to_vertex: Vec<u32> = (0..mesh.num_points() as u32).collect();
433            self.point_to_vertex_map = Some(point_to_vertex);
434            self.edgebreaker_attribute_connectivity.clear();
435        }
436        self.use_single_connectivity = use_single_connectivity;
437
438        match method {
439            MeshEncodingMethod::MeshSequentialEncoding => {
440                self.encode_sequential_connectivity(out_buffer)
441            }
442            MeshEncodingMethod::MeshEdgebreakerEncoding => {
443                self.encode_edgebreaker_connectivity(out_buffer)
444            }
445        }
446    }
447
448    fn encode_edgebreaker_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
449        let mesh = self
450            .mesh
451            .as_ref()
452            .expect("mesh must be set before encoding");
453        let corner_table = self
454            .corner_table
455            .as_ref()
456            .expect("corner_table must be set before edgebreaker encoding");
457
458        let mut encoder = MeshEdgebreakerEncoder::new(mesh.num_faces(), mesh.num_points());
459        // Opt-in legacy predictive (type-1) traversal, for round-tripping the
460        // pre-0.10.0 connectivity. Requires a < 2.0 target version.
461        #[cfg(feature = "legacy_bitstream_encode")]
462        encoder.set_force_predictive(
463            self.options.get_global_int("force_predictive_traversal", 0) == 1,
464        );
465        let (point_ids, data_to_corner_map, vertex_to_data_map) = encoder.encode_connectivity(
466            mesh,
467            corner_table,
468            &self.edgebreaker_attribute_connectivity,
469            out_buffer,
470            self.options.get_speed() as usize,
471        )?;
472        #[cfg(feature = "debug_logs")]
473        {
474            debug_log!("DEBUG: encode_edgebreaker_connectivity: point_ids.len()={}, data_to_corner_map.len()={}, vertex_to_data_map.len()={}",
475                 point_ids.len(), data_to_corner_map.len(), vertex_to_data_map.len());
476        }
477        self.point_ids = point_ids;
478
479        // Draco stores corner mapping in attribute (data) order.
480        self.data_to_corner_map = Some(data_to_corner_map);
481        self.vertex_to_data_map = Some(vertex_to_data_map);
482
483        Ok(())
484    }
485
486    /// Creates faces array using position attribute to deduplicate vertices.
487    /// This mimics C++ CreateCornerTableFromPositionAttribute.
488    /// Returns (faces, point_to_vertex_map) where:
489    /// - faces: vertex indices (deduplicated based on position values)
490    /// - point_to_vertex_map: maps each point index to its vertex index in the corner table
491    fn create_corner_table_from_position_attribute(
492        &self,
493        mesh: &Mesh,
494    ) -> (Vec<[crate::geometry_indices::VertexIndex; 3]>, Vec<u32>) {
495        use crate::geometry_attribute::GeometryAttributeType;
496
497        let pos_att_id = mesh.named_attribute_id(GeometryAttributeType::Position);
498        if pos_att_id < 0 {
499            // No position attribute, fall back to identity mapping
500            let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
501                .map(|i| {
502                    let face = mesh.face(FaceIndex(i as u32));
503                    [
504                        crate::geometry_indices::VertexIndex(face[0].0),
505                        crate::geometry_indices::VertexIndex(face[1].0),
506                        crate::geometry_indices::VertexIndex(face[2].0),
507                    ]
508                })
509                .collect();
510            let point_to_vertex: Vec<u32> = (0..mesh.num_points() as u32).collect();
511            return (faces, point_to_vertex);
512        }
513
514        let pos_att = mesh.attribute(pos_att_id);
515        let _buffer = pos_att.buffer();
516        let num_components = pos_att.num_components() as usize;
517        let _byte_stride = match pos_att.data_type() {
518            crate::draco_types::DataType::Float32 => num_components * 4,
519            crate::draco_types::DataType::Float64 => num_components * 8,
520            crate::draco_types::DataType::Int8 | crate::draco_types::DataType::Uint8 => {
521                num_components
522            }
523            crate::draco_types::DataType::Int16 | crate::draco_types::DataType::Uint16 => {
524                num_components * 2
525            }
526            crate::draco_types::DataType::Int32 | crate::draco_types::DataType::Uint32 => {
527                num_components * 4
528            }
529            crate::draco_types::DataType::Int64 | crate::draco_types::DataType::Uint64 => {
530                num_components * 8
531            }
532            _ => num_components * 4, // Default to 4 bytes per component
533        };
534
535        // Use attribute mapped indices directly to build point->vertex map. This mirrors
536        // C++ CreateCornerTableFromAttribute which uses att->mapped_index(face[j]).
537        let mut point_to_vertex: Vec<u32> = vec![0; mesh.num_points()];
538        for i in 0..mesh.num_points() {
539            let pt = PointIndex(i as u32);
540            let val_idx = pos_att.mapped_index(pt);
541            point_to_vertex[i] = val_idx.0;
542        }
543
544        // Build faces using attribute mapped indices (exact same mapping as C++).
545        let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = (0..mesh.num_faces())
546            .map(|i| {
547                let face = mesh.face(FaceIndex(i as u32));
548                [
549                    crate::geometry_indices::VertexIndex(point_to_vertex[face[0].0 as usize]),
550                    crate::geometry_indices::VertexIndex(point_to_vertex[face[1].0 as usize]),
551                    crate::geometry_indices::VertexIndex(point_to_vertex[face[2].0 as usize]),
552                ]
553            })
554            .collect();
555
556        #[cfg(feature = "debug_logs")]
557        {
558            debug_log!(
559                "Rust created faces (first 12): {:?}",
560                faces
561                    .iter()
562                    .take(12)
563                    .map(|f| [f[0].0, f[1].0, f[2].0])
564                    .collect::<Vec<_>>()
565            );
566            debug_log!(
567                "Rust point_to_vertex (first 25): {:?}",
568                point_to_vertex.iter().take(25).cloned().collect::<Vec<_>>()
569            );
570        }
571        (faces, point_to_vertex)
572    }
573
574    fn encode_sequential_connectivity(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
575        let mesh = self
576            .mesh
577            .as_ref()
578            .expect("mesh must be set before encoding");
579
580        // Encode the number of faces and points
581        // Use the buffer's version (set in encode_header) for version checks
582        let major = out_buffer.version_major();
583        let minor = out_buffer.version_minor();
584        if !uses_varint_encoding(major, minor) {
585            out_buffer.encode_u32(mesh.num_faces() as u32);
586            out_buffer.encode_u32(mesh.num_points() as u32);
587        } else {
588            out_buffer.encode_varint(mesh.num_faces() as u64);
589            out_buffer.encode_varint(mesh.num_points() as u64);
590        }
591
592        if mesh.num_faces() > 0 && mesh.num_points() > 0 {
593            out_buffer.encode_u8(1); // Raw connectivity
594            if mesh.num_points() < 256 {
595                for face_id in 0..mesh.num_faces() {
596                    let face = mesh.face(FaceIndex(face_id as u32));
597                    for i in 0..3 {
598                        out_buffer.encode_u8(face[i].0 as u8);
599                    }
600                }
601            } else if mesh.num_points() < 65536 {
602                for face_id in 0..mesh.num_faces() {
603                    let face = mesh.face(FaceIndex(face_id as u32));
604                    for i in 0..3 {
605                        out_buffer.encode_u16(face[i].0 as u16);
606                    }
607                }
608            } else if mesh.num_points() < (1 << 21) {
609                // Use varint encoding for indices when points fit in 21 bits
610                // This matches C++ behavior for better compression
611                for face_id in 0..mesh.num_faces() {
612                    let face = mesh.face(FaceIndex(face_id as u32));
613                    for i in 0..3 {
614                        out_buffer.encode_varint(face[i].0 as u64);
615                    }
616                }
617            } else {
618                // Default: use u32 for very large meshes
619                for face_id in 0..mesh.num_faces() {
620                    let face = mesh.face(FaceIndex(face_id as u32));
621                    for i in 0..3 {
622                        out_buffer.encode_u32(face[i].0);
623                    }
624                }
625            }
626        }
627
628        // Identity permutation for sequential encoding
629        self.point_ids = (0..mesh.num_points())
630            .map(|i| PointIndex(i as u32))
631            .collect();
632
633        Ok(())
634    }
635
636    fn encode_attributes(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
637        // NOTE: Unlike the decoder, the encoder does NOT need to apply UpdatePointToAttributeIndexMapping
638        // because the attribute still has identity mapping. The encoder uses the point_ids array
639        // (from edgebreaker traversal) to determine the order in which to process points, and
640        // mapped_index with identity mapping just returns the point index directly.
641
642        let mesh = self
643            .mesh
644            .as_ref()
645            .expect("mesh must be set before encoding");
646
647        let method_int = self.options.get_global_int("encoding_method", -1);
648        // Match C++ behavior: if encoding_method is not set (-1),
649        // use Edgebreaker for all options except speed == 10
650        let is_edgebreaker = if method_int == -1 {
651            self.options.get_speed() != 10
652        } else {
653            method_int == 1
654        };
655
656        if is_edgebreaker && !self.use_single_connectivity {
657            return self.encode_edgebreaker_attributes_split(out_buffer);
658        }
659
660        // Encode number of attribute decoders (u8).
661        // For both sequential and edgebreaker with single-connectivity mode:
662        // there's only ONE attribute encoder containing ALL attributes.
663        // This matches C++ behavior when use_single_connectivity_ = true (speed >= 6).
664        let num_attributes = mesh.num_attributes();
665        let num_encoders = if num_attributes > 0 { 1 } else { 0 };
666        // Use the buffer's version (set in encode_header) for version checks.
667        let major = out_buffer.version_major();
668        let minor = out_buffer.version_minor();
669
670        out_buffer.encode_u8(num_encoders as u8);
671
672        // Phase 1: attributes decoder identifiers.
673        // For single-encoder mode: one encoder with att_data_id = -1 (uses position connectivity)
674        if num_encoders > 0 && is_edgebreaker {
675            // att_data_id (i8), encoder_type (u8), traversal_method (u8)
676            // -1 means use position connectivity (single connectivity mode)
677            out_buffer.encode_u8((-1i8) as u8); // att_data_id = -1
678            out_buffer.encode_u8(0); // element_type = MESH_VERTEX_ATTRIBUTE
679
680            // Traversal method was added in bitstream 1.2. Older streams
681            // default to DEPTH_FIRST on decode and must not carry the byte.
682            if crate::version::bitstream_version(major, minor) >= 0x0102 {
683                // PREDICTION_DEGREE (1) for speed 0, DEPTH_FIRST (0) otherwise.
684                // This must match the traversal used in MeshEdgebreakerEncoder.
685                let encoding_speed = self.options.get_speed();
686                let traversal_method: u8 = if encoding_speed == 0 { 1 } else { 0 };
687                out_buffer.encode_u8(traversal_method);
688            }
689        }
690        // For sequential, nothing is written in phase 1 (EncodeAttributesEncoderIdentifier does nothing)
691
692        let mut decoder_types: Vec<u8> = Vec::with_capacity(mesh.num_attributes() as usize);
693
694        // Phase 2: Encode attribute encoder data
695        // Both sequential and edgebreaker now use single-encoder mode:
696        //   - Write num_attrs = total attributes
697        //   - Write all attribute metadata
698        //   - Write all decoder types
699
700        if num_encoders > 0 {
701            // Single encoder with all attributes (single-connectivity mode for edgebreaker)
702            // Write num_attrs = total number of attributes
703            if !uses_varint_encoding(major, minor) {
704                out_buffer.encode_u32(mesh.num_attributes() as u32);
705            } else {
706                out_buffer.encode_varint(mesh.num_attributes() as u64);
707            }
708
709            // Write all attribute metadata first
710            for i in 0..mesh.num_attributes() {
711                let att = mesh.attribute(i);
712
713                #[cfg(feature = "debug_logs")]
714                {
715                    debug_log!("DEBUG: Encoder encoding attribute {} metadata. Type: {:?}, Components: {}, Data: {:?}", i, att.attribute_type(), att.num_components(), att.data_type());
716                }
717                out_buffer.encode_u8(att.attribute_type() as u8);
718                out_buffer.encode_u8(att.data_type() as u8);
719                out_buffer.encode_u8(att.num_components());
720                out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
721
722                if !uses_varint_unique_id(major, minor) {
723                    out_buffer.encode_u16(att.unique_id() as u16);
724                } else {
725                    out_buffer.encode_varint(att.unique_id() as u64);
726                }
727            }
728
729            // Write all decoder types after all metadata (SequentialAttributeEncodersController pattern)
730            for i in 0..mesh.num_attributes() {
731                let att = mesh.attribute(i);
732                let quantization_bits = self.options.get_attribute_int(i, "quantization_bits", -1);
733                let is_quantized = quantization_bits > 0
734                    && (att.data_type() == DataType::Float32
735                        || att.data_type() == DataType::Float64);
736                let is_normal = att.attribute_type() == GeometryAttributeType::Normal;
737
738                let decoder_type: u8 = if is_quantized {
739                    if is_normal {
740                        3
741                    } else {
742                        2
743                    }
744                } else if att.data_type() != DataType::Float32 {
745                    1
746                } else {
747                    0
748                };
749                out_buffer.encode_u8(decoder_type);
750                decoder_types.push(decoder_type);
751            }
752        }
753
754        // Phase 3: Encode attribute values (all attributes first)
755        // C++ order: all EncodePortableAttribute calls, then all EncodeDataNeededByPortableTransform calls
756
757        // Store transforms and encoders for later use in transform data encoding
758        let mut quantization_transforms: Vec<Option<AttributeQuantizationTransform>> = Vec::new();
759        let mut portable_attributes: Vec<Option<PointAttribute>> = Vec::new();
760        let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> = Vec::new();
761
762        // First pass: encode all attribute VALUES
763        for i in 0..mesh.num_attributes() {
764            let att = mesh.attribute(i);
765            let decoder_type = decoder_types[i as usize];
766            let quantization_bits = self.options.get_attribute_int(i, "quantization_bits", -1);
767
768            match decoder_type {
769                3 => {
770                    // Normal attribute with octahedral encoding
771                    let mut encoder = SequentialNormalAttributeEncoder::new();
772                    if !encoder.init(
773                        self.point_cloud().expect("point_cloud set"),
774                        i,
775                        &self.options,
776                    ) {
777                        return Err(DracoError::DracoError(
778                            "Failed to init normal encoder".to_string(),
779                        ));
780                    }
781                    if !encoder.encode_values(
782                        self.point_cloud().expect("point_cloud set"),
783                        &self.point_ids,
784                        out_buffer,
785                        &self.options,
786                        self,
787                    ) {
788                        return Err(DracoError::DracoError(
789                            "Failed to encode normal values".to_string(),
790                        ));
791                    }
792                    normal_encoders.push(Some(encoder));
793                    quantization_transforms.push(None);
794                    portable_attributes.push(None);
795                }
796                2 => {
797                    // Quantized attribute (mapping already applied at start of encode_attributes)
798                    let mut q_transform = AttributeQuantizationTransform::new();
799                    if !q_transform.compute_parameters(att, quantization_bits) {
800                        return Err(DracoError::DracoError(
801                            "Failed to compute quantization parameters".to_string(),
802                        ));
803                    }
804                    let mut portable = PointAttribute::default();
805                    if !q_transform.transform_attribute(att, &self.point_ids, &mut portable) {
806                        return Err(DracoError::DracoError(
807                            "Failed to quantize attribute".to_string(),
808                        ));
809                    }
810
811                    let mut att_encoder = SequentialIntegerAttributeEncoder::new();
812                    att_encoder.init(i);
813                    if !att_encoder.encode_values(
814                        mesh as &PointCloud,
815                        &self.point_ids,
816                        out_buffer,
817                        &self.options,
818                        self,
819                        Some(&portable),
820                        true,
821                    ) {
822                        return Err(DracoError::DracoError(format!(
823                            "Failed to encode attribute {}",
824                            i
825                        )));
826                    }
827
828                    quantization_transforms.push(Some(q_transform));
829                    portable_attributes.push(Some(portable));
830                    normal_encoders.push(None);
831                }
832                1 => {
833                    // Integer attribute
834                    let mut att_encoder = SequentialIntegerAttributeEncoder::new();
835                    att_encoder.init(i);
836                    if !att_encoder.encode_values(
837                        mesh as &PointCloud,
838                        &self.point_ids,
839                        out_buffer,
840                        &self.options,
841                        self,
842                        None,
843                        true,
844                    ) {
845                        return Err(DracoError::DracoError(format!(
846                            "Failed to encode attribute {}",
847                            i
848                        )));
849                    }
850                    quantization_transforms.push(None);
851                    portable_attributes.push(None);
852                    normal_encoders.push(None);
853                }
854                0 => {
855                    // Generic/float attribute
856                    let mut att_encoder = SequentialAttributeEncoder::new();
857                    att_encoder.init(i);
858                    if !att_encoder.encode_values(mesh as &PointCloud, &self.point_ids, out_buffer)
859                    {
860                        return Err(DracoError::DracoError(format!(
861                            "Failed to encode attribute {}",
862                            i
863                        )));
864                    }
865                    quantization_transforms.push(None);
866                    portable_attributes.push(None);
867                    normal_encoders.push(None);
868                }
869                _ => {
870                    return Err(DracoError::DracoError(format!(
871                        "Unsupported encoder type {}",
872                        decoder_type
873                    )));
874                }
875            }
876        }
877
878        // Second pass: encode all TRANSFORM DATA
879        for i in 0..mesh.num_attributes() {
880            let decoder_type = decoder_types[i as usize];
881
882            match decoder_type {
883                3 => {
884                    // Normal attribute - encode octahedral transform data
885                    let bitstream_version = crate::version::bitstream_version(major, minor);
886                    if bitstream_version != 0 && bitstream_version < 0x0200 {
887                        continue;
888                    }
889                    if let Some(ref encoder) = normal_encoders[i as usize] {
890                        if !encoder.encode_data_needed_by_portable_transform(out_buffer) {
891                            return Err(DracoError::DracoError(
892                                "Failed to encode normal transform data".to_string(),
893                            ));
894                        }
895                    }
896                }
897                2 => {
898                    // Quantized attribute - encode quantization parameters
899                    if let Some(ref q_transform) = quantization_transforms[i as usize] {
900                        if !q_transform.encode_parameters(out_buffer) {
901                            return Err(DracoError::DracoError(
902                                "Failed to encode quantization parameters".to_string(),
903                            ));
904                        }
905                    }
906                }
907                1 | 0 => {
908                    // No transform data for integer/generic attributes
909                }
910                _ => {}
911            }
912        }
913
914        Ok(())
915    }
916
917    fn encode_edgebreaker_attributes_split(&mut self, out_buffer: &mut EncoderBuffer) -> Status {
918        let mesh = self
919            .mesh
920            .as_ref()
921            .expect("mesh must be set before encoding");
922        let mut groups: Vec<(i8, Vec<i32>)> = Vec::new();
923        let mut position_attrs = Vec::new();
924        for i in 0..mesh.num_attributes() {
925            if mesh.attribute(i).attribute_type() == GeometryAttributeType::Position {
926                position_attrs.push(i);
927            }
928        }
929        if !position_attrs.is_empty() {
930            groups.push((-1, position_attrs));
931        }
932        for (data_id, attr_conn) in self.edgebreaker_attribute_connectivity.iter().enumerate() {
933            groups.push((data_id as i8, vec![attr_conn.attribute_id]));
934        }
935
936        out_buffer.encode_u8(groups.len() as u8);
937
938        let major = out_buffer.version_major();
939        let minor = out_buffer.version_minor();
940        let writes_traversal_method = crate::version::bitstream_version(major, minor) >= 0x0102;
941        let traversal_method: u8 = if self.options.get_speed() == 0 { 1 } else { 0 };
942        for (att_data_id, _) in &groups {
943            out_buffer.encode_u8(*att_data_id as u8);
944            let element_type = if *att_data_id >= 0
945                && !self.edgebreaker_attribute_connectivity[*att_data_id as usize].no_interior_seams
946            {
947                1 // MESH_CORNER_ATTRIBUTE
948            } else {
949                0 // MESH_VERTEX_ATTRIBUTE
950            };
951            out_buffer.encode_u8(element_type);
952            if writes_traversal_method {
953                out_buffer.encode_u8(traversal_method);
954            }
955        }
956
957        let mut decoder_types_by_group: Vec<Vec<u8>> = Vec::with_capacity(groups.len());
958
959        for (_, attr_ids) in &groups {
960            if !uses_varint_encoding(major, minor) {
961                out_buffer.encode_u32(attr_ids.len() as u32);
962            } else {
963                out_buffer.encode_varint(attr_ids.len() as u64);
964            }
965
966            for &att_id in attr_ids {
967                let att = mesh.attribute(att_id);
968                out_buffer.encode_u8(att.attribute_type() as u8);
969                out_buffer.encode_u8(att.data_type() as u8);
970                out_buffer.encode_u8(att.num_components());
971                out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
972                if !uses_varint_unique_id(major, minor) {
973                    out_buffer.encode_u16(att.unique_id() as u16);
974                } else {
975                    out_buffer.encode_varint(att.unique_id() as u64);
976                }
977            }
978
979            let mut decoder_types = Vec::with_capacity(attr_ids.len());
980            for &att_id in attr_ids {
981                let decoder_type = self.decoder_type_for_attribute(att_id);
982                out_buffer.encode_u8(decoder_type);
983                decoder_types.push(decoder_type);
984            }
985            decoder_types_by_group.push(decoder_types);
986        }
987
988        for (group_i, (att_data_id, attr_ids)) in groups.iter().enumerate() {
989            let point_ids = if *att_data_id >= 0 {
990                self.prepare_active_attribute_connectivity(*att_data_id as usize)?
991            } else {
992                self.active_corner_table = None;
993                self.active_data_to_corner_map = None;
994                self.active_vertex_to_data_map = None;
995                self.point_ids.clone()
996            };
997
998            self.encode_attribute_group_values(
999                attr_ids,
1000                &decoder_types_by_group[group_i],
1001                &point_ids,
1002                out_buffer,
1003            )?;
1004        }
1005
1006        self.active_corner_table = None;
1007        self.active_data_to_corner_map = None;
1008        self.active_vertex_to_data_map = None;
1009        Ok(())
1010    }
1011
1012    fn decoder_type_for_attribute(&self, att_id: i32) -> u8 {
1013        let mesh = self
1014            .mesh
1015            .as_ref()
1016            .expect("mesh must be set before encoding");
1017        let att = mesh.attribute(att_id);
1018        let quantization_bits = self
1019            .options
1020            .get_attribute_int(att_id, "quantization_bits", -1);
1021        let is_quantized = quantization_bits > 0
1022            && (att.data_type() == DataType::Float32 || att.data_type() == DataType::Float64);
1023        let is_normal = att.attribute_type() == GeometryAttributeType::Normal;
1024
1025        if is_quantized {
1026            if is_normal {
1027                3
1028            } else {
1029                2
1030            }
1031        } else if att.data_type() != DataType::Float32 {
1032            1
1033        } else {
1034            0
1035        }
1036    }
1037
1038    fn prepare_active_attribute_connectivity(
1039        &mut self,
1040        data_id: usize,
1041    ) -> Result<Vec<PointIndex>, DracoError> {
1042        let mesh = self
1043            .mesh
1044            .as_ref()
1045            .expect("mesh must be set before encoding");
1046        let base_ct = self
1047            .corner_table
1048            .as_ref()
1049            .ok_or_else(|| DracoError::DracoError("corner_table must be set".to_string()))?;
1050        let attr_conn = self
1051            .edgebreaker_attribute_connectivity
1052            .get(data_id)
1053            .ok_or_else(|| {
1054                DracoError::DracoError("Invalid attribute connectivity id".to_string())
1055            })?;
1056
1057        if attr_conn.no_interior_seams {
1058            self.active_corner_table = None;
1059            self.active_data_to_corner_map = None;
1060            self.active_vertex_to_data_map = None;
1061            return Ok(self.point_ids.clone());
1062        }
1063
1064        let mut attr_ct = base_ct.clone();
1065        for c_idx in 0..attr_conn.seam_edges.len() {
1066            if !attr_conn.seam_edges[c_idx] {
1067                continue;
1068            }
1069            let c = crate::geometry_indices::CornerIndex(c_idx as u32);
1070            let opp = attr_ct.opposite(c);
1071            if opp != crate::geometry_indices::INVALID_CORNER_INDEX {
1072                attr_ct.set_opposite(c, crate::geometry_indices::INVALID_CORNER_INDEX);
1073                attr_ct.set_opposite(opp, crate::geometry_indices::INVALID_CORNER_INDEX);
1074            }
1075        }
1076        let base_num_vertices = attr_ct.num_vertices();
1077        if !attr_ct.compute_vertex_corners(base_num_vertices) {
1078            return Err(DracoError::DracoError(
1079                "Failed to compute attribute seam corner table".to_string(),
1080            ));
1081        }
1082
1083        let mut point_ids = Vec::with_capacity(attr_ct.vertex_corners.len());
1084        let mut data_to_corner_map = Vec::with_capacity(attr_ct.vertex_corners.len());
1085        let mut vertex_to_data_map = vec![-1i32; attr_ct.num_vertices()];
1086        for (data_id, &corner) in attr_ct.vertex_corners.iter().enumerate() {
1087            if corner == crate::geometry_indices::INVALID_CORNER_INDEX {
1088                point_ids.push(PointIndex(0));
1089                data_to_corner_map.push(crate::geometry_indices::INVALID_CORNER_INDEX.0);
1090                continue;
1091            }
1092            let face = mesh.face(FaceIndex(corner.0 / 3));
1093            let point_id = face[(corner.0 % 3) as usize];
1094            point_ids.push(point_id);
1095            data_to_corner_map.push(corner.0);
1096            let vertex = attr_ct.vertex(corner);
1097            if vertex != crate::geometry_indices::INVALID_VERTEX_INDEX
1098                && (vertex.0 as usize) < vertex_to_data_map.len()
1099            {
1100                vertex_to_data_map[vertex.0 as usize] = data_id as i32;
1101            }
1102        }
1103
1104        self.active_corner_table = Some(attr_ct);
1105        self.active_data_to_corner_map = Some(data_to_corner_map);
1106        self.active_vertex_to_data_map = Some(vertex_to_data_map);
1107        Ok(point_ids)
1108    }
1109
1110    fn encode_attribute_group_values(
1111        &mut self,
1112        attr_ids: &[i32],
1113        decoder_types: &[u8],
1114        point_ids: &[PointIndex],
1115        out_buffer: &mut EncoderBuffer,
1116    ) -> Status {
1117        let mesh = self
1118            .mesh
1119            .as_ref()
1120            .expect("mesh must be set before encoding");
1121        let mut quantization_transforms: Vec<Option<AttributeQuantizationTransform>> = Vec::new();
1122        let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> = Vec::new();
1123
1124        for (local_i, &att_id) in attr_ids.iter().enumerate() {
1125            let att = mesh.attribute(att_id);
1126            let decoder_type = decoder_types[local_i];
1127            let quantization_bits = self
1128                .options
1129                .get_attribute_int(att_id, "quantization_bits", -1);
1130
1131            match decoder_type {
1132                3 => {
1133                    let mut encoder = SequentialNormalAttributeEncoder::new();
1134                    if !encoder.init(
1135                        self.point_cloud().expect("point_cloud set"),
1136                        att_id,
1137                        &self.options,
1138                    ) {
1139                        return Err(DracoError::DracoError(
1140                            "Failed to init normal encoder".to_string(),
1141                        ));
1142                    }
1143                    if !encoder.encode_values(
1144                        self.point_cloud().expect("point_cloud set"),
1145                        point_ids,
1146                        out_buffer,
1147                        &self.options,
1148                        self,
1149                    ) {
1150                        return Err(DracoError::DracoError(
1151                            "Failed to encode normal values".to_string(),
1152                        ));
1153                    }
1154                    normal_encoders.push(Some(encoder));
1155                    quantization_transforms.push(None);
1156                }
1157                2 => {
1158                    let mut q_transform = AttributeQuantizationTransform::new();
1159                    if !q_transform.compute_parameters(att, quantization_bits) {
1160                        return Err(DracoError::DracoError(
1161                            "Failed to compute quantization parameters".to_string(),
1162                        ));
1163                    }
1164                    let mut portable = PointAttribute::default();
1165                    if !q_transform.transform_attribute(att, point_ids, &mut portable) {
1166                        return Err(DracoError::DracoError(
1167                            "Failed to quantize attribute".to_string(),
1168                        ));
1169                    }
1170
1171                    let mut att_encoder = SequentialIntegerAttributeEncoder::new();
1172                    att_encoder.init(att_id);
1173                    if !att_encoder.encode_values(
1174                        mesh as &PointCloud,
1175                        point_ids,
1176                        out_buffer,
1177                        &self.options,
1178                        self,
1179                        Some(&portable),
1180                        true,
1181                    ) {
1182                        return Err(DracoError::DracoError(format!(
1183                            "Failed to encode attribute {}",
1184                            att_id
1185                        )));
1186                    }
1187                    quantization_transforms.push(Some(q_transform));
1188                    normal_encoders.push(None);
1189                }
1190                1 => {
1191                    let mut att_encoder = SequentialIntegerAttributeEncoder::new();
1192                    att_encoder.init(att_id);
1193                    if !att_encoder.encode_values(
1194                        mesh as &PointCloud,
1195                        point_ids,
1196                        out_buffer,
1197                        &self.options,
1198                        self,
1199                        None,
1200                        true,
1201                    ) {
1202                        return Err(DracoError::DracoError(format!(
1203                            "Failed to encode attribute {}",
1204                            att_id
1205                        )));
1206                    }
1207                    quantization_transforms.push(None);
1208                    normal_encoders.push(None);
1209                }
1210                0 => {
1211                    let mut att_encoder = SequentialAttributeEncoder::new();
1212                    att_encoder.init(att_id);
1213                    if !att_encoder.encode_values(mesh as &PointCloud, point_ids, out_buffer) {
1214                        return Err(DracoError::DracoError(format!(
1215                            "Failed to encode attribute {}",
1216                            att_id
1217                        )));
1218                    }
1219                    quantization_transforms.push(None);
1220                    normal_encoders.push(None);
1221                }
1222                _ => {
1223                    return Err(DracoError::DracoError(format!(
1224                        "Unsupported encoder type {}",
1225                        decoder_type
1226                    )));
1227                }
1228            }
1229        }
1230
1231        for (local_i, &decoder_type) in decoder_types.iter().enumerate() {
1232            match decoder_type {
1233                3 => {
1234                    let major = out_buffer.version_major();
1235                    let minor = out_buffer.version_minor();
1236                    let bitstream_version = crate::version::bitstream_version(major, minor);
1237                    if bitstream_version != 0 && bitstream_version < 0x0200 {
1238                        continue;
1239                    }
1240                    if let Some(ref encoder) = normal_encoders[local_i] {
1241                        if !encoder.encode_data_needed_by_portable_transform(out_buffer) {
1242                            return Err(DracoError::DracoError(
1243                                "Failed to encode normal transform data".to_string(),
1244                            ));
1245                        }
1246                    }
1247                }
1248                2 => {
1249                    if let Some(ref q_transform) = quantization_transforms[local_i] {
1250                        if !q_transform.encode_parameters(out_buffer) {
1251                            return Err(DracoError::DracoError(
1252                                "Failed to encode quantization parameters".to_string(),
1253                            ));
1254                        }
1255                    }
1256                }
1257                1 | 0 => {}
1258                _ => {}
1259            }
1260        }
1261
1262        Ok(())
1263    }
1264
1265    fn compute_number_of_encoded_faces(&mut self) {
1266        if let Some(ref mesh) = self.mesh {
1267            self.num_encoded_faces = mesh.num_faces();
1268        }
1269    }
1270
1271    fn build_encoded_mesh_info(&mut self) -> Status {
1272        let num_attributes = self
1273            .mesh
1274            .as_ref()
1275            .expect("mesh must be set before encoding")
1276            .num_attributes();
1277        let mut attributes = Vec::with_capacity(num_attributes as usize);
1278        let mut encoded_num_points = self.point_ids.len();
1279
1280        for att_id in 0..num_attributes {
1281            let point_ids = self.encoded_point_ids_for_attribute(att_id)?;
1282            let num_encoded_values = point_ids.len();
1283            encoded_num_points = encoded_num_points.max(num_encoded_values);
1284
1285            let (position_min, position_max) =
1286                self.position_bounds_for_attribute(att_id, &point_ids)?;
1287            let att = self
1288                .mesh
1289                .as_ref()
1290                .expect("mesh must be set before encoding")
1291                .attribute(att_id);
1292            attributes.push(EncodedAttributeInfo {
1293                source_attribute_id: att_id,
1294                attribute_type: att.attribute_type(),
1295                data_type: att.data_type(),
1296                num_components: att.num_components(),
1297                normalized: att.normalized(),
1298                unique_id: att.unique_id(),
1299                num_encoded_values,
1300                position_min,
1301                position_max,
1302            });
1303        }
1304
1305        let (source_num_points, num_faces) = self
1306            .mesh
1307            .as_ref()
1308            .map(|mesh| (mesh.num_points(), mesh.num_faces()))
1309            .expect("mesh must be set before encoding");
1310        if self.method == 0 {
1311            encoded_num_points = source_num_points;
1312        } else {
1313            encoded_num_points = self.encoded_num_points_for_mesh(encoded_num_points)?;
1314        }
1315
1316        self.active_corner_table = None;
1317        self.active_data_to_corner_map = None;
1318        self.active_vertex_to_data_map = None;
1319        self.encoded_mesh_info = Some(EncodedMeshInfo {
1320            encoding_method: self.method,
1321            num_encoded_faces: num_faces,
1322            num_encoded_points: encoded_num_points,
1323            attributes,
1324        });
1325        Ok(())
1326    }
1327
1328    fn encoded_point_ids_for_attribute(
1329        &mut self,
1330        att_id: i32,
1331    ) -> Result<Vec<PointIndex>, DracoError> {
1332        if self.method == 0 || self.use_single_connectivity {
1333            return Ok(self.point_ids.clone());
1334        }
1335
1336        if let Some(data_id) = self
1337            .edgebreaker_attribute_connectivity
1338            .iter()
1339            .position(|connectivity| connectivity.attribute_id == att_id)
1340        {
1341            return self.prepare_active_attribute_connectivity(data_id);
1342        }
1343
1344        Ok(self.point_ids.clone())
1345    }
1346
1347    fn encoded_num_points_for_mesh(&mut self, base_num_points: usize) -> Result<usize, DracoError> {
1348        if self.method == 0 || self.use_single_connectivity {
1349            return Ok(base_num_points);
1350        }
1351
1352        let mut num_points = base_num_points;
1353        for data_id in 0..self.edgebreaker_attribute_connectivity.len() {
1354            if self.edgebreaker_attribute_connectivity[data_id].no_interior_seams {
1355                continue;
1356            }
1357            let point_ids = self.prepare_active_attribute_connectivity(data_id)?;
1358            num_points = num_points.max(point_ids.len());
1359        }
1360        self.active_corner_table = None;
1361        self.active_data_to_corner_map = None;
1362        self.active_vertex_to_data_map = None;
1363        Ok(num_points)
1364    }
1365
1366    fn position_bounds_for_attribute(
1367        &self,
1368        att_id: i32,
1369        point_ids: &[PointIndex],
1370    ) -> Result<PositionBounds, DracoError> {
1371        let mesh = self
1372            .mesh
1373            .as_ref()
1374            .expect("mesh must be set before encoding");
1375        let att = mesh.attribute(att_id);
1376        if att.attribute_type() != GeometryAttributeType::Position {
1377            return Ok((None, None));
1378        }
1379        if att.num_components() != 3 || att.data_type() != DataType::Float32 {
1380            return Ok((None, None));
1381        }
1382
1383        if self.decoder_type_for_attribute(att_id) == 2 {
1384            let quantization_bits = self
1385                .options
1386                .get_attribute_int(att_id, "quantization_bits", -1);
1387            let mut q_transform = AttributeQuantizationTransform::new();
1388            if !q_transform.compute_parameters(att, quantization_bits) {
1389                return Err(DracoError::DracoError(
1390                    "Failed to compute position quantization parameters".to_string(),
1391                ));
1392            }
1393
1394            let mut portable = PointAttribute::default();
1395            if !q_transform.transform_attribute(att, point_ids, &mut portable) {
1396                return Err(DracoError::DracoError(
1397                    "Failed to quantize position attribute for encoded mesh info".to_string(),
1398                ));
1399            }
1400
1401            let mut dequantized = PointAttribute::new();
1402            dequantized.try_init(
1403                GeometryAttributeType::Position,
1404                3,
1405                DataType::Float32,
1406                false,
1407                portable.size(),
1408            )?;
1409            if !q_transform.inverse_transform_attribute(&portable, &mut dequantized) {
1410                return Err(DracoError::DracoError(
1411                    "Failed to dequantize position attribute for encoded mesh info".to_string(),
1412                ));
1413            }
1414
1415            return Self::position_bounds_from_attribute(&dequantized, &[]);
1416        }
1417
1418        Self::position_bounds_from_attribute(att, point_ids)
1419    }
1420
1421    fn position_bounds_from_attribute(
1422        att: &PointAttribute,
1423        point_ids: &[PointIndex],
1424    ) -> Result<PositionBounds, DracoError> {
1425        let count = if point_ids.is_empty() {
1426            att.size()
1427        } else {
1428            point_ids.len()
1429        };
1430        if count == 0 {
1431            return Ok((None, None));
1432        }
1433
1434        let stride = usize::try_from(att.byte_stride()).map_err(|_| {
1435            DracoError::DracoError("Position attribute has invalid byte stride".to_string())
1436        })?;
1437        let bytes = att.buffer().data();
1438        let mut min = [f32::INFINITY; 3];
1439        let mut max = [f32::NEG_INFINITY; 3];
1440
1441        for i in 0..count {
1442            let point = if point_ids.is_empty() {
1443                PointIndex(i as u32)
1444            } else {
1445                point_ids[i]
1446            };
1447            let value_index = att.mapped_index(point);
1448            if value_index == INVALID_ATTRIBUTE_VALUE_INDEX {
1449                return Err(DracoError::DracoError(
1450                    "Position attribute point map contains an invalid entry".to_string(),
1451                ));
1452            }
1453
1454            let value_offset = (value_index.0 as usize)
1455                .checked_mul(stride)
1456                .ok_or_else(|| {
1457                    DracoError::DracoError("Position attribute offset overflow".to_string())
1458                })?;
1459            for component in 0..3 {
1460                let offset = value_offset
1461                    .checked_add(component * DataType::Float32.byte_length())
1462                    .ok_or_else(|| {
1463                        DracoError::DracoError("Position attribute offset overflow".to_string())
1464                    })?;
1465                let end = offset
1466                    .checked_add(DataType::Float32.byte_length())
1467                    .ok_or_else(|| {
1468                        DracoError::DracoError("Position attribute offset overflow".to_string())
1469                    })?;
1470                let Some(component_bytes) = bytes.get(offset..end) else {
1471                    return Err(DracoError::DracoError(
1472                        "Position attribute buffer is shorter than metadata".to_string(),
1473                    ));
1474                };
1475                let value = f32::from_le_bytes([
1476                    component_bytes[0],
1477                    component_bytes[1],
1478                    component_bytes[2],
1479                    component_bytes[3],
1480                ]);
1481                min[component] = min[component].min(value);
1482                max[component] = max[component].max(value);
1483            }
1484        }
1485
1486        Ok((
1487            Some(min.into_iter().map(f64::from).collect()),
1488            Some(max.into_iter().map(f64::from).collect()),
1489        ))
1490    }
1491}
1492
1493impl Default for MeshEncoder {
1494    fn default() -> Self {
1495        Self::new()
1496    }
1497}