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