Skip to main content

draco_core/
mesh_encoder.rs

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