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