Skip to main content

draco_core/
mesh_decoder.rs

1use crate::compression_config::EncodedGeometryType;
2use crate::decoder_buffer::DecoderBuffer;
3use crate::draco_types::DataType;
4use crate::geometry_attribute::{GeometryAttributeType, PointAttribute};
5use crate::mesh::Mesh;
6use crate::point_cloud_decoder::PointCloudDecoder;
7use crate::sequential_generic_attribute_decoder::SequentialGenericAttributeDecoder;
8use crate::sequential_integer_attribute_decoder::SequentialIntegerAttributeDecoder;
9use crate::status::{DracoError, Status};
10
11use crate::attribute_octahedron_transform::AttributeOctahedronTransform;
12use crate::attribute_quantization_transform::AttributeQuantizationTransform;
13use crate::attribute_transform::AttributeTransform;
14use crate::corner_table::CornerTable;
15use crate::geometry_indices::AttributeValueIndex;
16use crate::geometry_indices::{
17    CornerIndex, FaceIndex, PointIndex, VertexIndex, INVALID_CORNER_INDEX, INVALID_VERTEX_INDEX,
18};
19
20use crate::mesh_edgebreaker_decoder::MeshEdgebreakerDecoder;
21use crate::metadata::{GeometryMetadata, METADATA_FLAG_MASK};
22use crate::test_event_log;
23use crate::version::version_at_least;
24
25/// Output of an edgebreaker attribute traversal:
26/// `(point ids in traversal order, processed corners, vertex -> data-id map)`.
27type AttributeTraversalArrays = (Vec<PointIndex>, Vec<u32>, Vec<i32>);
28
29fn validate_num_attributes_in_decoder(
30    num_attributes_in_decoder: usize,
31    remaining_bytes: usize,
32) -> Result<(), DracoError> {
33    // Each attribute must have at least type, data type, component count,
34    // normalized flag, unique id, and a decoder type byte. Reject impossible
35    // counts before reserving vectors from untrusted input.
36    const MIN_ATTRIBUTE_BYTES: usize = 6;
37    if num_attributes_in_decoder == 0
38        || num_attributes_in_decoder > remaining_bytes / MIN_ATTRIBUTE_BYTES
39    {
40        return Err(DracoError::DracoError(
41            "Invalid number of attributes".to_string(),
42        ));
43    }
44    Ok(())
45}
46
47fn validate_num_components(num_components: u8) -> Result<(), DracoError> {
48    if num_components == 0 {
49        return Err(DracoError::DracoError(
50            "Invalid attribute component count".to_string(),
51        ));
52    }
53    Ok(())
54}
55
56fn copy_point_mapping(
57    source: &PointAttribute,
58    target: &mut PointAttribute,
59    num_points: usize,
60) -> Result<(), DracoError> {
61    target.set_explicit_mapping(num_points);
62    for point in 0..num_points {
63        let point_id = PointIndex(point as u32);
64        target.try_set_point_map_entry(point_id, source.mapped_index(point_id))?;
65    }
66    Ok(())
67}
68
69fn build_vertex_to_data_map_from_corner_map(
70    corner_table: &CornerTable,
71    data_to_corner_map: &[u32],
72) -> Result<Vec<i32>, DracoError> {
73    let mut vertex_to_data_map = vec![-1i32; corner_table.num_vertices()];
74    for (i, &corner_id) in data_to_corner_map.iter().enumerate() {
75        let corner = CornerIndex(corner_id);
76        if corner == INVALID_CORNER_INDEX {
77            continue;
78        }
79        if corner.0 as usize >= corner_table.num_corners() {
80            return Err(DracoError::DracoError(
81                "Data-to-corner map references an invalid corner".to_string(),
82            ));
83        }
84        let vertex = corner_table.vertex(corner);
85        if vertex == INVALID_VERTEX_INDEX {
86            continue;
87        }
88        let Some(slot) = vertex_to_data_map.get_mut(vertex.0 as usize) else {
89            return Err(DracoError::DracoError(
90                "Data-to-corner map references an invalid vertex".to_string(),
91            ));
92        };
93        *slot = i as i32;
94    }
95    Ok(vertex_to_data_map)
96}
97
98fn upsert_portable_attribute(
99    portable_attributes_by_id: &mut Vec<(i32, PointAttribute)>,
100    att_id: i32,
101    portable: PointAttribute,
102) {
103    if let Some((_, existing)) = portable_attributes_by_id
104        .iter_mut()
105        .find(|(id, _)| *id == att_id)
106    {
107        *existing = portable;
108    } else {
109        portable_attributes_by_id.push((att_id, portable));
110    }
111}
112
113/// Decoder for Draco triangle mesh bitstreams.
114///
115/// `MeshDecoder` reads a `.drc` bitstream produced by `MeshEncoder` (or C++
116/// Draco) and reconstructs a [`Mesh`]: faces, attributes, and any
117/// metadata. It handles both EdgeBreaker and sequential connectivity and
118/// dequantizes attributes back to their original data types.
119///
120/// A point-cloud bitstream (geometry type 0) is also accepted and decoded into
121/// the mesh's underlying [`PointCloud`](crate::PointCloud) with no faces.
122///
123/// # Examples
124///
125/// ```
126/// use draco_core::{DecoderBuffer, Mesh, MeshDecoder};
127///
128/// # fn decode(drc_bytes: &[u8]) -> Result<(), draco_core::DracoError> {
129/// let mut mesh = Mesh::new();
130/// MeshDecoder::new().decode(&mut DecoderBuffer::new(drc_bytes), &mut mesh)?;
131/// println!("{} faces, {} points", mesh.num_faces(), mesh.num_points());
132/// # Ok(())
133/// # }
134/// ```
135///
136/// A full encode/decode round trip is shown on the `MeshEncoder` type docs.
137pub struct MeshDecoder {
138    geometry_type: EncodedGeometryType,
139    method: u8,
140    flags: u16,
141    version_major: u8,
142    version_minor: u8,
143    corner_table: Option<Box<CornerTable>>,
144    edgebreaker_data_to_corner_map: Option<Vec<u32>>,
145    edgebreaker_attribute_seam_corners: Vec<Vec<u32>>,
146    edgebreaker_attribute_corner_tables: Vec<CornerTable>,
147    edgebreaker_attribute_vertices_on_seam: Vec<Vec<bool>>,
148    edgebreaker_processed_connectivity_corners: Vec<u32>,
149    edgebreaker_vertex_to_corner_map: Vec<u32>,
150    edgebreaker_is_vert_hole: Vec<bool>,
151    traversal_method: u8,
152}
153
154impl Default for MeshDecoder {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160impl MeshDecoder {
161    /// Creates a mesh decoder with default state.
162    pub fn new() -> Self {
163        Self {
164            geometry_type: EncodedGeometryType::TriangularMesh,
165            method: 0,
166            flags: 0,
167            version_major: 0,
168            version_minor: 0,
169            corner_table: None,
170            edgebreaker_data_to_corner_map: None,
171            edgebreaker_attribute_seam_corners: Vec::new(),
172            edgebreaker_attribute_corner_tables: Vec::new(),
173            edgebreaker_attribute_vertices_on_seam: Vec::new(),
174            edgebreaker_processed_connectivity_corners: Vec::new(),
175            edgebreaker_vertex_to_corner_map: Vec::new(),
176            edgebreaker_is_vert_hole: Vec::new(),
177            traversal_method: 0,
178        }
179    }
180
181    /// Decodes a Draco mesh from `in_buffer` into `out_mesh`.
182    ///
183    /// Reads the header, optional metadata, connectivity, and attributes,
184    /// populating `out_mesh`. Point-cloud bitstreams are decoded into the
185    /// mesh's underlying point cloud (no faces).
186    ///
187    /// # Errors
188    ///
189    /// Returns an error if the magic/header is invalid, the bitstream version
190    /// is unsupported, the geometry is malformed, or a required feature (such
191    /// as `point_cloud_decode`) is disabled.
192    pub fn decode(&mut self, in_buffer: &mut DecoderBuffer, out_mesh: &mut Mesh) -> Status {
193        // 1. Decode Header
194        self.decode_header(in_buffer)?;
195
196        // 2. Decode Metadata
197        if version_at_least(
198            self.version_major,
199            self.version_minor,
200            crate::version::VERSION_FLAGS_INTRODUCED,
201        ) && (self.flags & METADATA_FLAG_MASK) != 0
202        {
203            self.decode_metadata(in_buffer, out_mesh)?;
204        }
205
206        if self.geometry_type == EncodedGeometryType::PointCloud {
207            #[cfg(feature = "point_cloud_decode")]
208            {
209                // Point cloud files (geometry_type == 0) have no connectivity.
210                // Delegate to PointCloudDecoder which reads num_points + attributes
211                // directly into the Mesh's underlying PointCloud.
212                let mut pc_decoder = crate::point_cloud_decoder::PointCloudDecoder::new();
213                return pc_decoder.decode_after_header(
214                    self.version_major,
215                    self.version_minor,
216                    self.method,
217                    in_buffer,
218                    &mut *out_mesh,
219                );
220            }
221            #[cfg(not(feature = "point_cloud_decode"))]
222            {
223                return Err(DracoError::DracoError(
224                    "Point cloud decode support is disabled".to_string(),
225                ));
226            }
227        }
228
229        // 3. Decode Connectivity
230        self.decode_connectivity(in_buffer, out_mesh)?;
231
232        // 4. Decode Attributes
233        self.decode_attributes(in_buffer, out_mesh)
234    }
235
236    /// Test helper: Returns a reference to the decoded corner table (if any).
237    /// This is useful in unit tests that wish to compare encoder/decoder
238    /// corner table structures without accessing internal decoder types.
239    pub fn get_corner_table_ref(&self) -> Option<&crate::corner_table::CornerTable> {
240        self.corner_table.as_deref()
241    }
242
243    fn decode_metadata(
244        &self,
245        in_buffer: &mut DecoderBuffer,
246        out_mesh: &mut Mesh,
247    ) -> Result<(), DracoError> {
248        let metadata = GeometryMetadata::decode(in_buffer)
249            .map_err(|_| DracoError::DracoError("Failed to decode metadata".to_string()))?;
250        out_mesh.set_metadata(Some(metadata));
251        Ok(())
252    }
253
254    fn decode_header(&mut self, buffer: &mut DecoderBuffer) -> Status {
255        let mut magic = [0u8; 5];
256        buffer.decode_bytes(&mut magic)?;
257        if &magic != b"DRACO" {
258            return Err(DracoError::DracoError("Invalid magic".to_string()));
259        }
260
261        self.version_major = buffer.decode_u8()?;
262        self.version_minor = buffer.decode_u8()?;
263        buffer.set_version(self.version_major, self.version_minor);
264
265        let g_type = buffer.decode_u8()?;
266        self.geometry_type = match g_type {
267            0 => EncodedGeometryType::PointCloud,
268            1 => EncodedGeometryType::TriangularMesh,
269            _ => return Err(DracoError::DracoError("Invalid geometry type".to_string())),
270        };
271
272        self.method = buffer.decode_u8()?;
273
274        // Flags field is always present in the binary header (C++ reads unconditionally).
275        // The VERSION_FLAGS_INTRODUCED constant refers to when flag bits gained meaning,
276        // not when the bytes were added to the format.
277        self.flags = buffer
278            .decode_u16()
279            .map_err(|_| DracoError::DracoError("Failed to decode flags".to_string()))?;
280
281        Ok(())
282    }
283
284    fn decode_connectivity(&mut self, buffer: &mut DecoderBuffer, mesh: &mut Mesh) -> Status {
285        if self.method == 1 {
286            let mut eb_decoder = MeshEdgebreakerDecoder::new();
287            eb_decoder.decode_connectivity(buffer, mesh)?;
288
289            // Preserve edgebreaker-derived maps for attribute decoding.
290            self.edgebreaker_data_to_corner_map = eb_decoder.take_data_to_corner_map();
291            self.edgebreaker_attribute_seam_corners = eb_decoder.take_attribute_seam_corners();
292            self.edgebreaker_processed_connectivity_corners =
293                eb_decoder.get_processed_connectivity_corners().to_vec();
294            self.edgebreaker_vertex_to_corner_map = eb_decoder.get_vertex_to_corner_map().to_vec();
295            self.edgebreaker_is_vert_hole = eb_decoder.take_is_vert_hole();
296            self.traversal_method = eb_decoder.get_traversal_decoder_type();
297
298            // Use the edgebreaker decoder's corner table with proper opposite mappings
299            // instead of building a new one from mesh faces
300            if let Some(ct) = eb_decoder.take_corner_table() {
301                self.corner_table = Some(Box::new(ct));
302            } else {
303                return Err(DracoError::DracoError(
304                    "Edgebreaker decoder did not provide corner table".to_string(),
305                ));
306            }
307            self.rebuild_edgebreaker_attribute_corner_tables()?;
308            self.assign_edgebreaker_points_to_corners(mesh)?;
309        } else {
310            // Sequential connectivity encoding
311            // C++ MeshSequentialDecoder uses raw u32 for v < 2.2, varint for v >= 2.2
312            let seq_uses_varint = version_at_least(self.version_major, self.version_minor, (2, 2));
313            let (num_faces, num_points) = if !seq_uses_varint {
314                #[cfg(not(feature = "legacy_bitstream_decode"))]
315                {
316                    return Err(DracoError::BitstreamVersionUnsupported);
317                }
318                #[cfg(feature = "legacy_bitstream_decode")]
319                {
320                    let nf = buffer.decode_u32()? as usize;
321                    let np = buffer.decode_u32()? as usize;
322                    (nf, np)
323                }
324            } else {
325                let nf = buffer.decode_varint()? as usize;
326                let np = buffer.decode_varint()? as usize;
327                (nf, np)
328            };
329            // Consistency guard: a sequential mesh encodes connectivity indices
330            // for each face and attribute data for each point in the bytes that
331            // follow, so a count beyond the remaining bit budget is malformed.
332            // This bounds the connectivity and per-attribute buffers that are
333            // sized by these counts and prevents memory amplification from a tiny
334            // header. Relative input-consistency check, runs once, off hot path.
335            let max_count = buffer.remaining_size().saturating_mul(8);
336            if num_faces > max_count || num_points > max_count {
337                return Err(DracoError::DracoError(
338                    "Sequential mesh face/point count exceeds remaining bitstream size".to_string(),
339                ));
340            }
341            let num_indices = validate_mesh_index_count(num_faces)?;
342            mesh.set_num_points(num_points);
343
344            if num_faces > 0 && num_points > 0 {
345                let connectivity_method = buffer.decode_u8()?;
346                if connectivity_method == 0 {
347                    // Compressed
348                    let mut encoded_indices = make_zeroed_indices(num_indices)?;
349                    let options = crate::symbol_encoding::SymbolEncodingOptions::default();
350                    if !crate::symbol_encoding::decode_symbols(
351                        num_indices,
352                        1,
353                        &options,
354                        buffer,
355                        &mut encoded_indices,
356                    ) {
357                        return Err(DracoError::DracoError(
358                            "Failed to decode compressed sequential connectivity".to_string(),
359                        ));
360                    }
361                    let mut indices = make_zeroed_indices(num_indices)?;
362                    let mut last_index_value = 0i32;
363                    for (dst, encoded_val) in indices.iter_mut().zip(encoded_indices) {
364                        let mut index_diff = (encoded_val >> 1) as i32;
365                        if (encoded_val & 1) != 0 {
366                            if index_diff > last_index_value {
367                                return Err(DracoError::DracoError(
368                                    "Sequential connectivity index underflow".to_string(),
369                                ));
370                            }
371                            index_diff = -index_diff;
372                        } else if index_diff > i32::MAX - last_index_value {
373                            return Err(DracoError::DracoError(
374                                "Sequential connectivity index overflow".to_string(),
375                            ));
376                        }
377                        let index_value = last_index_value + index_diff;
378                        *dst = index_value as u32;
379                        last_index_value = index_value;
380                    }
381                    mesh.try_set_num_faces(num_faces)?;
382                    mesh.set_faces_from_flat_indices(&indices);
383                } else if connectivity_method == 1 {
384                    // Raw - bulk read indices from buffer
385                    if num_points < 256 {
386                        let bytes_needed = num_indices;
387                        let bytes = buffer.decode_slice(bytes_needed).map_err(|_| {
388                            DracoError::DracoError("Not enough data for u8 indices".to_string())
389                        })?;
390                        mesh.try_set_num_faces(num_faces)?;
391                        mesh.set_faces_from_u8_indices(bytes);
392                    } else if num_points < 65536 {
393                        let bytes_needed = num_indices.checked_mul(2).ok_or_else(|| {
394                            DracoError::DracoError("Mesh u16 index byte count overflow".to_string())
395                        })?;
396                        let bytes = buffer.decode_slice(bytes_needed).map_err(|_| {
397                            DracoError::DracoError("Not enough data for u16 indices".to_string())
398                        })?;
399                        mesh.try_set_num_faces(num_faces)?;
400                        mesh.set_faces_from_le_u16_indices(bytes);
401                    } else if num_points < (1 << 21) && seq_uses_varint {
402                        mesh.try_set_num_faces(num_faces)?;
403                        for face_id in 0..num_faces {
404                            mesh.set_face_from_indices(
405                                face_id,
406                                [
407                                    buffer.decode_varint()? as u32,
408                                    buffer.decode_varint()? as u32,
409                                    buffer.decode_varint()? as u32,
410                                ],
411                            );
412                        }
413                    } else {
414                        let bytes_needed = num_indices.checked_mul(4).ok_or_else(|| {
415                            DracoError::DracoError("Mesh u32 index byte count overflow".to_string())
416                        })?;
417                        let bytes = buffer.decode_slice(bytes_needed).map_err(|_| {
418                            DracoError::DracoError("Not enough data for u32 indices".to_string())
419                        })?;
420                        mesh.try_set_num_faces(num_faces)?;
421                        mesh.set_faces_from_le_u32_indices(bytes);
422                    }
423                } else {
424                    return Err(DracoError::DracoError(format!(
425                        "Unsupported sequential connectivity method: {}",
426                        connectivity_method
427                    )));
428                }
429                // If sequential mode uses compressed connectivity, we may need
430                // to remap indices for deduplication. For raw mode above,
431                // face indices match the flat array.
432
433                // Note: Sequential encoding does NOT use a CornerTable.
434                // C++ MeshSequentialDecoder::DecodeConnectivity() just calls mesh->AddFace()
435                // and uses LinearSequencer for attribute decoding (identity mapping).
436                // Corner tables are only needed for Edgebreaker's mesh prediction schemes.
437                // self.corner_table remains None for sequential decoding.
438            }
439        }
440
441        Ok(())
442    }
443
444    fn make_attribute_corner_table(
445        base_ct: &CornerTable,
446        seam_corners: &[u32],
447    ) -> Result<(CornerTable, Vec<bool>), DracoError> {
448        let mut ct = base_ct.clone();
449        let mut is_edge_on_seam = vec![false; base_ct.num_corners()];
450        let mut is_vertex_on_seam = vec![false; base_ct.num_vertices()];
451
452        for &c_u32 in seam_corners {
453            let c = CornerIndex(c_u32);
454            if c == INVALID_CORNER_INDEX {
455                continue;
456            }
457            if c.0 as usize >= base_ct.num_corners() {
458                return Err(DracoError::DracoError(
459                    "Invalid Edgebreaker attribute seam corner".to_string(),
460                ));
461            }
462            is_edge_on_seam[c.0 as usize] = true;
463            ct.set_opposite(c, INVALID_CORNER_INDEX);
464
465            let next_vertex = base_ct.vertex(base_ct.next(c));
466            if next_vertex != crate::geometry_indices::INVALID_VERTEX_INDEX {
467                is_vertex_on_seam[next_vertex.0 as usize] = true;
468            }
469            let previous_vertex = base_ct.vertex(base_ct.previous(c));
470            if previous_vertex != crate::geometry_indices::INVALID_VERTEX_INDEX {
471                is_vertex_on_seam[previous_vertex.0 as usize] = true;
472            }
473
474            let opp = base_ct.opposite(c);
475            if opp != INVALID_CORNER_INDEX {
476                if opp.0 as usize >= base_ct.num_corners() {
477                    return Err(DracoError::DracoError(
478                        "Invalid Edgebreaker attribute seam opposite corner".to_string(),
479                    ));
480                }
481                is_edge_on_seam[opp.0 as usize] = true;
482                ct.set_opposite(opp, INVALID_CORNER_INDEX);
483
484                let next_vertex = base_ct.vertex(base_ct.next(opp));
485                if next_vertex != crate::geometry_indices::INVALID_VERTEX_INDEX {
486                    is_vertex_on_seam[next_vertex.0 as usize] = true;
487                }
488                let previous_vertex = base_ct.vertex(base_ct.previous(opp));
489                if previous_vertex != crate::geometry_indices::INVALID_VERTEX_INDEX {
490                    is_vertex_on_seam[previous_vertex.0 as usize] = true;
491                }
492            }
493        }
494
495        let seam_opposite = |corner: CornerIndex| -> CornerIndex {
496            if corner == INVALID_CORNER_INDEX {
497                return INVALID_CORNER_INDEX;
498            }
499            if is_edge_on_seam[corner.0 as usize] {
500                INVALID_CORNER_INDEX
501            } else {
502                base_ct.opposite(corner)
503            }
504        };
505        let seam_swing_left = |corner: CornerIndex| -> CornerIndex {
506            base_ct.next(seam_opposite(base_ct.next(corner)))
507        };
508
509        ct.corner_to_vertex_map
510            .fill(crate::geometry_indices::INVALID_VERTEX_INDEX);
511        ct.vertex_corners.clear();
512
513        let mut num_new_vertices = 0usize;
514        for v in 0..base_ct.num_vertices() {
515            let c = base_ct.left_most_corner(VertexIndex(v as u32));
516            if c == INVALID_CORNER_INDEX {
517                continue;
518            }
519
520            let mut first_vertex_id = VertexIndex(num_new_vertices as u32);
521            num_new_vertices += 1;
522
523            let mut first_c = c;
524            if is_vertex_on_seam[v] {
525                let mut act_c = seam_swing_left(first_c);
526                let mut swing_steps = 0usize;
527                let max_swing_steps = base_ct.num_corners().saturating_add(1);
528                while act_c != INVALID_CORNER_INDEX {
529                    swing_steps += 1;
530                    if swing_steps > max_swing_steps {
531                        return Err(DracoError::DracoError(
532                            "Attribute seam left-swing traversal did not terminate".to_string(),
533                        ));
534                    }
535                    first_c = act_c;
536                    act_c = seam_swing_left(act_c);
537                }
538            }
539
540            ct.corner_to_vertex_map[first_c.0 as usize] = first_vertex_id;
541            ct.vertex_corners.push(first_c);
542
543            let mut act_c = base_ct.swing_right(first_c);
544            let mut swing_steps = 0usize;
545            let max_swing_steps = base_ct.num_corners().saturating_add(1);
546            while act_c != INVALID_CORNER_INDEX && act_c != first_c {
547                swing_steps += 1;
548                if swing_steps > max_swing_steps {
549                    return Err(DracoError::DracoError(
550                        "Attribute seam right-swing traversal did not terminate".to_string(),
551                    ));
552                }
553                if is_edge_on_seam[base_ct.next(act_c).0 as usize] {
554                    first_vertex_id = VertexIndex(num_new_vertices as u32);
555                    num_new_vertices += 1;
556                    ct.vertex_corners.push(act_c);
557                }
558                ct.corner_to_vertex_map[act_c.0 as usize] = first_vertex_id;
559                act_c = base_ct.swing_right(act_c);
560            }
561        }
562
563        ct.num_original_vertices = ct.vertex_corners.len();
564        ct.num_isolated_vertices = 0;
565        ct.num_degenerated_faces = base_ct.num_degenerated_faces;
566
567        Ok((ct, is_vertex_on_seam))
568    }
569
570    fn rebuild_edgebreaker_attribute_corner_tables(&mut self) -> Status {
571        self.edgebreaker_attribute_corner_tables.clear();
572        self.edgebreaker_attribute_vertices_on_seam.clear();
573        let Some(base_ct) = self.corner_table.as_deref() else {
574            return Ok(());
575        };
576        for seam_corners in &self.edgebreaker_attribute_seam_corners {
577            let (corner_table, vertices_on_seam) =
578                Self::make_attribute_corner_table(base_ct, seam_corners)?;
579            self.edgebreaker_attribute_corner_tables.push(corner_table);
580            self.edgebreaker_attribute_vertices_on_seam
581                .push(vertices_on_seam);
582        }
583        Ok(())
584    }
585
586    fn assign_edgebreaker_points_to_corners(&self, mesh: &mut Mesh) -> Status {
587        if self.edgebreaker_attribute_corner_tables.is_empty() {
588            return Ok(());
589        }
590        let Some(base_ct) = self.corner_table.as_deref() else {
591            return Ok(());
592        };
593
594        let num_corners = base_ct.num_corners();
595        let mut point_to_corner_map: Vec<u32> = Vec::new();
596        let mut corner_to_point_map = vec![u32::MAX; num_corners];
597
598        for v in 0..base_ct.num_vertices() {
599            let mut c = base_ct.left_most_corner(VertexIndex(v as u32));
600            if c == INVALID_CORNER_INDEX {
601                continue;
602            }
603
604            let mut first_corner = c;
605            let is_vert_hole = self
606                .edgebreaker_is_vert_hole
607                .get(v)
608                .copied()
609                .unwrap_or_else(|| {
610                    Self::is_vertex_on_boundary_impl(base_ct, VertexIndex(v as u32))
611                });
612            if !is_vert_hole {
613                for (attr_index, attr_ct) in
614                    self.edgebreaker_attribute_corner_tables.iter().enumerate()
615                {
616                    let base_vertex = base_ct.vertex(c);
617                    let Some(vertices_on_seam) =
618                        self.edgebreaker_attribute_vertices_on_seam.get(attr_index)
619                    else {
620                        continue;
621                    };
622                    if base_vertex == crate::geometry_indices::INVALID_VERTEX_INDEX
623                        || !vertices_on_seam
624                            .get(base_vertex.0 as usize)
625                            .copied()
626                            .unwrap_or(false)
627                    {
628                        continue;
629                    }
630                    let vertex_at_first = attr_ct.vertex(c);
631                    let mut act_c = base_ct.swing_right(c);
632                    let mut seam_found = false;
633                    let mut swing_steps = 0usize;
634                    let max_swing_steps = base_ct.num_corners().saturating_add(1);
635                    while act_c != INVALID_CORNER_INDEX && act_c != c {
636                        swing_steps += 1;
637                        if swing_steps > max_swing_steps {
638                            return Err(DracoError::DracoError(
639                                "Edgebreaker seam search traversal did not terminate".to_string(),
640                            ));
641                        }
642                        if attr_ct.vertex(act_c) != vertex_at_first {
643                            first_corner = act_c;
644                            seam_found = true;
645                            break;
646                        }
647                        act_c = base_ct.swing_right(act_c);
648                    }
649                    if seam_found {
650                        break;
651                    }
652                }
653            }
654
655            c = first_corner;
656            corner_to_point_map[c.0 as usize] = point_to_corner_map.len() as u32;
657            point_to_corner_map.push(c.0);
658
659            let mut prev_c = c;
660            c = base_ct.swing_right(c);
661            let mut swing_steps = 0usize;
662            let max_swing_steps = base_ct.num_corners().saturating_add(1);
663            while c != INVALID_CORNER_INDEX && c != first_corner {
664                swing_steps += 1;
665                if swing_steps > max_swing_steps {
666                    return Err(DracoError::DracoError(
667                        "Edgebreaker point assignment traversal did not terminate".to_string(),
668                    ));
669                }
670                let attribute_seam = self
671                    .edgebreaker_attribute_corner_tables
672                    .iter()
673                    .any(|attr_ct| attr_ct.vertex(c) != attr_ct.vertex(prev_c));
674                if attribute_seam {
675                    corner_to_point_map[c.0 as usize] = point_to_corner_map.len() as u32;
676                    point_to_corner_map.push(c.0);
677                } else {
678                    corner_to_point_map[c.0 as usize] = corner_to_point_map[prev_c.0 as usize];
679                }
680                prev_c = c;
681                c = base_ct.swing_right(c);
682            }
683        }
684
685        for face_id in 0..mesh.num_faces() {
686            let base = face_id * 3;
687            let p0 = corner_to_point_map[base];
688            let p1 = corner_to_point_map[base + 1];
689            let p2 = corner_to_point_map[base + 2];
690            if p0 == u32::MAX || p1 == u32::MAX || p2 == u32::MAX {
691                return Err(DracoError::DracoError(
692                    "Failed to assign Edgebreaker corner point".to_string(),
693                ));
694            }
695            mesh.set_face(
696                FaceIndex(face_id as u32),
697                [PointIndex(p0), PointIndex(p1), PointIndex(p2)],
698            );
699        }
700        mesh.set_num_points(point_to_corner_map.len());
701
702        Ok(())
703    }
704
705    fn decode_attributes(&mut self, buffer: &mut DecoderBuffer, mesh: &mut Mesh) -> Status {
706        // Both MeshSequentialEncoding and MeshEdgebreakerEncoding use a u8 for the number of
707        // attribute decoders.
708        let num_attributes_decoders = buffer.decode_u8()? as usize;
709        let num_points = mesh.num_points();
710
711        // For Edgebreaker, traversal sequencing is controlled per attribute decoder.
712        // We'll derive the correct (point_ids, data_to_corner_map) later for each decoder payload
713        // based on its traversal_method.
714        let point_ids = if self.method == 0 {
715            make_point_ids(num_points)?
716        } else {
717            Vec::new()
718        };
719        let data_to_corner_map: Option<Vec<u32>> = None;
720
721        let pc_decoder = PointCloudDecoder::new();
722        let bitstream_version: u16 =
723            crate::version::bitstream_version(self.version_major, self.version_minor);
724
725        struct PendingQuant {
726            att_id: i32,
727            portable: PointAttribute,
728            transform: AttributeQuantizationTransform,
729        }
730
731        struct PendingNormal {
732            att_id: i32,
733            portable: PointAttribute,
734            quantization_bits: u8,
735        }
736
737        // (1) Attribute decoder identifiers.
738        // For Edgebreaker this ties each decoder payload to attribute connectivity data.
739        let mut att_data_id_by_decoder: Vec<u8> = vec![0; num_attributes_decoders];
740        let mut encoder_type_by_decoder: Vec<u8> = vec![0; num_attributes_decoders];
741        let mut traversal_method_by_decoder: Vec<u8> = vec![0; num_attributes_decoders];
742        if self.method == 1 {
743            for i in 0..num_attributes_decoders {
744                att_data_id_by_decoder[i] = buffer.decode_u8()?;
745                encoder_type_by_decoder[i] = buffer.decode_u8()?;
746                // traversal_method was added in v1.2. For older streams, default to
747                // DEPTH_FIRST (0).
748                if bitstream_version >= 0x0102 {
749                    traversal_method_by_decoder[i] = buffer.decode_u8()?;
750                } else if !cfg!(feature = "legacy_bitstream_decode") {
751                    return Err(DracoError::BitstreamVersionUnsupported);
752                }
753            }
754        }
755
756        // (2) Attribute decoder data.
757        let mut att_ids_by_decoder: Vec<Vec<i32>> = Vec::with_capacity(num_attributes_decoders);
758        let mut decoder_types_by_decoder: Vec<Vec<u8>> =
759            Vec::with_capacity(num_attributes_decoders);
760
761        for _ in 0..num_attributes_decoders {
762            let num_attributes_in_decoder: usize = if bitstream_version < 0x0200 {
763                if !cfg!(feature = "legacy_bitstream_decode") {
764                    return Err(DracoError::BitstreamVersionUnsupported);
765                }
766                buffer.decode_u32()? as usize
767            } else {
768                buffer.decode_varint()? as usize
769            };
770            if num_attributes_in_decoder == 0 {
771                return Err(DracoError::DracoError(
772                    "Invalid number of attributes".to_string(),
773                ));
774            }
775            validate_num_attributes_in_decoder(num_attributes_in_decoder, buffer.remaining_size())?;
776
777            let mut att_ids: Vec<i32> = Vec::with_capacity(num_attributes_in_decoder);
778            let mut decoder_types: Vec<u8> = Vec::with_capacity(num_attributes_in_decoder);
779
780            for _ in 0..num_attributes_in_decoder {
781                let att_type_val = buffer.decode_u8()?;
782                let att_type = GeometryAttributeType::try_from(att_type_val)?;
783
784                let data_type_val = buffer.decode_u8()?;
785                let data_type = DataType::try_from(data_type_val)?;
786
787                let num_components = buffer.decode_u8()?;
788                validate_num_components(num_components)?;
789                let normalized = buffer.decode_u8()? != 0;
790                let unique_id: u32 = if bitstream_version < 0x0103 {
791                    if !cfg!(feature = "legacy_bitstream_decode") {
792                        return Err(DracoError::BitstreamVersionUnsupported);
793                    }
794                    buffer.decode_u16()? as u32
795                } else {
796                    buffer.decode_varint()? as u32
797                };
798
799                let mut att = PointAttribute::new();
800                att.try_init(att_type, num_components, data_type, normalized, num_points)?;
801                att.set_unique_id(unique_id);
802                let att_id = mesh.add_attribute_preserve_unique_id(att);
803                att_ids.push(att_id);
804
805                if self.method == 1 {
806                    let att_mut = mesh.try_attribute_mut(att_id)?;
807                    att_mut.set_explicit_mapping(num_points);
808                    for i in 0..num_points {
809                        att_mut.try_set_point_map_entry(
810                            PointIndex(i as u32),
811                            AttributeValueIndex(i as u32),
812                        )?;
813                    }
814                }
815            }
816
817            for _ in 0..num_attributes_in_decoder {
818                decoder_types.push(buffer.decode_u8()?);
819            }
820
821            att_ids_by_decoder.push(att_ids);
822            decoder_types_by_decoder.push(decoder_types);
823        }
824
825        // (3) Attribute decoder payloads.
826        let mut portable_attributes_by_id: Vec<(i32, PointAttribute)> = Vec::new();
827        for dec_i in 0..num_attributes_decoders {
828            let att_ids = &att_ids_by_decoder[dec_i];
829            let decoder_types = &decoder_types_by_decoder[dec_i];
830
831            // For edgebreaker, build an attribute-specific corner table (seams) if needed.
832            // Corner indices remain stable because we only break opposite links.
833            let mut attr_corner_table: Option<CornerTable> = None;
834            if self.method == 1 {
835                let att_data_id = att_data_id_by_decoder[dec_i] as usize;
836                let uses_attribute_connectivity =
837                    att_data_id_by_decoder[dec_i] != u8::MAX && encoder_type_by_decoder[dec_i] != 0;
838                if uses_attribute_connectivity
839                    && att_data_id < self.edgebreaker_attribute_seam_corners.len()
840                {
841                    if let Some(ct) = self.edgebreaker_attribute_corner_tables.get(att_data_id) {
842                        attr_corner_table = Some(ct.clone());
843                    }
844                }
845            }
846
847            // Determine the corner table used for prediction within this decoder.
848            // For edgebreaker, seams may split vertex fans and change the effective
849            // traversal sequence used by predictors.
850            let mut point_ids_for_decoder: Option<Vec<PointIndex>> = None;
851            let mut data_to_corner_map_for_decoder: Option<Vec<u32>> = None;
852            let mut vertex_to_data_map_for_decoder: Option<Vec<i32>> = None;
853            if self.method == 1 {
854                // If we have an attribute-specific seam corner table, recompute vertex
855                // corners after breaking opposites so we can derive the correct number
856                // of entries for this decoder.
857                if let Some(ref ct) = attr_corner_table {
858                    let (ids, map, v_map) =
859                        Self::generate_point_ids_and_corners_dfs_for_table(mesh, ct, &[])?;
860                    point_ids_for_decoder = Some(ids);
861                    data_to_corner_map_for_decoder = Some(map);
862                    vertex_to_data_map_for_decoder = Some(v_map);
863                }
864
865                // Note: For edgebreaker, we intentionally do NOT take a traversal
866                // mapping from `MeshEdgebreakerDecoder::assign_points_to_corners()`.
867                // The C++ decoder derives its attribute traversal from
868                // `MeshTraversalSequencer` (with no corner_order set), i.e. from
869                // deterministic traversal over the reconstructed corner table.
870                // Mixing a connectivity-derived map with a separately generated
871                // vertex_to_data_map can desynchronize prediction decoding.
872            }
873
874            let corner_table_for_decoder: Option<&CornerTable> =
875                if let Some(ref ct) = attr_corner_table {
876                    Some(ct)
877                } else {
878                    self.corner_table.as_deref()
879                };
880
881            // Optional vertex_to_data_map derived from the chosen data_to_corner_map.
882            // (Needed by mesh prediction schemes to map corner-table vertices -> data ids.)
883            // For edgebreaker, derive per-decoder traversal sequencing when seams are not
884            // applied (per-vertex attributes). This sequencing must match the bitstream
885            // traversal_method to keep prediction-scheme side streams (e.g. crease flags)
886            // synchronized.
887            let mut sequenced_point_ids: Option<Vec<PointIndex>> = None;
888            let mut sequenced_data_to_corner_map: Option<Vec<u32>> = None;
889            let mut sequenced_vertex_to_data_map: Option<Vec<i32>> = None;
890
891            // Generate point_ids using traversal method.
892            // For Edgebreaker, the decoder should match the encoder's traversal method.
893            // The per-decoder traversal method is stored in traversal_method_by_decoder.
894            // - traversal_method == 1 (PREDICTION_DEGREE): uses MaxPredictionDegree traversal
895            // - traversal_method == 0 (DEPTH_FIRST): uses DFS traversal
896            // Note: self.traversal_method is the edgebreaker decoder type (0=Standard, 1=Predictive, 2=Valence),
897            // which is different from the per-decoder traversal method.
898            if sequenced_point_ids.is_none() {
899                // Get the per-decoder traversal method (Speed 0 uses PREDICTION_DEGREE=1, others use DEPTH_FIRST=0)
900                let per_decoder_traversal =
901                    if self.method == 1 && dec_i < traversal_method_by_decoder.len() {
902                        traversal_method_by_decoder[dec_i]
903                    } else {
904                        0
905                    };
906                // For sequential encoding (method 0), use identity permutation
907                // because the encoder writes positions in point ID order [0, 1, 2, ...].
908                // For edgebreaker (method 1), use DFS/prediction traversal to match encoder.
909                if self.method == 0 {
910                    // Sequential encoding: C++ uses LinearSequencer which generates
911                    // identity mapping [0, 1, 2, ..., num_points-1] and calls
912                    // SetIdentityMapping() for attributes. No corner table or
913                    // data_to_corner_map is needed.
914                    // Use the mesh-wide identity sequence allocated above instead
915                    // of rebuilding an identical vector for each decoder.
916                    // sequenced_data_to_corner_map remains None - not needed for sequential
917                } else {
918                    // Edgebreaker decoding: traversal method depends on the per-decoder
919                    // traversal method written by the encoder.
920                    // - per_decoder_traversal == 1 (PREDICTION_DEGREE): MaxPredictionDegree traversal (speed 0)
921                    // - per_decoder_traversal == 0 (DEPTH_FIRST): DFS traversal (speed >= 1)
922
923                    if per_decoder_traversal == 1 {
924                        // Speed 0: use MaxPredictionDegree traversal
925                        let (ids, map, v_map) = self
926                            .generate_point_ids_and_corners_max_prediction_degree(
927                                mesh,
928                                &self.edgebreaker_processed_connectivity_corners,
929                            )?;
930                        sequenced_point_ids = Some(ids);
931                        sequenced_data_to_corner_map = Some(map);
932                        sequenced_vertex_to_data_map = Some(v_map); // Use directly from traversal
933                    } else {
934                        // Speed >= 1: use DFS with sequential faces. The traversal helper
935                        // already uses CornerIndex(3 * face_id) when no explicit seeds are
936                        // provided, so avoid allocating a temporary seed vector here.
937                        let (ids, map, v_map) =
938                            self.generate_point_ids_and_corners_dfs(mesh, &[])?;
939                        sequenced_point_ids = Some(ids);
940                        sequenced_data_to_corner_map = Some(map);
941                        sequenced_vertex_to_data_map = Some(v_map); // Use directly from DFS traversal
942                    }
943                }
944            }
945
946            // Generate vertex_to_data_map from the traversal result (only if not already set).
947            // This is needed by predictors (like Parallelogram) to find references by point index.
948            // Only needed for Edgebreaker (method 1) since sequential encoding uses only
949            // Difference prediction which doesn't need mesh connectivity.
950            if self.method == 1 && sequenced_vertex_to_data_map.is_none() {
951                if let Some(ref map) = sequenced_data_to_corner_map {
952                    let ct = self.corner_table.as_ref().ok_or_else(|| {
953                        DracoError::DracoError(
954                            "Edgebreaker attribute traversal missing corner table".to_string(),
955                        )
956                    })?;
957                    sequenced_vertex_to_data_map =
958                        Some(build_vertex_to_data_map_from_corner_map(ct, map)?);
959                }
960            }
961
962            // Choose which point sequence to use for decoding values in this decoder.
963            // If seams were applied, we derived a per-decoder point id list (possibly
964            // containing repeats). Otherwise, fall back to the mesh-wide sequence.
965            let point_ids_for_values: &[PointIndex] = if let Some(ref ids) = point_ids_for_decoder {
966                ids
967            } else if let Some(ref ids) = sequenced_point_ids {
968                ids
969            } else {
970                &point_ids
971            };
972            let data_to_corner_map_override_for_values: Option<&[u32]> =
973                if let Some(ref map) = data_to_corner_map_for_decoder {
974                    Some(map.as_slice())
975                } else if let Some(ref map) = sequenced_data_to_corner_map {
976                    Some(map.as_slice())
977                } else {
978                    data_to_corner_map.as_deref()
979                };
980            let vertex_to_data_map_override_for_values: Option<&[i32]> =
981                if point_ids_for_decoder.is_some() {
982                    vertex_to_data_map_for_decoder.as_deref()
983                } else {
984                    sequenced_vertex_to_data_map.as_deref()
985                };
986
987            let mut pending_quant: Vec<PendingQuant> = Vec::new();
988            let mut pending_normals: Vec<PendingNormal> = Vec::new();
989
990            for (local_i, &att_id) in att_ids.iter().enumerate() {
991                let decoder_type = decoder_types[local_i];
992                {
993                    let att = mesh.try_attribute_mut(att_id)?;
994                    if att.size() != point_ids_for_values.len() {
995                        att.resize_unique_entries(point_ids_for_values.len())?;
996                    }
997                }
998                match decoder_type {
999                    0 => {
1000                        let mut att_decoder = SequentialGenericAttributeDecoder::new();
1001                        att_decoder.init(&pc_decoder, att_id);
1002                        att_decoder.decode_values(mesh, point_ids_for_values, buffer)?;
1003                    }
1004                    1 => {
1005                        let mut att_decoder = SequentialIntegerAttributeDecoder::new();
1006                        att_decoder.init(&pc_decoder, att_id);
1007                        let portable_parent_attribute = if bitstream_version >= 0x0200 {
1008                            let pos_att_id =
1009                                mesh.named_attribute_id(GeometryAttributeType::Position);
1010                            portable_attributes_by_id
1011                                .iter()
1012                                .find(|(id, _)| *id == pos_att_id)
1013                                .map(|(_, att)| att)
1014                        } else {
1015                            None
1016                        };
1017                        if !att_decoder.decode_values(
1018                            mesh,
1019                            point_ids_for_values,
1020                            buffer,
1021                            corner_table_for_decoder,
1022                            data_to_corner_map_override_for_values,
1023                            vertex_to_data_map_override_for_values,
1024                            None,
1025                            portable_parent_attribute,
1026                            None,
1027                        ) {
1028                            return Err(DracoError::DracoError(
1029                                "Failed to decode integer attribute values".to_string(),
1030                            ));
1031                        }
1032                    }
1033                    2 => {
1034                        let mut portable = PointAttribute::default();
1035                        let (original_type, original_num_components) = {
1036                            let original = mesh.try_attribute(att_id)?;
1037                            (original.attribute_type(), original.num_components())
1038                        };
1039                        portable.try_init(
1040                            original_type,
1041                            original_num_components,
1042                            DataType::Uint32,
1043                            false,
1044                            point_ids_for_values.len(),
1045                        )?;
1046                        #[allow(unused_mut)]
1047                        let mut transform = AttributeQuantizationTransform::new();
1048                        // Legacy compatibility shim: C++ bitstreams with version < 2.0 store
1049                        // quantization params before the integer values, while v2.0+ stores
1050                        // them after the values. Rust-generated files never use the legacy
1051                        // layout, so this peek-ahead only exists to decode genuine old C++ files.
1052                        let quant_skip_bytes = if bitstream_version < 0x0200 {
1053                            #[cfg(not(feature = "legacy_bitstream_decode"))]
1054                            {
1055                                return Err(DracoError::BitstreamVersionUnsupported);
1056                            }
1057                            #[cfg(feature = "legacy_bitstream_decode")]
1058                            {
1059                                let saved_pos = buffer.position();
1060                                let method_byte = buffer.decode_u8().map_err(|_| {
1061                                    DracoError::DracoError(
1062                                        "Failed to read prediction method".to_string(),
1063                                    )
1064                                })?;
1065                                if method_byte != 0xFF {
1066                                    let _transform_byte = buffer.decode_u8().map_err(|_| {
1067                                        DracoError::DracoError(
1068                                            "Failed to read transform type".to_string(),
1069                                        )
1070                                    })?;
1071                                }
1072                                let original = mesh.try_attribute(att_id)?;
1073                                if !transform.decode_parameters(original, buffer) {
1074                                    return Err(DracoError::DracoError(
1075                                        "Failed to decode quantization parameters (v<2.0)"
1076                                            .to_string(),
1077                                    ));
1078                                }
1079                                let bytes_consumed = buffer.position() - saved_pos;
1080                                let pred_header_bytes = if method_byte != 0xFF { 2 } else { 1 };
1081                                let skip = bytes_consumed - pred_header_bytes;
1082                                buffer.set_position(saved_pos).map_err(|_| {
1083                                    DracoError::DracoError(
1084                                        "Failed to reset buffer position".to_string(),
1085                                    )
1086                                })?;
1087                                skip
1088                            }
1089                        } else {
1090                            0
1091                        };
1092                        let mut att_decoder = SequentialIntegerAttributeDecoder::new();
1093                        att_decoder.init(&pc_decoder, att_id);
1094                        let mut skip_hook_fn = move |buf: &mut DecoderBuffer<'_>| -> bool {
1095                            if quant_skip_bytes == 0 {
1096                                return true;
1097                            }
1098                            buf.try_advance(quant_skip_bytes).is_ok()
1099                        };
1100                        let pre_hook_opt: Option<&mut dyn FnMut(&mut DecoderBuffer<'_>) -> bool> =
1101                            if quant_skip_bytes > 0 {
1102                                Some(&mut skip_hook_fn)
1103                            } else {
1104                                None
1105                            };
1106                        let portable_parent_attribute = if bitstream_version >= 0x0200 {
1107                            let pos_att_id =
1108                                mesh.named_attribute_id(GeometryAttributeType::Position);
1109                            portable_attributes_by_id
1110                                .iter()
1111                                .find(|(id, _)| *id == pos_att_id)
1112                                .map(|(_, att)| att)
1113                        } else {
1114                            None
1115                        };
1116                        if !att_decoder.decode_values(
1117                            mesh,
1118                            point_ids_for_values,
1119                            buffer,
1120                            corner_table_for_decoder,
1121                            data_to_corner_map_override_for_values,
1122                            vertex_to_data_map_override_for_values,
1123                            Some(&mut portable),
1124                            portable_parent_attribute,
1125                            pre_hook_opt,
1126                        ) {
1127                            return Err(DracoError::DracoError(
1128                                "Failed to decode quantized portable values".to_string(),
1129                            ));
1130                        }
1131                        pending_quant.push(PendingQuant {
1132                            att_id,
1133                            portable,
1134                            transform,
1135                        });
1136                    }
1137                    3 => {
1138                        let mut portable = PointAttribute::default();
1139                        portable.try_init(
1140                            GeometryAttributeType::Generic,
1141                            2,
1142                            DataType::Uint32,
1143                            false,
1144                            point_ids_for_values.len(),
1145                        )?;
1146                        // Legacy compatibility shim: C++ bitstreams with version < 2.0 store
1147                        // normal octahedron quantization bits after the prediction header but
1148                        // before integer values. Rust-generated files never use this layout.
1149                        #[allow(unused_mut)]
1150                        let mut quant_bits: u8 = 0;
1151                        let normal_skip_bytes = if bitstream_version < 0x0200 {
1152                            #[cfg(not(feature = "legacy_bitstream_decode"))]
1153                            {
1154                                return Err(DracoError::BitstreamVersionUnsupported);
1155                            }
1156                            #[cfg(feature = "legacy_bitstream_decode")]
1157                            {
1158                                let saved_pos = buffer.position();
1159                                // Skip prediction_method + transform_type
1160                                let method_byte = buffer.decode_u8().map_err(|_| {
1161                                    DracoError::DracoError(
1162                                        "Failed to read prediction method".to_string(),
1163                                    )
1164                                })?;
1165                                if method_byte != 0xFF {
1166                                    let _transform_byte = buffer.decode_u8().map_err(|_| {
1167                                        DracoError::DracoError(
1168                                            "Failed to read transform type".to_string(),
1169                                        )
1170                                    })?;
1171                                }
1172                                // Read quant_bits at the correct position
1173                                quant_bits = buffer.decode_u8().map_err(|_| {
1174                                    DracoError::DracoError(
1175                                        "Failed to read normal quant_bits".to_string(),
1176                                    )
1177                                })?;
1178                                if !AttributeOctahedronTransform::is_valid_quantization_bits(
1179                                    quant_bits as i32,
1180                                ) {
1181                                    return Err(DracoError::DracoError(
1182                                        "Invalid normal quantization bits".to_string(),
1183                                    ));
1184                                }
1185                                let bytes_consumed = buffer.position() - saved_pos;
1186                                let pred_header_bytes = if method_byte != 0xFF { 2 } else { 1 };
1187                                let skip = bytes_consumed - pred_header_bytes;
1188                                buffer.set_position(saved_pos).map_err(|_| {
1189                                    DracoError::DracoError(
1190                                        "Failed to reset buffer position".to_string(),
1191                                    )
1192                                })?;
1193                                skip
1194                            }
1195                        } else {
1196                            0
1197                        };
1198                        let mut att_decoder = SequentialIntegerAttributeDecoder::new();
1199                        att_decoder.init(&pc_decoder, att_id);
1200                        let mut normal_skip_fn = move |buf: &mut DecoderBuffer<'_>| -> bool {
1201                            if normal_skip_bytes == 0 {
1202                                return true;
1203                            }
1204                            buf.try_advance(normal_skip_bytes).is_ok()
1205                        };
1206                        let normal_hook: Option<&mut dyn FnMut(&mut DecoderBuffer<'_>) -> bool> =
1207                            if normal_skip_bytes > 0 {
1208                                Some(&mut normal_skip_fn)
1209                            } else {
1210                                None
1211                            };
1212                        let portable_parent_attribute = if bitstream_version >= 0x0200 {
1213                            let pos_att_id =
1214                                mesh.named_attribute_id(GeometryAttributeType::Position);
1215                            portable_attributes_by_id
1216                                .iter()
1217                                .find(|(id, _)| *id == pos_att_id)
1218                                .map(|(_, att)| att)
1219                        } else {
1220                            None
1221                        };
1222                        if !att_decoder.decode_values(
1223                            mesh,
1224                            point_ids_for_values,
1225                            buffer,
1226                            corner_table_for_decoder,
1227                            data_to_corner_map_override_for_values,
1228                            vertex_to_data_map_override_for_values,
1229                            Some(&mut portable),
1230                            portable_parent_attribute,
1231                            normal_hook,
1232                        ) {
1233                            return Err(DracoError::DracoError(
1234                                "Failed to decode normal portable values".to_string(),
1235                            ));
1236                        }
1237                        pending_normals.push(PendingNormal {
1238                            att_id,
1239                            portable,
1240                            quantization_bits: quant_bits,
1241                        });
1242                    }
1243                    _ => {
1244                        return Err(DracoError::DracoError(format!(
1245                            "Unsupported sequential decoder type: {}",
1246                            decoder_type
1247                        )));
1248                    }
1249                }
1250            }
1251
1252            // Decode transform data for all attributes.
1253            // For C++ files with bitstream version < 2.0, quantization params were already
1254            // decoded before integer values (legacy peek-ahead above). For v >= 2.0
1255            // (including all Rust-generated files), they are decoded here after all values.
1256            for (local_i, &att_id) in att_ids.iter().enumerate() {
1257                match decoder_types[local_i] {
1258                    2 if bitstream_version >= 0x0200 => {
1259                        let idx = pending_quant
1260                            .iter()
1261                            .position(|p| p.att_id == att_id)
1262                            .ok_or_else(|| {
1263                                DracoError::DracoError("Missing pending quant entry".to_string())
1264                            })?;
1265                        let original = mesh.try_attribute(att_id)?;
1266                        if !pending_quant[idx]
1267                            .transform
1268                            .decode_parameters(original, buffer)
1269                        {
1270                            return Err(DracoError::DracoError(
1271                                "Failed to decode quantization parameters".to_string(),
1272                            ));
1273                        }
1274                    }
1275                    3 if bitstream_version >= 0x0200 => {
1276                        let idx = pending_normals
1277                            .iter()
1278                            .position(|p| p.att_id == att_id)
1279                            .ok_or_else(|| {
1280                                DracoError::DracoError("Missing pending normal entry".to_string())
1281                            })?;
1282                        let bits = buffer.decode_u8()?;
1283                        if !AttributeOctahedronTransform::is_valid_quantization_bits(bits as i32) {
1284                            return Err(DracoError::DracoError(
1285                                "Invalid normal quantization bits".to_string(),
1286                            ));
1287                        }
1288                        pending_normals[idx].quantization_bits = bits;
1289                    }
1290                    _ => {}
1291                }
1292            }
1293
1294            // Apply inverse transforms.
1295            for q in &pending_quant {
1296                let dst = mesh.try_attribute_mut(q.att_id)?;
1297                if dst.size() != q.portable.size() {
1298                    dst.resize_unique_entries(q.portable.size())?;
1299                }
1300                if !q.transform.inverse_transform_attribute(&q.portable, dst) {
1301                    return Err(DracoError::DracoError(
1302                        "Failed to dequantize attribute".to_string(),
1303                    ));
1304                }
1305            }
1306            for n in &pending_normals {
1307                let mut oct = AttributeOctahedronTransform::new(-1);
1308                if !oct.set_parameters(n.quantization_bits as i32) {
1309                    return Err(DracoError::DracoError(
1310                        "Invalid normal quantization bits".to_string(),
1311                    ));
1312                }
1313                let dst = mesh.try_attribute_mut(n.att_id)?;
1314                if dst.size() != n.portable.size() {
1315                    dst.resize_unique_entries(n.portable.size())?;
1316                }
1317                if !oct.inverse_transform_attribute_with_legacy_octahedron(
1318                    &n.portable,
1319                    dst,
1320                    bitstream_version < 0x0200,
1321                ) {
1322                    return Err(DracoError::DracoError(
1323                        "Failed to decode normals".to_string(),
1324                    ));
1325                }
1326            }
1327
1328            // Apply UpdatePointToAttributeIndexMapping for Edgebreaker (method 1)
1329            // This creates the final mapping from mesh points to attribute values,
1330            // matching C++ MeshTraversalSequencer::UpdatePointToAttributeIndexMapping.
1331            //
1332            // The key insight: values are stored in data_id order (determined by DFS).
1333            // vertex_to_data_map[v] tells us which data_id holds vertex v's value.
1334            // In the decoder, mesh point == corner table vertex (since faces are built from CT).
1335            // So point p should get value from data_id = vertex_to_data_map[p].
1336            if self.method == 1 {
1337                let mapping_v_map = vertex_to_data_map_for_decoder
1338                    .as_deref()
1339                    .or(sequenced_vertex_to_data_map.as_deref());
1340                if let Some(v_map) = mapping_v_map {
1341                    let num_points = mesh.num_points();
1342                    let mut point_to_value: Vec<Option<AttributeValueIndex>> =
1343                        vec![None; num_points];
1344                    if let Some(ct) = corner_table_for_decoder {
1345                        for face_id in 0..mesh.num_faces() {
1346                            let face = mesh.face(FaceIndex(face_id as u32));
1347                            for corner_offset in 0..3 {
1348                                let corner = CornerIndex((face_id * 3 + corner_offset) as u32);
1349                                let vertex = ct.vertex(corner);
1350                                let point = face[corner_offset].0 as usize;
1351                                if point < point_to_value.len()
1352                                    && vertex != INVALID_VERTEX_INDEX
1353                                    && (vertex.0 as usize) < v_map.len()
1354                                    && v_map[vertex.0 as usize] >= 0
1355                                {
1356                                    point_to_value[point] =
1357                                        Some(AttributeValueIndex(v_map[vertex.0 as usize] as u32));
1358                                }
1359                            }
1360                        }
1361                    } else {
1362                        for p in 0..num_points {
1363                            if p < v_map.len() && v_map[p] >= 0 {
1364                                point_to_value[p] = Some(AttributeValueIndex(v_map[p] as u32));
1365                            }
1366                        }
1367                    }
1368
1369                    for &att_id in att_ids {
1370                        let att = mesh.try_attribute_mut(att_id)?;
1371                        att.set_explicit_mapping(num_points);
1372                        for (point, value) in point_to_value.iter().enumerate() {
1373                            if let Some(value) = value {
1374                                att.try_set_point_map_entry(PointIndex(point as u32), *value)?;
1375                            }
1376                        }
1377                    }
1378                }
1379            }
1380
1381            for q in pending_quant {
1382                let mut portable = q.portable;
1383                copy_point_mapping(
1384                    mesh.try_attribute(q.att_id)?,
1385                    &mut portable,
1386                    mesh.num_points(),
1387                )?;
1388                upsert_portable_attribute(&mut portable_attributes_by_id, q.att_id, portable);
1389            }
1390            for n in pending_normals {
1391                let mut portable = n.portable;
1392                copy_point_mapping(
1393                    mesh.try_attribute(n.att_id)?,
1394                    &mut portable,
1395                    mesh.num_points(),
1396                )?;
1397                upsert_portable_attribute(&mut portable_attributes_by_id, n.att_id, portable);
1398            }
1399        }
1400
1401        Ok(())
1402    }
1403
1404    /// Discovery-order traversal: use the order points were created during reconstruction.
1405    #[allow(dead_code)]
1406    fn generate_point_ids_and_corners_discovery(&self, mesh: &Mesh) -> (Vec<PointIndex>, Vec<u32>) {
1407        let num_points = mesh.num_points();
1408        let mut point_ids = Vec::with_capacity(num_points);
1409        let mut data_to_corner_map = Vec::with_capacity(num_points);
1410
1411        for i in 0..num_points {
1412            let pid = PointIndex(i as u32);
1413            point_ids.push(pid);
1414            let corner = self
1415                .edgebreaker_vertex_to_corner_map
1416                .get(i)
1417                .cloned()
1418                .unwrap_or(u32::MAX);
1419            data_to_corner_map.push(if corner == u32::MAX { 0 } else { corner });
1420        }
1421
1422        (point_ids, data_to_corner_map)
1423    }
1424
1425    #[allow(dead_code)]
1426    fn generate_point_ids_and_corners_dfs(
1427        &self,
1428        mesh: &Mesh,
1429        processed_connectivity_corners: &[u32],
1430    ) -> Result<AttributeTraversalArrays, DracoError> {
1431        let corner_table = self.corner_table.as_ref().ok_or_else(|| {
1432            DracoError::DracoError(
1433                "Edgebreaker DFS attribute traversal missing corner table".to_string(),
1434            )
1435        })?;
1436        Self::generate_point_ids_and_corners_dfs_for_table(
1437            mesh,
1438            corner_table,
1439            processed_connectivity_corners,
1440        )
1441    }
1442
1443    fn generate_point_ids_and_corners_dfs_for_table(
1444        mesh: &Mesh,
1445        corner_table: &CornerTable,
1446        processed_connectivity_corners: &[u32],
1447    ) -> Result<AttributeTraversalArrays, DracoError> {
1448        // Reject an inconsistent (e.g. seam-modified) corner table before the DFS
1449        // indexes per-vertex / per-face arrays by table-derived ids.
1450        if !corner_table.is_index_consistent() {
1451            return Err(DracoError::DracoError(
1452                "Inconsistent corner table for attribute traversal".to_string(),
1453            ));
1454        }
1455        let num_vertices = corner_table.num_vertices();
1456        let num_faces = corner_table.num_faces();
1457
1458        let mut point_ids = Vec::with_capacity(num_vertices);
1459        let mut data_to_corner_map = Vec::with_capacity(num_vertices);
1460        let mut vertex_to_data_map = vec![-1i32; num_vertices];
1461        let mut visited_vertices = vec![false; num_vertices];
1462        let mut visited_faces = vec![false; num_faces];
1463        let event_log_enabled = test_event_log::enabled();
1464
1465        // Helper to get mesh PointIndex from corner (matches C++ Mesh::CornerToPointId)
1466        let corner_to_point_id = |c: CornerIndex| -> PointIndex {
1467            if c == INVALID_CORNER_INDEX {
1468                return PointIndex(u32::MAX);
1469            }
1470            let face_id = FaceIndex(c.0 / 3);
1471            let corner_offset = (c.0 % 3) as usize;
1472            mesh.face(face_id)[corner_offset]
1473        };
1474
1475        // Visit a corner table vertex and record it as a point ID.
1476        // This matches C++ MeshAttributeIndicesEncodingObserver::OnNewVertexVisited
1477        // which gets point_id from mesh_->face(corner / 3)[corner % 3]
1478
1479        // DFS traversal matching C++ DepthFirstTraverser::TraverseFromCorner exactly
1480        let mut traverse_from_corner =
1481            |start_corner: CornerIndex,
1482             point_ids: &mut Vec<PointIndex>,
1483             vertex_to_data_map: &mut Vec<i32>,
1484             visited_vertices: &mut Vec<bool>,
1485             visited_faces: &mut Vec<bool>| {
1486                let start_face = corner_table.face(start_corner);
1487                if start_face == crate::geometry_indices::INVALID_FACE_INDEX {
1488                    return;
1489                }
1490                if visited_faces[start_face.0 as usize] {
1491                    return; // Already traversed
1492                }
1493
1494                let mut corner_stack: Vec<CornerIndex> = Vec::new();
1495                corner_stack.push(start_corner);
1496
1497                // For the first face, check the remaining corners as they may not be processed yet.
1498                // C++ visits Next, then Previous vertices BEFORE the main loop.
1499                let next_vert = corner_table.vertex(corner_table.next(start_corner));
1500                let prev_vert = corner_table.vertex(corner_table.previous(start_corner));
1501
1502                if next_vert == crate::geometry_indices::INVALID_VERTEX_INDEX
1503                    || prev_vert == crate::geometry_indices::INVALID_VERTEX_INDEX
1504                {
1505                    return;
1506                }
1507
1508                // Visit Next vertex
1509                if !visited_vertices[next_vert.0 as usize] {
1510                    visited_vertices[next_vert.0 as usize] = true;
1511                    let next_corner = corner_table.next(start_corner);
1512                    let point_id = corner_to_point_id(next_corner);
1513                    let data_id = point_ids.len() as i32;
1514                    vertex_to_data_map[next_vert.0 as usize] = data_id;
1515                    if event_log_enabled {
1516                        test_event_log::record_event(format!(
1517                            "MAP:{}->v{}",
1518                            next_corner.0, next_vert.0
1519                        ));
1520                        test_event_log::record_event(format!(
1521                            "MAP_POINT:{}->p{}",
1522                            next_corner.0, point_id.0
1523                        ));
1524                    }
1525                    point_ids.push(point_id);
1526                    data_to_corner_map.push(next_corner.0);
1527                }
1528                // Visit Previous vertex
1529                if !visited_vertices[prev_vert.0 as usize] {
1530                    visited_vertices[prev_vert.0 as usize] = true;
1531                    let prev_corner = corner_table.previous(start_corner);
1532                    let point_id = corner_to_point_id(prev_corner);
1533                    let data_id = point_ids.len() as i32;
1534                    vertex_to_data_map[prev_vert.0 as usize] = data_id;
1535                    if event_log_enabled {
1536                        test_event_log::record_event(format!(
1537                            "MAP:{}->v{}",
1538                            prev_corner.0, prev_vert.0
1539                        ));
1540                        test_event_log::record_event(format!(
1541                            "MAP_POINT:{}->p{}",
1542                            prev_corner.0, point_id.0
1543                        ));
1544                    }
1545                    point_ids.push(point_id);
1546                    data_to_corner_map.push(prev_corner.0);
1547                }
1548
1549                // Start the actual traversal (matching C++ while loop)
1550                while let Some(mut corner_id) = corner_stack.pop() {
1551                    let mut face_id = corner_table.face(corner_id);
1552
1553                    // Make sure the face hasn't been visited yet
1554                    if corner_id == INVALID_CORNER_INDEX || visited_faces[face_id.0 as usize] {
1555                        continue; // This face has been already traversed
1556                    }
1557
1558                    loop {
1559                        visited_faces[face_id.0 as usize] = true;
1560
1561                        let vert_id = corner_table.vertex(corner_id);
1562                        if vert_id == crate::geometry_indices::INVALID_VERTEX_INDEX {
1563                            break;
1564                        }
1565
1566                        if !visited_vertices[vert_id.0 as usize] {
1567                            let on_boundary =
1568                                Self::is_vertex_on_boundary_impl(corner_table, vert_id);
1569                            visited_vertices[vert_id.0 as usize] = true;
1570                            let point_id = corner_to_point_id(corner_id);
1571                            let data_id = point_ids.len() as i32;
1572                            vertex_to_data_map[vert_id.0 as usize] = data_id;
1573                            if event_log_enabled {
1574                                test_event_log::record_event(format!(
1575                                    "MAP:{}->v{}",
1576                                    corner_id.0, vert_id.0
1577                                ));
1578                                test_event_log::record_event(format!(
1579                                    "MAP_POINT:{}->p{}",
1580                                    corner_id.0, point_id.0
1581                                ));
1582                            }
1583                            point_ids.push(point_id);
1584                            data_to_corner_map.push(corner_id.0);
1585
1586                            if !on_boundary {
1587                                // Continue to right corner (GetRightCorner = Opposite(Next))
1588                                corner_id = corner_table.opposite(corner_table.next(corner_id));
1589                                if corner_id == INVALID_CORNER_INDEX {
1590                                    break;
1591                                }
1592                                face_id = corner_table.face(corner_id);
1593                                continue;
1594                            }
1595                        }
1596
1597                        // The current vertex has been already visited or it was on a boundary.
1598                        // We need to determine whether we can visit any of its neighboring faces.
1599                        let right_corner_id = corner_table.opposite(corner_table.next(corner_id)); // GetRightCorner
1600                        let left_corner_id =
1601                            corner_table.opposite(corner_table.previous(corner_id)); // GetLeftCorner
1602
1603                        let right_face_id = if right_corner_id == INVALID_CORNER_INDEX {
1604                            crate::geometry_indices::INVALID_FACE_INDEX
1605                        } else {
1606                            corner_table.face(right_corner_id)
1607                        };
1608                        let left_face_id = if left_corner_id == INVALID_CORNER_INDEX {
1609                            crate::geometry_indices::INVALID_FACE_INDEX
1610                        } else {
1611                            corner_table.face(left_corner_id)
1612                        };
1613
1614                        let right_visited = right_face_id
1615                            == crate::geometry_indices::INVALID_FACE_INDEX
1616                            || visited_faces[right_face_id.0 as usize];
1617                        let left_visited = left_face_id
1618                            == crate::geometry_indices::INVALID_FACE_INDEX
1619                            || visited_faces[left_face_id.0 as usize];
1620
1621                        if right_visited {
1622                            if left_visited {
1623                                // Both neighboring faces are visited. End reached.
1624                                break;
1625                            } else {
1626                                // Go to the left face
1627                                corner_id = left_corner_id;
1628                                face_id = left_face_id;
1629                            }
1630                        } else if left_visited {
1631                            // Left face visited, go to the right one
1632                            corner_id = right_corner_id;
1633                            face_id = right_face_id;
1634                        } else {
1635                            // Both neighboring faces are unvisited, we need to visit both.
1636                            // Split the traversal.
1637                            // First make the top of the current corner stack point to the left face
1638                            // (this one will be processed second).
1639                            // Add a new corner to the top of the stack (right face needs to be
1640                            // traversed first).
1641                            corner_stack.push(left_corner_id);
1642                            corner_stack.push(right_corner_id);
1643                            break;
1644                        }
1645                    }
1646                }
1647            };
1648
1649        // Run the traverser in the same way as C++ MeshTraversalSequencer:
1650        // - If a corner_order is provided, process only those corners.
1651        // - Otherwise, process sequential CornerIndex(3 * face_id).
1652        if !processed_connectivity_corners.is_empty() {
1653            for &c in processed_connectivity_corners {
1654                traverse_from_corner(
1655                    CornerIndex(c),
1656                    &mut point_ids,
1657                    &mut vertex_to_data_map,
1658                    &mut visited_vertices,
1659                    &mut visited_faces,
1660                );
1661            }
1662        } else {
1663            for f in 0..num_faces {
1664                if !visited_faces[f] {
1665                    traverse_from_corner(
1666                        CornerIndex((f * 3) as u32),
1667                        &mut point_ids,
1668                        &mut vertex_to_data_map,
1669                        &mut visited_vertices,
1670                        &mut visited_faces,
1671                    );
1672                }
1673            }
1674        }
1675
1676        Ok((point_ids, data_to_corner_map, vertex_to_data_map))
1677    }
1678
1679    #[allow(dead_code)]
1680    fn generate_point_ids_and_corners_max_prediction_degree(
1681        &self,
1682        mesh: &Mesh,
1683        _processed_connectivity_corners: &[u32],
1684    ) -> Result<AttributeTraversalArrays, DracoError> {
1685        // Matches C++ MaxPredictionDegreeTraverser (MESH_TRAVERSAL_PREDICTION_DEGREE).
1686        let corner_table = self.corner_table.as_ref().ok_or_else(|| {
1687            DracoError::DracoError(
1688                "Edgebreaker prediction-degree traversal missing corner table".to_string(),
1689            )
1690        })?;
1691        // Reject an inconsistent corner table before the traversal indexes
1692        // per-vertex / per-face arrays by table-derived ids.
1693        if !corner_table.is_index_consistent() {
1694            return Err(DracoError::DracoError(
1695                "Inconsistent corner table for attribute traversal".to_string(),
1696            ));
1697        }
1698        let num_vertices = corner_table.num_vertices();
1699        let num_faces = corner_table.num_faces();
1700
1701        let mut point_ids = Vec::with_capacity(num_vertices);
1702        let mut data_to_corner_map = Vec::with_capacity(num_vertices);
1703        // Build vertex_to_data_map during traversal: vertex_to_data_map[vertex_id] = data_id
1704        // where data_id is the index into point_ids where this vertex was first visited.
1705        let mut vertex_to_data_map: Vec<i32> = vec![-1; num_vertices];
1706
1707        let mut visited_vertices = vec![false; num_vertices];
1708        let mut visited_faces = vec![false; num_faces];
1709        let mut prediction_degree: Vec<i32> = vec![0; num_vertices];
1710        let event_log_enabled = test_event_log::enabled();
1711
1712        // Buckets (stacks) for priorities 0..2.
1713        let mut stacks: [Vec<CornerIndex>; 3] = [Vec::new(), Vec::new(), Vec::new()];
1714        let mut best_priority: usize = 0;
1715
1716        // Helper to get mesh PointIndex from corner (matches C++ Mesh::CornerToPointId)
1717        let corner_to_point_id = |c: CornerIndex| -> PointIndex {
1718            if c == INVALID_CORNER_INDEX {
1719                return PointIndex(u32::MAX);
1720            }
1721            let face_id = FaceIndex(c.0 / 3);
1722            let corner_offset = (c.0 % 3) as usize;
1723            mesh.face(face_id)[corner_offset]
1724        };
1725
1726        let visit_vertex = |v: VertexIndex,
1727                            c: CornerIndex,
1728                            point_ids: &mut Vec<PointIndex>,
1729                            data_to_corner_map: &mut Vec<u32>,
1730                            visited_vertices: &mut [bool],
1731                            vertex_to_data_map: &mut [i32]| {
1732            if v == INVALID_VERTEX_INDEX {
1733                return;
1734            }
1735            let vi = v.0 as usize;
1736            if vi >= visited_vertices.len() {
1737                return;
1738            }
1739            if !visited_vertices[vi] {
1740                visited_vertices[vi] = true;
1741                // Record vertex->data_id mapping BEFORE pushing to point_ids
1742                // data_id is current length of point_ids (0-indexed sequence number)
1743                vertex_to_data_map[vi] = point_ids.len() as i32;
1744                // Use corner_to_point_id to get mesh PointIndex from corner
1745                let point_id = corner_to_point_id(c);
1746                if event_log_enabled {
1747                    test_event_log::record_event(format!("MAP:{}->v{}", c.0, v.0));
1748                    test_event_log::record_event(format!("MAP_POINT:{}->p{}", c.0, point_id.0));
1749                }
1750                point_ids.push(point_id);
1751                data_to_corner_map.push(c.0);
1752            }
1753        };
1754
1755        let compute_priority = |corner_id: CornerIndex,
1756                                visited_vertices: &[bool],
1757                                prediction_degree: &mut [i32]|
1758         -> usize {
1759            if corner_id == INVALID_CORNER_INDEX {
1760                return 2;
1761            }
1762            let v_tip = corner_table.vertex(corner_id);
1763            if v_tip == INVALID_VERTEX_INDEX {
1764                return 2;
1765            }
1766            let vi = v_tip.0 as usize;
1767            if vi < visited_vertices.len() && visited_vertices[vi] {
1768                return 0;
1769            }
1770            if vi < prediction_degree.len() {
1771                prediction_degree[vi] += 1;
1772                if prediction_degree[vi] > 1 {
1773                    1
1774                } else {
1775                    2
1776                }
1777            } else {
1778                2
1779            }
1780        };
1781
1782        let add_corner_to_stack = |ci: CornerIndex,
1783                                   priority: usize,
1784                                   stacks: &mut [Vec<CornerIndex>; 3],
1785                                   best_priority: &mut usize| {
1786            let p = priority.min(2);
1787            stacks[p].push(ci);
1788            if p < *best_priority {
1789                *best_priority = p;
1790            }
1791        };
1792
1793        let pop_next_corner =
1794            |stacks: &mut [Vec<CornerIndex>; 3], best_priority: &mut usize| -> CornerIndex {
1795                for p in *best_priority..3 {
1796                    if let Some(ci) = stacks[p].pop() {
1797                        *best_priority = p;
1798                        return ci;
1799                    }
1800                }
1801                INVALID_CORNER_INDEX
1802            };
1803
1804        let clear_stacks = |stacks: &mut [Vec<CornerIndex>; 3]| {
1805            stacks[0].clear();
1806            stacks[1].clear();
1807            stacks[2].clear();
1808        };
1809
1810        let traverse_from_corner =
1811            |start_corner: CornerIndex,
1812             point_ids: &mut Vec<PointIndex>,
1813             data_to_corner_map: &mut Vec<u32>,
1814             visited_vertices: &mut Vec<bool>,
1815             visited_faces: &mut Vec<bool>,
1816             prediction_degree: &mut Vec<i32>,
1817             stacks: &mut [Vec<CornerIndex>; 3],
1818             best_priority: &mut usize,
1819             vertex_to_data_map: &mut Vec<i32>| {
1820                let start_face = corner_table.face(start_corner);
1821                if start_face == crate::geometry_indices::INVALID_FACE_INDEX {
1822                    return;
1823                }
1824                if visited_faces[start_face.0 as usize] {
1825                    return;
1826                }
1827
1828                clear_stacks(stacks);
1829                stacks[0].push(start_corner);
1830                *best_priority = 0;
1831
1832                // Pre-visit next, prev and tip vertices.
1833                let next_c = corner_table.next(start_corner);
1834                let prev_c = corner_table.previous(start_corner);
1835                visit_vertex(
1836                    corner_table.vertex(next_c),
1837                    next_c,
1838                    point_ids,
1839                    data_to_corner_map,
1840                    visited_vertices,
1841                    vertex_to_data_map,
1842                );
1843                visit_vertex(
1844                    corner_table.vertex(prev_c),
1845                    prev_c,
1846                    point_ids,
1847                    data_to_corner_map,
1848                    visited_vertices,
1849                    vertex_to_data_map,
1850                );
1851                visit_vertex(
1852                    corner_table.vertex(start_corner),
1853                    start_corner,
1854                    point_ids,
1855                    data_to_corner_map,
1856                    visited_vertices,
1857                    vertex_to_data_map,
1858                );
1859
1860                loop {
1861                    let mut corner_id = pop_next_corner(stacks, best_priority);
1862                    if corner_id == INVALID_CORNER_INDEX {
1863                        break;
1864                    }
1865                    let face_id0 = corner_table.face(corner_id);
1866                    if face_id0 == crate::geometry_indices::INVALID_FACE_INDEX {
1867                        continue;
1868                    }
1869                    if visited_faces[face_id0.0 as usize] {
1870                        continue;
1871                    }
1872
1873                    loop {
1874                        let face_id = corner_table.face(corner_id);
1875                        if face_id == crate::geometry_indices::INVALID_FACE_INDEX {
1876                            break;
1877                        }
1878                        visited_faces[face_id.0 as usize] = true;
1879
1880                        let vert_id = corner_table.vertex(corner_id);
1881                        if vert_id != INVALID_VERTEX_INDEX {
1882                            let vi = vert_id.0 as usize;
1883                            if vi < visited_vertices.len() && !visited_vertices[vi] {
1884                                visit_vertex(
1885                                    vert_id,
1886                                    corner_id,
1887                                    point_ids,
1888                                    data_to_corner_map,
1889                                    visited_vertices,
1890                                    vertex_to_data_map,
1891                                );
1892                            }
1893                        }
1894
1895                        let right_corner_id = corner_table.right_corner(corner_id);
1896                        let left_corner_id = corner_table.left_corner(corner_id);
1897                        let right_face_id = if right_corner_id == INVALID_CORNER_INDEX {
1898                            crate::geometry_indices::INVALID_FACE_INDEX
1899                        } else {
1900                            corner_table.face(right_corner_id)
1901                        };
1902                        let left_face_id = if left_corner_id == INVALID_CORNER_INDEX {
1903                            crate::geometry_indices::INVALID_FACE_INDEX
1904                        } else {
1905                            corner_table.face(left_corner_id)
1906                        };
1907
1908                        let is_right_face_visited = right_face_id
1909                            == crate::geometry_indices::INVALID_FACE_INDEX
1910                            || visited_faces[right_face_id.0 as usize];
1911                        let is_left_face_visited = left_face_id
1912                            == crate::geometry_indices::INVALID_FACE_INDEX
1913                            || visited_faces[left_face_id.0 as usize];
1914
1915                        if !is_left_face_visited {
1916                            let priority = compute_priority(
1917                                left_corner_id,
1918                                visited_vertices,
1919                                prediction_degree,
1920                            );
1921                            if is_right_face_visited && priority <= *best_priority {
1922                                corner_id = left_corner_id;
1923                                continue;
1924                            }
1925                            add_corner_to_stack(left_corner_id, priority, stacks, best_priority);
1926                        }
1927
1928                        if !is_right_face_visited {
1929                            let priority = compute_priority(
1930                                right_corner_id,
1931                                visited_vertices,
1932                                prediction_degree,
1933                            );
1934                            if priority <= *best_priority {
1935                                corner_id = right_corner_id;
1936                                continue;
1937                            }
1938                            add_corner_to_stack(right_corner_id, priority, stacks, best_priority);
1939                        }
1940
1941                        break;
1942                    }
1943                }
1944            };
1945
1946        // C++ DECODER traverses faces SEQUENTIALLY (face 0, face 1, face 2, ...)
1947        // NOT using processed_connectivity_corners (that's only for the ENCODER)!
1948        // See C++ MeshTraversalSequencer::GenerateSequenceInternal() - when corner_order_ is null,
1949        // it does: for (int i = 0; i < num_faces; ++i) ProcessCorner(CornerIndex(3 * i));
1950        for f in 0..num_faces {
1951            if visited_faces[f] {
1952                continue;
1953            }
1954            let first_corner = corner_table.first_corner(FaceIndex(f as u32));
1955            traverse_from_corner(
1956                first_corner,
1957                &mut point_ids,
1958                &mut data_to_corner_map,
1959                &mut visited_vertices,
1960                &mut visited_faces,
1961                &mut prediction_degree,
1962                &mut stacks,
1963                &mut best_priority,
1964                &mut vertex_to_data_map,
1965            );
1966        }
1967
1968        Ok((point_ids, data_to_corner_map, vertex_to_data_map))
1969    }
1970
1971    #[allow(dead_code)]
1972    fn is_vertex_on_boundary(&self, corner_table: &CornerTable, vert_id: VertexIndex) -> bool {
1973        let start_c = corner_table.left_most_corner(vert_id);
1974        if start_c == INVALID_CORNER_INDEX {
1975            return true;
1976        }
1977        let mut c = start_c;
1978        loop {
1979            // Edge (c, next(c)) is incident to v.
1980            if corner_table.opposite(c) == INVALID_CORNER_INDEX {
1981                return true;
1982            }
1983            // Edge (prev(c), c) is also incident to v.
1984            if corner_table.opposite(corner_table.previous(c)) == INVALID_CORNER_INDEX {
1985                return true;
1986            }
1987            c = corner_table.swing_right(c);
1988            if c == INVALID_CORNER_INDEX {
1989                return true;
1990            }
1991            if c == start_c {
1992                break;
1993            }
1994        }
1995        false
1996    }
1997
1998    /// Helper function to check if a vertex is on the boundary
1999    /// Matches C++ CornerTable::IsOnBoundary
2000    fn is_vertex_on_boundary_impl(
2001        corner_table: &crate::corner_table::CornerTable,
2002        v: VertexIndex,
2003    ) -> bool {
2004        let corner = corner_table.left_most_corner(v);
2005        if corner == INVALID_CORNER_INDEX {
2006            return true; // Isolated vertex - treat as boundary
2007        }
2008        // C++ checks: if (SwingLeft(corner) == kInvalidCornerIndex) return true;
2009        if corner_table.swing_left(corner) == INVALID_CORNER_INDEX {
2010            return true;
2011        }
2012        false
2013    }
2014}
2015
2016#[cfg(test)]
2017mod tests {
2018    use super::*;
2019
2020    #[test]
2021    fn attribute_corner_table_rejects_out_of_range_seam_corner() {
2022        let mut corner_table = CornerTable::new(1);
2023        corner_table.set_face_vertices(FaceIndex(0), PointIndex(0), PointIndex(1), PointIndex(2));
2024
2025        let invalid_corner = corner_table.num_corners() as u32;
2026        let status = MeshDecoder::make_attribute_corner_table(&corner_table, &[invalid_corner]);
2027
2028        assert!(status.is_err());
2029    }
2030
2031    #[test]
2032    fn vertex_to_data_map_rejects_out_of_range_corner() {
2033        let mut corner_table = CornerTable::new(1);
2034        corner_table.set_face_vertices(FaceIndex(0), PointIndex(0), PointIndex(1), PointIndex(2));
2035
2036        let invalid_corner = corner_table.num_corners() as u32;
2037        let status = build_vertex_to_data_map_from_corner_map(&corner_table, &[invalid_corner]);
2038
2039        assert!(status.is_err());
2040    }
2041}
2042
2043fn validate_mesh_index_count(num_faces: usize) -> Result<usize, DracoError> {
2044    num_faces
2045        .checked_mul(3)
2046        .ok_or_else(|| DracoError::DracoError("Mesh face index count overflow".to_string()))
2047}
2048
2049fn make_zeroed_indices(num_indices: usize) -> Result<Vec<u32>, DracoError> {
2050    let mut indices = Vec::new();
2051    indices
2052        .try_reserve_exact(num_indices)
2053        .map_err(|_| DracoError::DracoError("Failed to allocate mesh indices".to_string()))?;
2054    indices.resize(num_indices, 0);
2055    Ok(indices)
2056}
2057
2058fn make_point_ids(num_points: usize) -> Result<Vec<PointIndex>, DracoError> {
2059    let mut point_ids = Vec::new();
2060    point_ids
2061        .try_reserve_exact(num_points)
2062        .map_err(|_| DracoError::DracoError("Failed to allocate point ids".to_string()))?;
2063    for i in 0..num_points {
2064        point_ids.push(PointIndex(i as u32));
2065    }
2066    Ok(point_ids)
2067}