1use std::collections::HashMap;
32use std::fs;
33use std::io;
34use std::path::Path;
35
36use draco_core::decoder_buffer::DecoderBuffer;
37use draco_core::draco_types::DataType;
38use draco_core::geometry_attribute::PointAttribute;
39use draco_core::mesh::Mesh;
40use draco_core::mesh_decoder::MeshDecoder;
41#[cfg(feature = "point_cloud_decode")]
42use draco_core::point_cloud::PointCloud;
43#[cfg(feature = "point_cloud_decode")]
44use draco_core::point_cloud_decoder::PointCloudDecoder;
45use serde::Deserialize;
46
47use crate::traits::ReadFromBytes;
48
49use crate::gltf_geometry::{
53 add_named_attribute, decode_geometry, supported_semantic_spec, AccessorSource, DecodedAccessor,
54 GltfError, Result, GLTF_COMPONENT_BYTE, GLTF_COMPONENT_FLOAT, GLTF_COMPONENT_SHORT,
55 GLTF_COMPONENT_UNSIGNED_BYTE, GLTF_COMPONENT_UNSIGNED_INT, GLTF_COMPONENT_UNSIGNED_SHORT,
56 GLTF_MODE_TRIANGLES,
57};
58
59#[allow(dead_code)]
64#[derive(Debug, Deserialize)]
65#[serde(rename_all = "camelCase")]
66struct GltfRoot {
67 asset: Asset,
68 #[serde(default)]
69 accessors: Vec<Accessor>,
70 #[serde(default)]
71 buffer_views: Vec<BufferView>,
72 #[serde(default)]
73 buffers: Vec<Buffer>,
74 #[serde(default)]
75 meshes: Vec<GltfMesh>,
76 #[serde(default)]
77 nodes: Vec<GltfNode>,
78 #[serde(default)]
79 scenes: Vec<GltfScene>,
80 #[serde(default)]
81 skins: Vec<serde_json::Value>,
82 #[serde(default)]
83 animations: Vec<serde_json::Value>,
84 scene: Option<usize>,
86 #[serde(default)]
87 extensions_used: Vec<String>,
88 #[serde(default)]
89 extensions_required: Vec<String>,
90}
91
92#[allow(dead_code)]
93#[derive(Debug, Deserialize)]
94#[serde(rename_all = "camelCase")]
95struct Asset {
96 version: String,
97 min_version: Option<String>,
98}
99
100#[allow(dead_code)]
102#[derive(Debug, Deserialize)]
103#[serde(rename_all = "camelCase")]
104struct GltfScene {
105 name: Option<String>,
106 #[serde(default)]
107 nodes: Vec<usize>,
108}
109
110#[allow(dead_code)]
112#[derive(Debug, Deserialize)]
113#[serde(rename_all = "camelCase")]
114struct GltfNode {
115 name: Option<String>,
116 mesh: Option<usize>,
118 #[serde(default)]
120 children: Vec<usize>,
121 matrix: Option<[f32; 16]>,
123 translation: Option<[f32; 3]>,
125 rotation: Option<[f32; 4]>,
127 scale: Option<[f32; 3]>,
129 skin: Option<usize>,
130}
131
132#[allow(dead_code)]
133#[derive(Debug, Deserialize)]
134#[serde(rename_all = "camelCase")]
135struct Accessor {
136 buffer_view: Option<usize>,
137 byte_offset: Option<usize>,
138 component_type: u32,
139 #[serde(default)]
140 normalized: bool,
141 count: usize,
142 #[serde(rename = "type")]
143 accessor_type: String,
144 #[serde(default)]
145 min: Vec<f64>,
146 #[serde(default)]
147 max: Vec<f64>,
148 sparse: Option<serde_json::Value>,
149}
150
151#[allow(dead_code)]
152#[derive(Debug, Deserialize)]
153#[serde(rename_all = "camelCase")]
154struct BufferView {
155 buffer: usize,
156 byte_offset: Option<usize>,
157 byte_length: usize,
158 byte_stride: Option<usize>,
159 target: Option<u32>,
160}
161
162#[allow(dead_code)]
163#[derive(Debug, Deserialize)]
164#[serde(rename_all = "camelCase")]
165struct Buffer {
166 byte_length: usize,
167 uri: Option<String>,
168}
169
170#[derive(Debug, Deserialize)]
171#[serde(rename_all = "camelCase")]
172struct GltfMesh {
173 name: Option<String>,
174 primitives: Vec<Primitive>,
175}
176
177#[allow(dead_code)]
178#[derive(Debug, Deserialize)]
179#[serde(rename_all = "camelCase")]
180struct Primitive {
181 #[serde(default)]
182 attributes: HashMap<String, usize>,
183 indices: Option<usize>,
184 mode: Option<u32>,
185 material: Option<usize>,
186 #[serde(default)]
187 targets: Vec<HashMap<String, usize>>,
188 extensions: Option<PrimitiveExtensions>,
189}
190
191#[derive(Debug, Deserialize)]
192#[serde(rename_all = "camelCase")]
193struct PrimitiveExtensions {
194 #[serde(rename = "KHR_draco_mesh_compression")]
195 khr_draco_mesh_compression: Option<DracoExtension>,
196}
197
198#[derive(Debug, Deserialize)]
199#[serde(rename_all = "camelCase")]
200struct DracoExtension {
201 buffer_view: usize,
202 #[serde(default)]
203 attributes: HashMap<String, usize>,
204}
205
206const GLB_MAGIC: u32 = 0x46546C67; const GLB_VERSION: u32 = 2;
212const GLB_CHUNK_JSON: u32 = 0x4E4F534A; const GLB_CHUNK_BIN: u32 = 0x004E4942; const KHR_DRACO_MESH_COMPRESSION: &str = "KHR_draco_mesh_compression";
215
216pub struct GltfReader {
222 root: GltfRoot,
223 buffers: Vec<Vec<u8>>,
224}
225
226struct GltfAccessorReader<'a> {
229 accessors: &'a [Accessor],
230 buffer_views: &'a [BufferView],
231 buffers: &'a [Vec<u8>],
232}
233
234impl AccessorSource for GltfAccessorReader<'_> {
235 fn read_attribute(
236 &self,
237 accessor: usize,
238 expected_types: &[&str],
239 allowed_component_types: &[u32],
240 ) -> Result<DecodedAccessor> {
241 GltfAccessorReader::read_attribute(self, accessor, expected_types, allowed_component_types)
242 }
243
244 fn read_indices(&self, accessor: usize) -> Result<Vec<u32>> {
245 GltfAccessorReader::read_indices(self, accessor)
246 }
247}
248
249impl<'a> GltfAccessorReader<'a> {
250 fn new(root: &'a GltfRoot, buffers: &'a [Vec<u8>]) -> Self {
251 Self {
252 accessors: &root.accessors,
253 buffer_views: &root.buffer_views,
254 buffers,
255 }
256 }
257
258 fn read_attribute(
259 &self,
260 accessor_idx: usize,
261 expected_types: &[&str],
262 allowed_component_types: &[u32],
263 ) -> Result<DecodedAccessor> {
264 let accessor = self.accessor(accessor_idx)?;
265
266 if !expected_types
267 .iter()
268 .any(|expected| accessor.accessor_type == *expected)
269 {
270 return Err(GltfError::InvalidGltf(format!(
271 "Expected one of {:?} accessor, got {}",
272 expected_types, accessor.accessor_type
273 )));
274 }
275
276 if !allowed_component_types.contains(&accessor.component_type) {
277 return Err(GltfError::Unsupported(format!(
278 "Unsupported {} component type: {}",
279 accessor.accessor_type, accessor.component_type
280 )));
281 }
282
283 let num_components = accessor_num_components(&accessor.accessor_type)?;
284 let data_type = data_type_for_component_type(accessor.component_type)?;
285 let component_size = data_type.byte_length();
286 let row_size = num_components as usize * component_size;
287 let layout = self.accessor_layout(accessor, row_size, component_size, true, "Accessor")?;
288
289 let mut bytes = Vec::with_capacity(accessor.count * row_size);
290 for i in 0..accessor.count {
291 let offset = layout
292 .start
293 .checked_add(i * layout.stride)
294 .ok_or_else(|| GltfError::InvalidGltf("Accessor range overflow".into()))?;
295 if offset + row_size > layout.view_end {
296 return Err(GltfError::InvalidGltf(format!(
297 "{} accessor out of bounds",
298 accessor.accessor_type
299 )));
300 }
301 bytes.extend_from_slice(&layout.buffer[offset..offset + row_size]);
302 }
303
304 Ok(DecodedAccessor::new(
305 accessor.count,
306 num_components,
307 data_type,
308 accessor.normalized,
309 bytes,
310 ))
311 }
312
313 fn read_indices(&self, accessor_idx: usize) -> Result<Vec<u32>> {
314 let accessor = self.accessor(accessor_idx)?;
315
316 if accessor.accessor_type != "SCALAR" {
317 return Err(GltfError::InvalidGltf(format!(
318 "Expected SCALAR accessor for indices, got {}",
319 accessor.accessor_type
320 )));
321 }
322
323 let component_size = match accessor.component_type {
324 GLTF_COMPONENT_UNSIGNED_BYTE => 1,
325 GLTF_COMPONENT_UNSIGNED_SHORT => 2,
326 GLTF_COMPONENT_UNSIGNED_INT => 4,
327 _ => {
328 return Err(GltfError::Unsupported(format!(
329 "Unsupported index component type: {}",
330 accessor.component_type
331 )));
332 }
333 };
334 let layout = self.accessor_layout(
335 accessor,
336 component_size,
337 component_size,
338 false,
339 "Index accessor",
340 )?;
341 let mut result = Vec::with_capacity(accessor.count);
342
343 match accessor.component_type {
344 GLTF_COMPONENT_UNSIGNED_BYTE => {
345 for i in 0..accessor.count {
346 let offset = layout.start + i * layout.stride;
347 if offset + component_size > layout.view_end {
348 return Err(GltfError::InvalidGltf(
349 "Index accessor out of bounds".into(),
350 ));
351 }
352 result.push(layout.buffer[offset] as u32);
353 }
354 }
355 GLTF_COMPONENT_UNSIGNED_SHORT => {
356 for i in 0..accessor.count {
357 let offset = layout.start + i * layout.stride;
358 if offset + component_size > layout.view_end {
359 return Err(GltfError::InvalidGltf(
360 "Index accessor out of bounds".into(),
361 ));
362 }
363 let val =
364 u16::from_le_bytes([layout.buffer[offset], layout.buffer[offset + 1]]);
365 result.push(val as u32);
366 }
367 }
368 GLTF_COMPONENT_UNSIGNED_INT => {
369 for i in 0..accessor.count {
370 let offset = layout.start + i * layout.stride;
371 if offset + component_size > layout.view_end {
372 return Err(GltfError::InvalidGltf(
373 "Index accessor out of bounds".into(),
374 ));
375 }
376 let val = u32::from_le_bytes([
377 layout.buffer[offset],
378 layout.buffer[offset + 1],
379 layout.buffer[offset + 2],
380 layout.buffer[offset + 3],
381 ]);
382 result.push(val);
383 }
384 }
385 _ => unreachable!(),
386 }
387
388 Ok(result)
389 }
390
391 fn accessor(&self, accessor_idx: usize) -> Result<&Accessor> {
392 self.accessors.get(accessor_idx).ok_or_else(|| {
393 GltfError::InvalidGltf(format!("Invalid accessor index: {}", accessor_idx))
394 })
395 }
396
397 fn accessor_layout(
398 &self,
399 accessor: &Accessor,
400 element_size: usize,
401 component_size: usize,
402 vertex_attribute: bool,
403 label: &str,
404 ) -> Result<AccessorLayout<'a>> {
405 if accessor.sparse.is_some() {
406 return Err(GltfError::Unsupported(
407 "Sparse accessors are not supported".into(),
408 ));
409 }
410 if accessor.count == 0 {
411 return Err(GltfError::InvalidGltf(format!(
412 "{} count must be greater than zero",
413 label
414 )));
415 }
416
417 let buffer_view_idx = accessor
418 .buffer_view
419 .ok_or_else(|| GltfError::InvalidGltf(format!("{} has no bufferView", label)))?;
420
421 let buffer_view = self.buffer_views.get(buffer_view_idx).ok_or_else(|| {
422 GltfError::InvalidGltf(format!("Invalid bufferView index: {}", buffer_view_idx))
423 })?;
424
425 let buffer = self.buffers.get(buffer_view.buffer).ok_or_else(|| {
426 GltfError::InvalidGltf(format!("Invalid buffer index: {}", buffer_view.buffer))
427 })?;
428
429 let view_offset = buffer_view.byte_offset.unwrap_or(0);
430 let accessor_offset = accessor.byte_offset.unwrap_or(0);
431 if !accessor_offset.is_multiple_of(component_size) {
432 return Err(GltfError::InvalidGltf(format!(
433 "{} byteOffset is not aligned to component size {}",
434 label, component_size
435 )));
436 }
437
438 let start = view_offset
439 .checked_add(accessor_offset)
440 .ok_or_else(|| GltfError::InvalidGltf("Accessor start overflow".into()))?;
441 if start % component_size != 0 {
442 return Err(GltfError::InvalidGltf(format!(
443 "{} absolute byte offset is not aligned to component size {}",
444 label, component_size
445 )));
446 }
447 if !vertex_attribute && buffer_view.byte_stride.is_some() {
448 return Err(GltfError::InvalidGltf(format!(
449 "{} bufferView must not define byteStride",
450 label
451 )));
452 }
453
454 let stride = buffer_view.byte_stride.unwrap_or(element_size);
455
456 if stride < element_size {
457 return Err(GltfError::InvalidGltf(format!(
458 "{} byteStride {} is smaller than element size {}",
459 label, stride, element_size
460 )));
461 }
462 if stride % component_size != 0 {
463 return Err(GltfError::InvalidGltf(format!(
464 "{} byteStride {} is not aligned to component size {}",
465 label, stride, component_size
466 )));
467 }
468 if let Some(byte_stride) = buffer_view.byte_stride {
469 if !(4..=252).contains(&byte_stride) {
470 return Err(GltfError::InvalidGltf(format!(
471 "{} byteStride {} is outside glTF range 4..=252",
472 label, byte_stride
473 )));
474 }
475 if vertex_attribute && byte_stride % 4 != 0 {
476 return Err(GltfError::InvalidGltf(format!(
477 "{} byteStride {} is not 4-byte aligned",
478 label, byte_stride
479 )));
480 }
481 }
482
483 let view_end = view_offset
484 .checked_add(buffer_view.byte_length)
485 .ok_or_else(|| GltfError::InvalidGltf("Buffer view range overflow".into()))?;
486 if start > view_end {
487 return Err(GltfError::InvalidGltf(format!(
488 "{} starts past bufferView end",
489 label
490 )));
491 }
492 let byte_len = stride
493 .checked_mul(accessor.count - 1)
494 .and_then(|prefix| prefix.checked_add(element_size))
495 .ok_or_else(|| GltfError::InvalidGltf("Accessor byte range overflow".into()))?;
496 let accessor_end = start
497 .checked_add(byte_len)
498 .ok_or_else(|| GltfError::InvalidGltf("Accessor byte range overflow".into()))?;
499 if accessor_end > view_end {
500 return Err(GltfError::InvalidGltf(format!(
501 "{} accessor does not fit its bufferView",
502 label
503 )));
504 }
505 if view_end > buffer.len() {
506 return Err(GltfError::InvalidGltf(
507 "Buffer view extends past buffer end".into(),
508 ));
509 }
510
511 Ok(AccessorLayout {
512 buffer,
513 start,
514 stride,
515 view_end,
516 })
517 }
518}
519
520struct AccessorLayout<'a> {
521 buffer: &'a [u8],
522 start: usize,
523 stride: usize,
524 view_end: usize,
525}
526
527struct GlbChunks<'a> {
528 json: &'a [u8],
529 bin: Option<&'a [u8]>,
530}
531
532#[derive(Debug, Clone)]
534pub struct DracoPrimitiveInfo {
535 pub mesh_index: usize,
537 pub mesh_name: Option<String>,
539 pub primitive_index: usize,
541 pub buffer_view: usize,
543 pub attributes: HashMap<String, usize>,
545}
546
547impl GltfReader {
548 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
552 let path = path.as_ref();
553 let data = fs::read(path)?;
554
555 if data.len() >= 4 && read_u32_le(&data[0..4]) == GLB_MAGIC {
556 Self::from_glb_with_base_path(&data, path.parent())
557 } else {
558 let base_path = path.parent();
559 Self::from_gltf(&data, base_path)
560 }
561 }
562
563 pub fn from_glb(data: &[u8]) -> Result<Self> {
565 Self::from_glb_with_base_path(data, None)
566 }
567
568 pub fn from_bytes(data: &[u8]) -> Result<Self> {
573 Self::from_bytes_with_base_path(data, None)
574 }
575
576 pub fn from_bytes_with_base_path(data: &[u8], base_path: Option<&Path>) -> Result<Self> {
578 if data.len() >= 4 && read_u32_le(&data[0..4]) == GLB_MAGIC {
579 Self::from_glb_with_base_path(data, base_path)
580 } else {
581 Self::from_gltf(data, base_path)
582 }
583 }
584
585 fn from_glb_with_base_path(data: &[u8], base_path: Option<&Path>) -> Result<Self> {
586 let chunks = parse_glb_chunks(data)?;
587 let root: GltfRoot = serde_json::from_slice(chunks.json)?;
588 validate_root_metadata(&root)?;
589 reject_unsupported_features(&root)?;
590 let buffers = load_buffers(&root, true, chunks.bin, base_path)?;
591
592 Ok(Self { root, buffers })
593 }
594
595 pub fn from_gltf(json_data: &[u8], base_path: Option<&Path>) -> Result<Self> {
597 let root: GltfRoot = serde_json::from_slice(json_data)?;
598 validate_root_metadata(&root)?;
599 reject_unsupported_features(&root)?;
600 let buffers = load_buffers(&root, false, None, base_path)?;
601
602 Ok(Self { root, buffers })
603 }
604
605 pub fn from_bytes_lenient(data: &[u8]) -> Result<Self> {
614 Self::from_bytes_lenient_with_base_path(data, None)
615 }
616
617 pub fn from_bytes_lenient_with_base_path(
619 data: &[u8],
620 base_path: Option<&Path>,
621 ) -> Result<Self> {
622 if data.len() >= 4 && read_u32_le(&data[0..4]) == GLB_MAGIC {
623 let chunks = parse_glb_chunks(data)?;
624 let root: GltfRoot = serde_json::from_slice(chunks.json)?;
625 validate_root_metadata(&root)?;
626 let buffers = load_buffers(&root, true, chunks.bin, base_path)?;
627 Ok(Self { root, buffers })
628 } else {
629 let root: GltfRoot = serde_json::from_slice(data)?;
630 validate_root_metadata(&root)?;
631 let buffers = load_buffers(&root, false, None, base_path)?;
632 Ok(Self { root, buffers })
633 }
634 }
635
636 pub fn from_value(doc: &serde_json::Value, buffers: Vec<Vec<u8>>) -> Result<Self> {
650 let root: GltfRoot = serde_json::from_value(doc.clone())?;
651 validate_root_metadata(&root)?;
652 Ok(Self { root, buffers })
653 }
654
655 #[cfg(feature = "gltf-writer")]
658 pub(crate) fn buffers(&self) -> &[Vec<u8>] {
659 &self.buffers
660 }
661
662 pub fn decode_primitive_with_semantics(
676 &self,
677 mesh_idx: usize,
678 prim_idx: usize,
679 ) -> Result<(Mesh, Vec<(String, u32)>)> {
680 let gltf_mesh = self.root.meshes.get(mesh_idx).ok_or_else(|| {
681 GltfError::InvalidGltf(format!("Mesh index {} out of range", mesh_idx))
682 })?;
683 let primitive = gltf_mesh.primitives.get(prim_idx).ok_or_else(|| {
684 GltfError::InvalidGltf(format!(
685 "Primitive index {}:{} out of range",
686 mesh_idx, prim_idx
687 ))
688 })?;
689 if primitive
690 .extensions
691 .as_ref()
692 .and_then(|ext| ext.khr_draco_mesh_compression.as_ref())
693 .is_some()
694 {
695 return Err(GltfError::Unsupported(
696 "primitive is already Draco-compressed".into(),
697 ));
698 }
699 self.decode_standard_primitive(mesh_idx, prim_idx, primitive)
700 }
701
702 pub fn has_draco_extension(&self) -> bool {
704 self.root
705 .extensions_used
706 .iter()
707 .any(|ext| ext == KHR_DRACO_MESH_COMPRESSION)
708 }
709
710 pub fn draco_primitives(&self) -> Vec<DracoPrimitiveInfo> {
712 let mut result = Vec::new();
713
714 for (mesh_idx, mesh) in self.root.meshes.iter().enumerate() {
715 for (prim_idx, primitive) in mesh.primitives.iter().enumerate() {
716 if let Some(ext) = &primitive.extensions {
717 if let Some(draco) = &ext.khr_draco_mesh_compression {
718 result.push(DracoPrimitiveInfo {
719 mesh_index: mesh_idx,
720 mesh_name: mesh.name.clone(),
721 primitive_index: prim_idx,
722 buffer_view: draco.buffer_view,
723 attributes: draco.attributes.clone(),
724 });
725 }
726 }
727 }
728 }
729
730 result
731 }
732
733 pub fn get_draco_data(&self, info: &DracoPrimitiveInfo) -> Result<&[u8]> {
735 let buffer_view = self
736 .root
737 .buffer_views
738 .get(info.buffer_view)
739 .ok_or_else(|| {
740 GltfError::InvalidGltf(format!("Invalid buffer view index: {}", info.buffer_view))
741 })?;
742
743 let buffer = self.buffers.get(buffer_view.buffer).ok_or_else(|| {
744 GltfError::InvalidGltf(format!("Invalid buffer index: {}", buffer_view.buffer))
745 })?;
746
747 let offset = buffer_view.byte_offset.unwrap_or(0);
748 let end = offset
749 .checked_add(buffer_view.byte_length)
750 .ok_or_else(|| GltfError::InvalidGltf("Buffer view range overflow".into()))?;
751
752 if end > buffer.len() {
753 return Err(GltfError::InvalidGltf(
754 "Buffer view extends past buffer end".into(),
755 ));
756 }
757
758 Ok(&buffer[offset..end])
759 }
760
761 pub fn decode_draco_mesh(&self, info: &DracoPrimitiveInfo) -> Result<Mesh> {
763 let data = self.get_draco_data(info)?;
764 let mut decoder_buffer = DecoderBuffer::new(data);
765 let mut mesh = Mesh::new();
766 let mut decoder = MeshDecoder::new();
767
768 decoder
769 .decode(&mut decoder_buffer, &mut mesh)
770 .map_err(|e| GltfError::DracoDecode(format!("{:?}", e)))?;
771
772 let primitive = self.primitive_for_draco_info(info)?;
773 self.validate_draco_primitive_metadata(info, primitive, &mesh)?;
774 self.add_draco_side_attributes(&mut mesh, primitive, info)?;
775
776 Ok(mesh)
777 }
778
779 #[cfg(feature = "point_cloud_decode")]
781 pub fn decode_draco_point_cloud(&self, info: &DracoPrimitiveInfo) -> Result<PointCloud> {
782 let data = self.get_draco_data(info)?;
783 let mut decoder_buffer = DecoderBuffer::new(data);
784 let mut point_cloud = PointCloud::new();
785 let mut decoder = PointCloudDecoder::new();
786
787 decoder
788 .decode(&mut decoder_buffer, &mut point_cloud)
789 .map_err(|e| GltfError::DracoDecode(format!("{:?}", e)))?;
790
791 Ok(point_cloud)
792 }
793
794 pub fn decode_all_draco_meshes(&self) -> Result<Vec<(DracoPrimitiveInfo, Mesh)>> {
796 let primitives = self.draco_primitives();
797 let mut result = Vec::with_capacity(primitives.len());
798
799 for info in primitives {
800 let mesh = self.decode_draco_mesh(&info)?;
801 result.push((info, mesh));
802 }
803
804 Ok(result)
805 }
806
807 fn decode_standard_primitive(
818 &self,
819 _mesh_idx: usize,
820 _prim_idx: usize,
821 primitive: &Primitive,
822 ) -> Result<(Mesh, Vec<(String, u32)>)> {
823 let mode = primitive.mode.unwrap_or(GLTF_MODE_TRIANGLES);
824 let attributes: Vec<(String, usize)> = primitive
825 .attributes
826 .iter()
827 .map(|(semantic, accessor)| (semantic.clone(), *accessor))
828 .collect();
829 decode_geometry(
830 &self.accessor_reader(),
831 mode,
832 &attributes,
833 primitive.indices,
834 )
835 }
836
837 fn accessor_reader(&self) -> GltfAccessorReader<'_> {
838 GltfAccessorReader::new(&self.root, &self.buffers)
839 }
840
841 fn decode_primitive_mesh(
842 &self,
843 mesh_idx: usize,
844 gltf_mesh: &GltfMesh,
845 prim_idx: usize,
846 primitive: &Primitive,
847 ) -> Result<Mesh> {
848 if let Some(draco) = primitive
849 .extensions
850 .as_ref()
851 .and_then(|ext| ext.khr_draco_mesh_compression.as_ref())
852 {
853 let info = DracoPrimitiveInfo {
854 mesh_index: mesh_idx,
855 mesh_name: gltf_mesh.name.clone(),
856 primitive_index: prim_idx,
857 buffer_view: draco.buffer_view,
858 attributes: draco.attributes.clone(),
859 };
860 self.decode_draco_mesh(&info)
861 } else {
862 self.decode_standard_primitive(mesh_idx, prim_idx, primitive)
863 .map(|(mesh, _)| mesh)
864 }
865 }
866
867 fn primitive_for_draco_info(&self, info: &DracoPrimitiveInfo) -> Result<&Primitive> {
868 let mesh = self.root.meshes.get(info.mesh_index).ok_or_else(|| {
869 GltfError::InvalidGltf(format!("Invalid mesh index: {}", info.mesh_index))
870 })?;
871 let primitive = mesh.primitives.get(info.primitive_index).ok_or_else(|| {
872 GltfError::InvalidGltf(format!(
873 "Invalid primitive index {} for mesh {}",
874 info.primitive_index, info.mesh_index
875 ))
876 })?;
877 let draco = primitive
878 .extensions
879 .as_ref()
880 .and_then(|ext| ext.khr_draco_mesh_compression.as_ref())
881 .ok_or_else(|| {
882 GltfError::InvalidGltf(format!(
883 "Primitive {}:{} does not use {}",
884 info.mesh_index, info.primitive_index, KHR_DRACO_MESH_COMPRESSION
885 ))
886 })?;
887 if draco.buffer_view != info.buffer_view || draco.attributes != info.attributes {
888 return Err(GltfError::InvalidGltf(
889 "Draco primitive info does not match source primitive".into(),
890 ));
891 }
892 Ok(primitive)
893 }
894
895 fn validate_draco_primitive_metadata(
896 &self,
897 info: &DracoPrimitiveInfo,
898 primitive: &Primitive,
899 mesh: &Mesh,
900 ) -> Result<()> {
901 let mode = primitive.mode.unwrap_or(GLTF_MODE_TRIANGLES);
902 if mode != GLTF_MODE_TRIANGLES {
903 return Err(GltfError::Unsupported(format!(
904 "{} supports only TRIANGLES=4 for Draco mesh primitives, got mode {}",
905 KHR_DRACO_MESH_COMPRESSION, mode
906 )));
907 }
908 if !info.attributes.contains_key("POSITION") {
909 return Err(GltfError::InvalidGltf(format!(
910 "{} primitive is missing POSITION in extension attributes",
911 KHR_DRACO_MESH_COMPRESSION
912 )));
913 }
914
915 for (semantic, &draco_attribute_id) in &info.attributes {
916 let Some(accessor_idx) = primitive.attributes.get(semantic) else {
917 return Err(GltfError::InvalidGltf(format!(
918 "{} attribute {} is not present in primitive.attributes",
919 KHR_DRACO_MESH_COMPRESSION, semantic
920 )));
921 };
922 let attribute_spec = supported_semantic_spec(semantic)?;
923 let attribute = mesh.try_attribute(draco_attribute_id as i32).map_err(|_| {
924 GltfError::InvalidGltf(format!(
925 "Draco attribute id {} for {} is out of range",
926 draco_attribute_id, semantic
927 ))
928 })?;
929 if attribute.attribute_type() != attribute_spec.attribute_type {
930 return Err(GltfError::InvalidGltf(format!(
931 "Draco attribute {} has type {:?}, expected {:?}",
932 semantic,
933 attribute.attribute_type(),
934 attribute_spec.attribute_type
935 )));
936 }
937 self.validate_accessor_matches_attribute(*accessor_idx, semantic, attribute)?;
938 }
939
940 if let Some(indices_accessor_idx) = primitive.indices {
941 let accessor = self
942 .root
943 .accessors
944 .get(indices_accessor_idx)
945 .ok_or_else(|| {
946 GltfError::InvalidGltf(format!(
947 "Invalid indices accessor index: {}",
948 indices_accessor_idx
949 ))
950 })?;
951 if accessor.sparse.is_some() {
952 return Err(GltfError::Unsupported(
953 "Sparse index accessors are not supported".into(),
954 ));
955 }
956 if accessor.accessor_type != "SCALAR" {
957 return Err(GltfError::InvalidGltf(format!(
958 "Expected SCALAR accessor for Draco indices, got {}",
959 accessor.accessor_type
960 )));
961 }
962 if ![
963 GLTF_COMPONENT_UNSIGNED_BYTE,
964 GLTF_COMPONENT_UNSIGNED_SHORT,
965 GLTF_COMPONENT_UNSIGNED_INT,
966 ]
967 .contains(&accessor.component_type)
968 {
969 return Err(GltfError::Unsupported(format!(
970 "Unsupported Draco index accessor component type: {}",
971 accessor.component_type
972 )));
973 }
974 let expected_count = mesh.num_faces() * 3;
975 if accessor.count != expected_count {
976 return Err(GltfError::InvalidGltf(format!(
977 "Draco indices accessor count {} does not match decoded index count {}",
978 accessor.count, expected_count
979 )));
980 }
981 }
982
983 Ok(())
984 }
985
986 fn validate_accessor_matches_attribute(
987 &self,
988 accessor_idx: usize,
989 semantic: &str,
990 attribute: &PointAttribute,
991 ) -> Result<()> {
992 let accessor = self.root.accessors.get(accessor_idx).ok_or_else(|| {
993 GltfError::InvalidGltf(format!("Invalid accessor index: {}", accessor_idx))
994 })?;
995 if accessor.sparse.is_some() {
996 return Err(GltfError::Unsupported(format!(
997 "Sparse accessor for {} is not supported",
998 semantic
999 )));
1000 }
1001 let expected_accessor_type = gltf_type_for_num_components(attribute.num_components())?;
1002 if accessor.accessor_type != expected_accessor_type {
1003 return Err(GltfError::InvalidGltf(format!(
1004 "{} accessor type {} does not match decoded attribute type {}",
1005 semantic, accessor.accessor_type, expected_accessor_type
1006 )));
1007 }
1008 let expected_component_type = component_type_for_data_type(attribute.data_type())?;
1009 let attribute_spec = supported_semantic_spec(semantic)?;
1010 if !attribute_spec
1011 .allowed_component_types
1012 .contains(&expected_component_type)
1013 {
1014 return Err(GltfError::Unsupported(format!(
1015 "{} decoded component type {} is not supported by draco-io glTF",
1016 semantic, expected_component_type
1017 )));
1018 }
1019 if accessor.component_type != expected_component_type {
1020 return Err(GltfError::InvalidGltf(format!(
1021 "{} accessor componentType {} does not match decoded componentType {}",
1022 semantic, accessor.component_type, expected_component_type
1023 )));
1024 }
1025 if accessor.normalized != attribute.normalized() {
1026 return Err(GltfError::InvalidGltf(format!(
1027 "{} accessor normalized={} does not match decoded normalized={}",
1028 semantic,
1029 accessor.normalized,
1030 attribute.normalized()
1031 )));
1032 }
1033 if accessor.count != attribute.size() {
1034 return Err(GltfError::InvalidGltf(format!(
1035 "{} accessor count {} does not match decoded attribute count {}",
1036 semantic,
1037 accessor.count,
1038 attribute.size()
1039 )));
1040 }
1041 Ok(())
1042 }
1043
1044 fn add_draco_side_attributes(
1045 &self,
1046 mesh: &mut Mesh,
1047 primitive: &Primitive,
1048 info: &DracoPrimitiveInfo,
1049 ) -> Result<()> {
1050 let accessor_reader = self.accessor_reader();
1051 let mut attributes: Vec<_> = primitive.attributes.iter().collect();
1052 attributes.sort_by_key(|(left, _)| *left);
1053
1054 for (semantic, accessor_idx) in attributes {
1055 if info.attributes.contains_key(semantic) {
1056 continue;
1057 }
1058 add_named_attribute(mesh, &accessor_reader, semantic, *accessor_idx, None)?;
1059 }
1060 Ok(())
1061 }
1062
1063 pub fn num_meshes(&self) -> usize {
1065 self.root.meshes.len()
1066 }
1067
1068 pub fn num_buffers(&self) -> usize {
1070 self.buffers.len()
1071 }
1072
1073 pub fn extensions_used(&self) -> &[String] {
1075 &self.root.extensions_used
1076 }
1077
1078 pub fn extensions_required(&self) -> &[String] {
1080 &self.root.extensions_required
1081 }
1082}
1083
1084fn read_u32_le(data: &[u8]) -> u32 {
1089 u32::from_le_bytes([data[0], data[1], data[2], data[3]])
1090}
1091
1092fn gltf_type_for_num_components(num_components: u8) -> Result<&'static str> {
1093 match num_components {
1094 1 => Ok("SCALAR"),
1095 2 => Ok("VEC2"),
1096 3 => Ok("VEC3"),
1097 4 => Ok("VEC4"),
1098 _ => Err(GltfError::InvalidGltf(format!(
1099 "Invalid accessor component count: {}",
1100 num_components
1101 ))),
1102 }
1103}
1104
1105fn component_type_for_data_type(data_type: DataType) -> Result<u32> {
1106 match data_type {
1107 DataType::Int8 => Ok(GLTF_COMPONENT_BYTE),
1108 DataType::Uint8 => Ok(GLTF_COMPONENT_UNSIGNED_BYTE),
1109 DataType::Int16 => Ok(GLTF_COMPONENT_SHORT),
1110 DataType::Uint16 => Ok(GLTF_COMPONENT_UNSIGNED_SHORT),
1111 DataType::Uint32 => Ok(GLTF_COMPONENT_UNSIGNED_INT),
1112 DataType::Float32 => Ok(GLTF_COMPONENT_FLOAT),
1113 _ => Err(GltfError::Unsupported(format!(
1114 "Unsupported Draco attribute data type for glTF: {:?}",
1115 data_type
1116 ))),
1117 }
1118}
1119
1120fn validate_root_metadata(root: &GltfRoot) -> Result<()> {
1121 if root.asset.version != "2.0" {
1122 return Err(GltfError::Unsupported(format!(
1123 "Unsupported glTF asset version: {}",
1124 root.asset.version
1125 )));
1126 }
1127 if let Some(min_version) = &root.asset.min_version {
1128 if min_version != "2.0" {
1129 return Err(GltfError::Unsupported(format!(
1130 "Unsupported glTF minimum version: {}",
1131 min_version
1132 )));
1133 }
1134 }
1135
1136 for required in &root.extensions_required {
1142 if !root.extensions_used.iter().any(|used| used == required) {
1143 return Err(GltfError::InvalidGltf(format!(
1144 "Required extension {} is not listed in extensionsUsed",
1145 required
1146 )));
1147 }
1148 }
1149
1150 let mut has_draco_primitive = false;
1151 for mesh in &root.meshes {
1152 for primitive in &mesh.primitives {
1153 if primitive
1154 .extensions
1155 .as_ref()
1156 .and_then(|ext| ext.khr_draco_mesh_compression.as_ref())
1157 .is_some()
1158 {
1159 has_draco_primitive = true;
1160 }
1161 }
1162 }
1163 if has_draco_primitive
1164 && !root
1165 .extensions_used
1166 .iter()
1167 .any(|used| used == KHR_DRACO_MESH_COMPRESSION)
1168 {
1169 return Err(GltfError::InvalidGltf(format!(
1170 "Primitive uses {} but extensionsUsed does not list it",
1171 KHR_DRACO_MESH_COMPRESSION
1172 )));
1173 }
1174
1175 Ok(())
1176}
1177
1178fn reject_unsupported_features(root: &GltfRoot) -> Result<()> {
1185 for required in &root.extensions_required {
1189 if required != KHR_DRACO_MESH_COMPRESSION {
1190 return Err(GltfError::Unsupported(format!(
1191 "Unsupported required extension: {}",
1192 required
1193 )));
1194 }
1195 }
1196
1197 for (mesh_idx, mesh) in root.meshes.iter().enumerate() {
1198 for (prim_idx, primitive) in mesh.primitives.iter().enumerate() {
1199 if !primitive.targets.is_empty() {
1200 return Err(GltfError::Unsupported(format!(
1201 "Morph targets are not supported on primitive {}:{}",
1202 mesh_idx, prim_idx
1203 )));
1204 }
1205 }
1206 }
1207
1208 if !root.skins.is_empty() {
1209 return Err(GltfError::Unsupported("Skins are not supported".into()));
1210 }
1211 if root.nodes.iter().any(|node| node.skin.is_some()) {
1212 return Err(GltfError::Unsupported(
1213 "Skinned nodes are not supported".into(),
1214 ));
1215 }
1216 if !root.animations.is_empty() {
1217 return Err(GltfError::Unsupported(
1218 "Animations are not supported".into(),
1219 ));
1220 }
1221
1222 Ok(())
1223}
1224
1225fn parse_glb_chunks(data: &[u8]) -> Result<GlbChunks<'_>> {
1226 if data.len() < 12 {
1227 return Err(GltfError::InvalidGlb(
1228 "File too small for GLB header".into(),
1229 ));
1230 }
1231
1232 let magic = read_u32_le(&data[0..4]);
1233 let version = read_u32_le(&data[4..8]);
1234 let length = read_u32_le(&data[8..12]) as usize;
1235
1236 if magic != GLB_MAGIC {
1237 return Err(GltfError::InvalidGlb("Invalid GLB magic".into()));
1238 }
1239 if version != GLB_VERSION {
1240 return Err(GltfError::InvalidGlb(format!(
1241 "Unsupported GLB version: {}",
1242 version
1243 )));
1244 }
1245 if length > data.len() {
1246 return Err(GltfError::InvalidGlb("File truncated".into()));
1247 }
1248 if length != data.len() {
1249 return Err(GltfError::InvalidGlb(
1250 "GLB header length does not match file length".into(),
1251 ));
1252 }
1253
1254 let mut offset = 12;
1255 let mut json_chunk: Option<&[u8]> = None;
1256 let mut bin_chunk: Option<&[u8]> = None;
1257 let mut chunk_index = 0usize;
1258 let mut seen_bin = false;
1259
1260 while offset + 8 <= length {
1261 let chunk_length = read_u32_le(&data[offset..offset + 4]) as usize;
1262 let chunk_type = read_u32_le(&data[offset + 4..offset + 8]);
1263 offset += 8;
1264
1265 if !chunk_length.is_multiple_of(4) {
1266 return Err(GltfError::InvalidGlb(
1267 "GLB chunk length is not 4-byte aligned".into(),
1268 ));
1269 }
1270 if offset + chunk_length > length {
1271 return Err(GltfError::InvalidGlb("Chunk extends past file end".into()));
1272 }
1273
1274 let chunk_data = &data[offset..offset + chunk_length];
1275 offset += chunk_length;
1276
1277 match chunk_type {
1278 GLB_CHUNK_JSON => {
1279 if chunk_index != 0 {
1280 return Err(GltfError::InvalidGlb(
1281 "JSON chunk must be the first GLB chunk".into(),
1282 ));
1283 }
1284 if json_chunk.is_some() {
1285 return Err(GltfError::InvalidGlb("Duplicate JSON chunk".into()));
1286 }
1287 json_chunk = Some(chunk_data);
1288 }
1289 GLB_CHUNK_BIN => {
1290 if chunk_index == 0 {
1291 return Err(GltfError::InvalidGlb(
1292 "BIN chunk must not precede JSON chunk".into(),
1293 ));
1294 }
1295 if seen_bin {
1296 return Err(GltfError::InvalidGlb("Duplicate BIN chunk".into()));
1297 }
1298 if chunk_index != 1 {
1299 return Err(GltfError::InvalidGlb(
1300 "BIN chunk must be the second GLB chunk".into(),
1301 ));
1302 }
1303 seen_bin = true;
1304 bin_chunk = Some(chunk_data);
1305 }
1306 _ => {}
1307 }
1308 chunk_index += 1;
1309 }
1310
1311 if offset != length {
1312 return Err(GltfError::InvalidGlb(
1313 "GLB chunk table ended on a partial header".into(),
1314 ));
1315 }
1316
1317 Ok(GlbChunks {
1318 json: json_chunk.ok_or_else(|| GltfError::InvalidGlb("No JSON chunk".into()))?,
1319 bin: bin_chunk,
1320 })
1321}
1322
1323fn load_buffers(
1324 root: &GltfRoot,
1325 is_glb: bool,
1326 glb_bin_chunk: Option<&[u8]>,
1327 base_path: Option<&Path>,
1328) -> Result<Vec<Vec<u8>>> {
1329 let mut buffers = Vec::with_capacity(root.buffers.len());
1330 for (i, buffer) in root.buffers.iter().enumerate() {
1331 buffers.push(load_buffer(i, buffer, is_glb, glb_bin_chunk, base_path)?);
1332 }
1333 Ok(buffers)
1334}
1335
1336fn load_buffer(
1337 index: usize,
1338 buffer: &Buffer,
1339 is_glb: bool,
1340 glb_bin_chunk: Option<&[u8]>,
1341 base_path: Option<&Path>,
1342) -> Result<Vec<u8>> {
1343 if let Some(uri) = &buffer.uri {
1344 let data = if uri.starts_with("data:") {
1345 decode_data_uri(uri)?
1346 } else if let Some(base) = base_path {
1347 fs::read(base.join(uri))?
1348 } else {
1349 return Err(GltfError::Unsupported(
1354 "External buffer URIs require a base path; cannot resolve from in-memory bytes"
1355 .into(),
1356 ));
1357 };
1358 return trim_buffer_to_declared_length(index, buffer, data, false);
1359 }
1360
1361 if is_glb {
1362 if index == 0 {
1363 let bin = glb_bin_chunk.ok_or_else(|| {
1364 GltfError::InvalidGlb("Buffer 0 has no URI but no BIN chunk present".into())
1365 })?;
1366 return trim_buffer_to_declared_length(index, buffer, bin.to_vec(), true);
1367 }
1368 return Err(GltfError::InvalidGlb(format!(
1369 "Buffer {} has no URI and is not buffer 0",
1370 index
1371 )));
1372 }
1373
1374 Err(GltfError::InvalidGltf(
1375 "Buffer without URI in non-GLB file".into(),
1376 ))
1377}
1378
1379fn trim_buffer_to_declared_length(
1380 index: usize,
1381 buffer: &Buffer,
1382 data: Vec<u8>,
1383 is_glb_bin: bool,
1384) -> Result<Vec<u8>> {
1385 if data.len() < buffer.byte_length {
1386 return Err(GltfError::InvalidGltf(format!(
1387 "Buffer {} byteLength {} exceeds resource length {}",
1388 index,
1389 buffer.byte_length,
1390 data.len()
1391 )));
1392 }
1393 if is_glb_bin && data.len() > buffer.byte_length + 3 {
1394 return Err(GltfError::InvalidGlb(format!(
1395 "GLB BIN chunk length {} is more than 3 bytes larger than buffer[0].byteLength {}",
1396 data.len(),
1397 buffer.byte_length
1398 )));
1399 }
1400 Ok(data[..buffer.byte_length].to_vec())
1401}
1402
1403fn accessor_num_components(accessor_type: &str) -> Result<u8> {
1404 match accessor_type {
1405 "SCALAR" => Ok(1),
1406 "VEC2" => Ok(2),
1407 "VEC3" => Ok(3),
1408 "VEC4" => Ok(4),
1409 _ => Err(GltfError::Unsupported(format!(
1410 "Unsupported accessor type: {}",
1411 accessor_type
1412 ))),
1413 }
1414}
1415
1416fn data_type_for_component_type(component_type: u32) -> Result<DataType> {
1417 match component_type {
1418 GLTF_COMPONENT_BYTE => Ok(DataType::Int8),
1419 GLTF_COMPONENT_UNSIGNED_BYTE => Ok(DataType::Uint8),
1420 GLTF_COMPONENT_SHORT => Ok(DataType::Int16),
1421 GLTF_COMPONENT_UNSIGNED_SHORT => Ok(DataType::Uint16),
1422 GLTF_COMPONENT_UNSIGNED_INT => Ok(DataType::Uint32),
1423 GLTF_COMPONENT_FLOAT => Ok(DataType::Float32),
1424 _ => Err(GltfError::Unsupported(format!(
1425 "Unsupported component type: {}",
1426 component_type
1427 ))),
1428 }
1429}
1430
1431fn decode_data_uri(uri: &str) -> Result<Vec<u8>> {
1432 let comma_pos = uri
1434 .find(',')
1435 .ok_or_else(|| GltfError::InvalidGltf("Invalid data URI: no comma".into()))?;
1436
1437 let header = &uri[5..comma_pos]; let data = &uri[comma_pos + 1..];
1439
1440 if header.contains(";base64") {
1441 decode_base64(data)
1442 } else {
1443 Ok(percent_decode(data))
1445 }
1446}
1447
1448impl crate::traits::Reader for GltfReader {
1451 fn open<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
1452 GltfReader::open(path).map_err(|e| std::io::Error::other(e.to_string()))
1453 }
1454
1455 fn read_meshes(&mut self) -> std::io::Result<Vec<draco_core::mesh::Mesh>> {
1456 self.decode_all_meshes()
1457 .map_err(|e| std::io::Error::other(e.to_string()))
1458 }
1459}
1460
1461impl ReadFromBytes for GltfReader {
1462 fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
1463 GltfReader::from_bytes(bytes).map_err(|e| io::Error::other(e.to_string()))
1464 }
1465}
1466
1467impl GltfReader {
1468 pub fn decode_all_meshes(&self) -> Result<Vec<Mesh>> {
1470 let mut result = Vec::new();
1471
1472 for (mesh_idx, gltf_mesh) in self.root.meshes.iter().enumerate() {
1473 for (prim_idx, primitive) in gltf_mesh.primitives.iter().enumerate() {
1474 let mesh = self.decode_primitive_mesh(mesh_idx, gltf_mesh, prim_idx, primitive)?;
1475 result.push(mesh);
1476 }
1477 }
1478
1479 Ok(result)
1480 }
1481
1482 fn compute_node_transform(node: &GltfNode) -> Option<crate::scene::Transform> {
1484 if let Some(m) = &node.matrix {
1485 Some(crate::scene::Transform {
1487 matrix: [
1488 [m[0], m[4], m[8], m[12]],
1489 [m[1], m[5], m[9], m[13]],
1490 [m[2], m[6], m[10], m[14]],
1491 [m[3], m[7], m[11], m[15]],
1492 ],
1493 })
1494 } else if node.translation.is_some() || node.rotation.is_some() || node.scale.is_some() {
1495 let t = node.translation.unwrap_or([0.0, 0.0, 0.0]);
1497 let r = node.rotation.unwrap_or([0.0, 0.0, 0.0, 1.0]); let s = node.scale.unwrap_or([1.0, 1.0, 1.0]);
1499
1500 let (qx, qy, qz, qw) = (r[0], r[1], r[2], r[3]);
1502 let xx = qx * qx;
1503 let yy = qy * qy;
1504 let zz = qz * qz;
1505 let xy = qx * qy;
1506 let xz = qx * qz;
1507 let yz = qy * qz;
1508 let wx = qw * qx;
1509 let wy = qw * qy;
1510 let wz = qw * qz;
1511
1512 let rot = [
1514 [1.0 - 2.0 * (yy + zz), 2.0 * (xy - wz), 2.0 * (xz + wy)],
1515 [2.0 * (xy + wz), 1.0 - 2.0 * (xx + zz), 2.0 * (yz - wx)],
1516 [2.0 * (xz - wy), 2.0 * (yz + wx), 1.0 - 2.0 * (xx + yy)],
1517 ];
1518
1519 Some(crate::scene::Transform {
1521 matrix: [
1522 [rot[0][0] * s[0], rot[0][1] * s[1], rot[0][2] * s[2], t[0]],
1523 [rot[1][0] * s[0], rot[1][1] * s[1], rot[1][2] * s[2], t[1]],
1524 [rot[2][0] * s[0], rot[2][1] * s[1], rot[2][2] * s[2], t[2]],
1525 [0.0, 0.0, 0.0, 1.0],
1526 ],
1527 })
1528 } else {
1529 None
1530 }
1531 }
1532
1533 fn build_scene_node(
1535 &self,
1536 node_idx: usize,
1537 visited: &mut Vec<bool>,
1538 ) -> Result<crate::scene::SceneNode> {
1539 if node_idx >= self.root.nodes.len() {
1540 return Err(GltfError::InvalidGltf(format!(
1541 "Invalid node index: {}",
1542 node_idx
1543 )));
1544 }
1545
1546 if visited[node_idx] {
1548 return Err(GltfError::InvalidGltf(format!(
1549 "Cycle detected at node {}",
1550 node_idx
1551 )));
1552 }
1553 visited[node_idx] = true;
1554
1555 let gltf_node = &self.root.nodes[node_idx];
1556
1557 let mut scene_node = crate::scene::SceneNode::new(gltf_node.name.clone());
1558 scene_node.transform = Self::compute_node_transform(gltf_node);
1559
1560 if let Some(mesh_idx) = gltf_node.mesh {
1562 if let Some(gltf_mesh) = self.root.meshes.get(mesh_idx) {
1563 for (prim_idx, primitive) in gltf_mesh.primitives.iter().enumerate() {
1564 let mesh =
1565 self.decode_primitive_mesh(mesh_idx, gltf_mesh, prim_idx, primitive)?;
1566
1567 let mesh_instance_name = if gltf_mesh.primitives.len() > 1 {
1568 gltf_mesh
1569 .name
1570 .as_ref()
1571 .map(|n| format!("{}_{}", n, prim_idx))
1572 } else {
1573 gltf_mesh.name.clone()
1574 };
1575
1576 scene_node.mesh_instances.push(crate::scene::MeshInstance {
1577 name: mesh_instance_name,
1578 mesh,
1579 transform: None, });
1581 }
1582 }
1583 }
1584
1585 for &child_idx in &gltf_node.children {
1587 let child_node = self.build_scene_node(child_idx, visited)?;
1588 scene_node.children.push(child_node);
1589 }
1590
1591 Ok(scene_node)
1592 }
1593
1594 fn root_node_indices_without_scenes(&self) -> Vec<usize> {
1595 let mut is_child = vec![false; self.root.nodes.len()];
1596 for node in &self.root.nodes {
1597 for &child_idx in &node.children {
1598 if child_idx < is_child.len() {
1599 is_child[child_idx] = true;
1600 }
1601 }
1602 }
1603
1604 (0..self.root.nodes.len())
1605 .filter(|&i| !is_child[i])
1606 .collect()
1607 }
1608
1609 fn build_scene_from_roots(
1610 &self,
1611 name: Option<String>,
1612 root_node_indices: &[usize],
1613 ) -> Result<crate::scene::Scene> {
1614 let mut visited = vec![false; self.root.nodes.len()];
1615 let mut root_nodes = Vec::with_capacity(root_node_indices.len());
1616 for &node_idx in root_node_indices {
1617 root_nodes.push(self.build_scene_node(node_idx, &mut visited)?);
1618 }
1619
1620 Ok(crate::scene::Scene { name, root_nodes })
1621 }
1622
1623 fn read_scene_result(&self) -> Result<crate::scene::Scene> {
1624 let scene_idx = self.root.scene.or({
1625 if self.root.scenes.is_empty() {
1626 None
1627 } else {
1628 Some(0)
1629 }
1630 });
1631
1632 if let Some(idx) = scene_idx {
1633 let gltf_scene =
1634 self.root.scenes.get(idx).ok_or_else(|| {
1635 GltfError::InvalidGltf(format!("Invalid scene index: {}", idx))
1636 })?;
1637 self.build_scene_from_roots(gltf_scene.name.clone(), &gltf_scene.nodes)
1638 } else {
1639 let roots = self.root_node_indices_without_scenes();
1640 self.build_scene_from_roots(None, &roots)
1641 }
1642 }
1643
1644 fn read_scenes_result(&self) -> Result<Vec<crate::scene::Scene>> {
1645 if self.root.scenes.is_empty() {
1646 let roots = self.root_node_indices_without_scenes();
1647 return self
1648 .build_scene_from_roots(None, &roots)
1649 .map(|scene| vec![scene]);
1650 }
1651
1652 self.root
1653 .scenes
1654 .iter()
1655 .map(|scene| self.build_scene_from_roots(scene.name.clone(), &scene.nodes))
1656 .collect()
1657 }
1658}
1659
1660impl crate::scene::SceneReader for GltfReader {
1661 fn read_scene(&mut self) -> std::io::Result<crate::scene::Scene> {
1662 self.read_scene_result()
1663 .map_err(|e| std::io::Error::other(e.to_string()))
1664 }
1665
1666 fn read_scenes(&mut self) -> std::io::Result<Vec<crate::scene::Scene>> {
1667 self.read_scenes_result()
1668 .map_err(|e| std::io::Error::other(e.to_string()))
1669 }
1670}
1671fn decode_base64(input: &str) -> Result<Vec<u8>> {
1672 const DECODE_TABLE: [i8; 128] = [
1674 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1675 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
1676 -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
1677 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1,
1678 -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
1679 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
1680 ];
1681
1682 let input: Vec<u8> = input
1683 .bytes()
1684 .filter(|&b| b != b'\n' && b != b'\r')
1685 .collect();
1686 let mut output = Vec::with_capacity(input.len() * 3 / 4);
1687
1688 let chunks = input.chunks(4);
1689 for chunk in chunks {
1690 if chunk.is_empty() {
1691 break;
1692 }
1693
1694 let mut buf = [0u8; 4];
1695 let mut valid_count = 0;
1696
1697 for (i, &byte) in chunk.iter().enumerate() {
1698 if byte == b'=' {
1699 buf[i] = 0;
1700 } else if byte < 128 {
1701 let val = DECODE_TABLE[byte as usize];
1702 if val < 0 {
1703 return Err(GltfError::InvalidGltf("Invalid base64 character".into()));
1704 }
1705 buf[i] = val as u8;
1706 valid_count = i + 1;
1707 } else {
1708 return Err(GltfError::InvalidGltf("Invalid base64 character".into()));
1709 }
1710 }
1711
1712 for item in buf.iter_mut().skip(chunk.len()) {
1714 *item = 0;
1715 }
1716
1717 let n = ((buf[0] as u32) << 18)
1718 | ((buf[1] as u32) << 12)
1719 | ((buf[2] as u32) << 6)
1720 | (buf[3] as u32);
1721
1722 output.push((n >> 16) as u8);
1723 if valid_count > 2 {
1724 output.push((n >> 8) as u8);
1725 }
1726 if valid_count > 3 {
1727 output.push(n as u8);
1728 }
1729 }
1730
1731 Ok(output)
1732}
1733
1734fn percent_decode(input: &str) -> Vec<u8> {
1735 let mut output = Vec::with_capacity(input.len());
1736 let bytes = input.as_bytes();
1737 let mut i = 0;
1738
1739 while i < bytes.len() {
1740 if bytes[i] == b'%' && i + 2 < bytes.len() {
1741 if let (Some(h), Some(l)) = (hex_digit(bytes[i + 1]), hex_digit(bytes[i + 2])) {
1742 output.push((h << 4) | l);
1743 i += 3;
1744 continue;
1745 }
1746 }
1747 output.push(bytes[i]);
1748 i += 1;
1749 }
1750
1751 output
1752}
1753
1754fn hex_digit(b: u8) -> Option<u8> {
1755 match b {
1756 b'0'..=b'9' => Some(b - b'0'),
1757 b'a'..=b'f' => Some(b - b'a' + 10),
1758 b'A'..=b'F' => Some(b - b'A' + 10),
1759 _ => None,
1760 }
1761}
1762
1763#[cfg(test)]
1768mod tests {
1769 use super::*;
1770 use draco_core::draco_types::DataType;
1771 use draco_core::geometry_attribute::GeometryAttributeType;
1772 #[cfg(feature = "gltf-writer")]
1773 use draco_core::geometry_attribute::PointAttribute;
1774 use draco_core::mesh::Mesh;
1775 use tempfile::tempdir;
1776
1777 fn build_glb(json: &str) -> Vec<u8> {
1778 let mut json_bytes = json.as_bytes().to_vec();
1779 while !json_bytes.len().is_multiple_of(4) {
1780 json_bytes.push(b' ');
1781 }
1782
1783 let total_len = 12 + 8 + json_bytes.len();
1784 let mut glb = Vec::with_capacity(total_len);
1785 glb.extend_from_slice(&GLB_MAGIC.to_le_bytes());
1786 glb.extend_from_slice(&GLB_VERSION.to_le_bytes());
1787 glb.extend_from_slice(&(total_len as u32).to_le_bytes());
1788 glb.extend_from_slice(&(json_bytes.len() as u32).to_le_bytes());
1789 glb.extend_from_slice(&GLB_CHUNK_JSON.to_le_bytes());
1790 glb.extend_from_slice(&json_bytes);
1791 glb
1792 }
1793
1794 fn build_glb_chunks(chunks: &[(u32, Vec<u8>)]) -> Vec<u8> {
1795 let total_len = 12 + chunks.iter().map(|(_, data)| 8 + data.len()).sum::<usize>();
1796 let mut glb = Vec::with_capacity(total_len);
1797 glb.extend_from_slice(&GLB_MAGIC.to_le_bytes());
1798 glb.extend_from_slice(&GLB_VERSION.to_le_bytes());
1799 glb.extend_from_slice(&(total_len as u32).to_le_bytes());
1800 for (chunk_type, data) in chunks {
1801 glb.extend_from_slice(&(data.len() as u32).to_le_bytes());
1802 glb.extend_from_slice(&chunk_type.to_le_bytes());
1803 glb.extend_from_slice(data);
1804 }
1805 glb
1806 }
1807
1808 fn padded_json(json: &str) -> Vec<u8> {
1809 let mut bytes = json.as_bytes().to_vec();
1810 while !bytes.len().is_multiple_of(4) {
1811 bytes.push(b' ');
1812 }
1813 bytes
1814 }
1815
1816 fn padded_bin(data: &[u8]) -> Vec<u8> {
1817 let mut bytes = data.to_vec();
1818 while !bytes.len().is_multiple_of(4) {
1819 bytes.push(0);
1820 }
1821 bytes
1822 }
1823
1824 fn triangle_positions() -> Vec<u8> {
1825 [
1826 0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0,
1829 ]
1830 .into_iter()
1831 .flat_map(f32::to_le_bytes)
1832 .collect()
1833 }
1834
1835 #[cfg(feature = "gltf-writer")]
1836 fn triangle_mesh() -> Mesh {
1837 let mut mesh = Mesh::new();
1838 mesh.set_num_points(3);
1839 mesh.add_face([
1840 draco_core::geometry_indices::PointIndex(0),
1841 draco_core::geometry_indices::PointIndex(1),
1842 draco_core::geometry_indices::PointIndex(2),
1843 ]);
1844
1845 let mut positions = PointAttribute::new();
1846 positions.init(
1847 GeometryAttributeType::Position,
1848 3,
1849 DataType::Float32,
1850 false,
1851 3,
1852 );
1853 positions.buffer_mut().write(0, &triangle_positions());
1854 mesh.add_attribute(positions);
1855 mesh
1856 }
1857
1858 #[cfg(feature = "gltf-writer")]
1859 fn writer_gltf_json_value() -> serde_json::Value {
1860 let mut writer = crate::gltf_writer::GltfWriter::new();
1861 writer
1862 .add_draco_mesh(&triangle_mesh(), Some("triangle"), None)
1863 .unwrap();
1864 serde_json::from_str(&writer.to_gltf_embedded().unwrap()).unwrap()
1865 }
1866
1867 fn read_attribute_bytes(mesh: &Mesh, attribute_type: GeometryAttributeType) -> Vec<u8> {
1868 mesh.named_attribute(attribute_type)
1869 .expect("missing attribute")
1870 .buffer()
1871 .data()
1872 .to_vec()
1873 }
1874
1875 #[test]
1876 fn test_base64_decode() {
1877 assert_eq!(decode_base64("SGVsbG8=").unwrap(), b"Hello");
1878 assert_eq!(decode_base64("SGVsbG8gV29ybGQ=").unwrap(), b"Hello World");
1879 assert_eq!(decode_base64("YQ==").unwrap(), b"a");
1880 assert_eq!(decode_base64("YWI=").unwrap(), b"ab");
1881 assert_eq!(decode_base64("YWJj").unwrap(), b"abc");
1882 }
1883
1884 #[test]
1885 fn test_percent_decode() {
1886 assert_eq!(percent_decode("Hello%20World"), b"Hello World");
1887 assert_eq!(percent_decode("%2F"), b"/");
1888 assert_eq!(percent_decode("test"), b"test");
1889 }
1890
1891 #[test]
1892 fn test_glb_magic() {
1893 let magic_bytes = b"glTF";
1896 let magic = u32::from_le_bytes(*magic_bytes);
1897 assert_eq!(magic, GLB_MAGIC);
1898 }
1899
1900 #[test]
1901 fn test_minimal_gltf_json() {
1902 let json = r#"{
1903 "asset": {"version": "2.0"},
1904 "meshes": [],
1905 "buffers": [],
1906 "bufferViews": [],
1907 "accessors": []
1908 }"#;
1909
1910 let root: GltfRoot = serde_json::from_str(json).unwrap();
1911 assert!(root.meshes.is_empty());
1912 assert!(root.buffers.is_empty());
1913 }
1914
1915 #[test]
1916 fn test_read_scenes_returns_all_gltf_scenes() {
1917 use crate::scene::SceneReader;
1918
1919 let json = r#"{
1920 "asset": {"version": "2.0"},
1921 "scene": 1,
1922 "scenes": [
1923 {"name": "Preview", "nodes": [0]},
1924 {"name": "Full", "nodes": [1, 2]}
1925 ],
1926 "nodes": [
1927 {"name": "PreviewRoot"},
1928 {"name": "FullRootA"},
1929 {"name": "FullRootB"}
1930 ]
1931 }"#;
1932
1933 let mut reader = GltfReader::from_gltf(json.as_bytes(), None).unwrap();
1934 let default_scene = reader.read_scene().unwrap();
1935 assert_eq!(default_scene.name, Some("Full".to_string()));
1936 assert_eq!(default_scene.root_nodes.len(), 2);
1937 assert_eq!(
1938 default_scene.root_nodes[0].name,
1939 Some("FullRootA".to_string())
1940 );
1941
1942 let scenes = reader.read_scenes().unwrap();
1943 assert_eq!(scenes.len(), 2);
1944 assert_eq!(scenes[0].name, Some("Preview".to_string()));
1945 assert_eq!(scenes[0].root_nodes.len(), 1);
1946 assert_eq!(
1947 scenes[0].root_nodes[0].name,
1948 Some("PreviewRoot".to_string())
1949 );
1950 assert_eq!(scenes[1].name, Some("Full".to_string()));
1951 assert_eq!(scenes[1].root_nodes.len(), 2);
1952 }
1953
1954 #[test]
1955 fn test_gltf_with_draco_extension() {
1956 let json = r#"{
1957 "asset": {"version": "2.0"},
1958 "extensionsUsed": ["KHR_draco_mesh_compression"],
1959 "meshes": [{
1960 "name": "TestMesh",
1961 "primitives": [{
1962 "attributes": {"POSITION": 0},
1963 "extensions": {
1964 "KHR_draco_mesh_compression": {
1965 "bufferView": 0,
1966 "attributes": {"POSITION": 0}
1967 }
1968 }
1969 }]
1970 }],
1971 "buffers": [{"byteLength": 3, "uri": "data:application/octet-stream;base64,AAAA"}],
1972 "bufferViews": [{"buffer": 0, "byteLength": 3}],
1973 "accessors": []
1974 }"#;
1975
1976 let reader = GltfReader::from_gltf(json.as_bytes(), None).unwrap();
1977 assert!(reader.has_draco_extension());
1978 assert_eq!(reader.num_meshes(), 1);
1979
1980 let primitives = reader.draco_primitives();
1981 assert_eq!(primitives.len(), 1);
1982 assert_eq!(primitives[0].mesh_name, Some("TestMesh".to_string()));
1983 assert_eq!(primitives[0].buffer_view, 0);
1984 }
1985
1986 #[test]
1987 fn test_glb_rejects_header_length_mismatch() {
1988 let mut glb = build_glb(r#"{"asset":{"version":"2.0"}}"#);
1989 let shorter = (glb.len() as u32 - 1).to_le_bytes();
1990 glb[8..12].copy_from_slice(&shorter);
1991
1992 assert!(matches!(
1993 GltfReader::from_glb(&glb),
1994 Err(GltfError::InvalidGlb(_))
1995 ));
1996 }
1997
1998 #[test]
1999 fn test_glb_rejects_extra_trailing_bytes() {
2000 let mut glb = build_glb(r#"{"asset":{"version":"2.0"}}"#);
2001 glb.push(0);
2002
2003 assert!(matches!(
2004 GltfReader::from_glb(&glb),
2005 Err(GltfError::InvalidGlb(_))
2006 ));
2007 }
2008
2009 #[test]
2010 fn test_glb_rejects_json_not_first_duplicate_bin_and_unaligned_chunk() {
2011 let json = padded_json(r#"{"asset":{"version":"2.0"}}"#);
2012 let bin = padded_bin(&[1, 2, 3, 4]);
2013
2014 let bin_first =
2015 build_glb_chunks(&[(GLB_CHUNK_BIN, bin.clone()), (GLB_CHUNK_JSON, json.clone())]);
2016 assert!(matches!(
2017 GltfReader::from_glb(&bin_first),
2018 Err(GltfError::InvalidGlb(_))
2019 ));
2020
2021 let duplicate_bin = build_glb_chunks(&[
2022 (GLB_CHUNK_JSON, json),
2023 (GLB_CHUNK_BIN, bin.clone()),
2024 (GLB_CHUNK_BIN, bin),
2025 ]);
2026 assert!(matches!(
2027 GltfReader::from_glb(&duplicate_bin),
2028 Err(GltfError::InvalidGlb(_))
2029 ));
2030
2031 let unaligned = build_glb_chunks(&[(GLB_CHUNK_JSON, b"{}".to_vec())]);
2032 assert!(matches!(
2033 GltfReader::from_glb(&unaligned),
2034 Err(GltfError::InvalidGlb(_))
2035 ));
2036 }
2037
2038 #[test]
2039 fn test_rejects_unknown_required_extension() {
2040 let json = r#"{
2041 "asset": {"version": "2.0"},
2042 "extensionsUsed": ["EXT_required"],
2043 "extensionsRequired": ["EXT_required"]
2044 }"#;
2045
2046 assert!(matches!(
2047 GltfReader::from_gltf(json.as_bytes(), None),
2048 Err(GltfError::Unsupported(_))
2049 ));
2050 }
2051
2052 #[test]
2053 fn test_rejects_draco_primitive_missing_extensions_used() {
2054 let json = r#"{
2055 "asset": {"version": "2.0"},
2056 "meshes": [{
2057 "primitives": [{
2058 "attributes": {"POSITION": 0},
2059 "extensions": {
2060 "KHR_draco_mesh_compression": {
2061 "bufferView": 0,
2062 "attributes": {"POSITION": 0}
2063 }
2064 }
2065 }]
2066 }],
2067 "buffers": [{"byteLength": 3, "uri": "data:application/octet-stream;base64,AAAA"}],
2068 "bufferViews": [{"buffer": 0, "byteLength": 3}],
2069 "accessors": []
2070 }"#;
2071
2072 assert!(matches!(
2073 GltfReader::from_gltf(json.as_bytes(), None),
2074 Err(GltfError::InvalidGltf(_))
2075 ));
2076 }
2077
2078 #[test]
2079 fn test_glb_open_loads_relative_external_buffer() {
2080 let dir = tempdir().unwrap();
2081 let bin_path = dir.path().join("mesh.bin");
2082 std::fs::write(&bin_path, triangle_positions()).unwrap();
2083
2084 let json = r#"{
2085 "asset": {"version": "2.0"},
2086 "buffers": [{"byteLength": 36, "uri": "mesh.bin"}],
2087 "bufferViews": [{"buffer": 0, "byteOffset": 0, "byteLength": 36}],
2088 "accessors": [{
2089 "bufferView": 0,
2090 "componentType": 5126,
2091 "count": 3,
2092 "type": "VEC3"
2093 }],
2094 "meshes": [{
2095 "primitives": [{
2096 "attributes": {"POSITION": 0},
2097 "mode": 4
2098 }]
2099 }]
2100 }"#;
2101 let glb = build_glb(json);
2102 let glb_path = dir.path().join("external.glb");
2103 std::fs::write(&glb_path, &glb).unwrap();
2104
2105 let reader = GltfReader::open(&glb_path).unwrap();
2106 let meshes = reader.decode_all_meshes().unwrap();
2107
2108 assert_eq!(meshes.len(), 1);
2109 assert_eq!(meshes[0].num_points(), 3);
2110 assert_eq!(meshes[0].num_faces(), 1);
2111 assert_eq!(
2112 read_attribute_bytes(&meshes[0], GeometryAttributeType::Position),
2113 triangle_positions()
2114 );
2115 }
2116
2117 #[test]
2118 fn test_from_glb_rejects_external_buffer_without_base_path() {
2119 let json = r#"{
2120 "asset": {"version": "2.0"},
2121 "buffers": [{"byteLength": 36, "uri": "mesh.bin"}],
2122 "bufferViews": [{"buffer": 0, "byteOffset": 0, "byteLength": 36}],
2123 "accessors": [],
2124 "meshes": []
2125 }"#;
2126
2127 let err = match GltfReader::from_glb(&build_glb(json)) {
2128 Ok(_) => panic!("external buffer unexpectedly loaded without a base path"),
2129 Err(err) => err,
2130 };
2131 assert!(matches!(err, GltfError::Unsupported(_)));
2132 }
2133
2134 #[test]
2135 fn test_texcoord_unsigned_short_normalized_vec2() {
2136 let mut bytes = triangle_positions();
2137 let texcoords = [0u16, 0, 65535, 0, 0, 65535];
2138 bytes.extend(texcoords.into_iter().flat_map(u16::to_le_bytes));
2139 let data_uri = format!(
2140 "data:application/octet-stream;base64,{}",
2141 base64_for_test(&bytes)
2142 );
2143 let json = format!(
2144 r#"{{
2145 "asset": {{"version": "2.0"}},
2146 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2147 "bufferViews": [
2148 {{"buffer": 0, "byteOffset": 0, "byteLength": 36}},
2149 {{"buffer": 0, "byteOffset": 36, "byteLength": 12}}
2150 ],
2151 "accessors": [
2152 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}},
2153 {{
2154 "bufferView": 1,
2155 "componentType": 5123,
2156 "normalized": true,
2157 "count": 3,
2158 "type": "VEC2"
2159 }}
2160 ],
2161 "meshes": [{{
2162 "primitives": [{{
2163 "attributes": {{"POSITION": 0, "TEXCOORD_0": 1}},
2164 "mode": 4
2165 }}]
2166 }}]
2167 }}"#,
2168 bytes.len(),
2169 data_uri
2170 );
2171
2172 let mesh = GltfReader::from_gltf(json.as_bytes(), None)
2173 .unwrap()
2174 .decode_all_meshes()
2175 .unwrap()
2176 .remove(0);
2177 let texcoord = mesh
2178 .named_attribute(GeometryAttributeType::TexCoord)
2179 .expect("missing texcoord");
2180
2181 assert_eq!(texcoord.data_type(), DataType::Uint16);
2182 assert!(texcoord.normalized());
2183 assert_eq!(texcoord.num_components(), 2);
2184 assert_eq!(texcoord.buffer().data(), &bytes[36..48]);
2185 }
2186
2187 #[test]
2188 fn test_color_unsigned_byte_normalized_vec3() {
2189 let mut bytes = triangle_positions();
2190 let colors = [255u8, 0, 0, 0, 255, 0, 0, 0, 255];
2191 bytes.extend(colors);
2192 let data_uri = format!(
2193 "data:application/octet-stream;base64,{}",
2194 base64_for_test(&bytes)
2195 );
2196 let json = format!(
2197 r#"{{
2198 "asset": {{"version": "2.0"}},
2199 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2200 "bufferViews": [
2201 {{"buffer": 0, "byteOffset": 0, "byteLength": 36}},
2202 {{"buffer": 0, "byteOffset": 36, "byteLength": 9}}
2203 ],
2204 "accessors": [
2205 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}},
2206 {{
2207 "bufferView": 1,
2208 "componentType": 5121,
2209 "normalized": true,
2210 "count": 3,
2211 "type": "VEC3"
2212 }}
2213 ],
2214 "meshes": [{{
2215 "primitives": [{{
2216 "attributes": {{"POSITION": 0, "COLOR_0": 1}},
2217 "mode": 4
2218 }}]
2219 }}]
2220 }}"#,
2221 bytes.len(),
2222 data_uri
2223 );
2224
2225 let mesh = GltfReader::from_gltf(json.as_bytes(), None)
2226 .unwrap()
2227 .decode_all_meshes()
2228 .unwrap()
2229 .remove(0);
2230 let color = mesh
2231 .named_attribute(GeometryAttributeType::Color)
2232 .expect("missing color");
2233
2234 assert_eq!(color.data_type(), DataType::Uint8);
2235 assert!(color.normalized());
2236 assert_eq!(color.num_components(), 3);
2237 assert_eq!(color.buffer().data(), &bytes[36..45]);
2238 }
2239
2240 #[test]
2241 fn test_points_primitive_decodes_without_faces() {
2242 let indices = [2u16, 0];
2243 let mut bytes = triangle_positions();
2244 bytes.extend(indices.into_iter().flat_map(u16::to_le_bytes));
2245 let data_uri = format!(
2246 "data:application/octet-stream;base64,{}",
2247 base64_for_test(&bytes)
2248 );
2249 let json = format!(
2250 r#"{{
2251 "asset": {{"version": "2.0"}},
2252 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2253 "bufferViews": [
2254 {{"buffer": 0, "byteOffset": 0, "byteLength": 36}},
2255 {{"buffer": 0, "byteOffset": 36, "byteLength": 4}}
2256 ],
2257 "accessors": [
2258 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}},
2259 {{"bufferView": 1, "componentType": 5123, "count": 2, "type": "SCALAR"}}
2260 ],
2261 "meshes": [{{
2262 "primitives": [{{
2263 "attributes": {{"POSITION": 0}},
2264 "indices": 1,
2265 "mode": 0
2266 }}]
2267 }}]
2268 }}"#,
2269 bytes.len(),
2270 data_uri
2271 );
2272
2273 let mesh = GltfReader::from_gltf(json.as_bytes(), None)
2274 .unwrap()
2275 .decode_all_meshes()
2276 .unwrap()
2277 .remove(0);
2278
2279 assert_eq!(mesh.num_points(), 2);
2280 assert_eq!(mesh.num_faces(), 0);
2281 let positions = read_attribute_bytes(&mesh, GeometryAttributeType::Position);
2282 assert_eq!(&positions[0..12], &triangle_positions()[24..36]);
2283 assert_eq!(&positions[12..24], &triangle_positions()[0..12]);
2284 }
2285
2286 #[cfg(feature = "gltf-writer")]
2287 #[test]
2288 fn test_writer_glb_roundtrips_through_reader() {
2289 let dir = tempdir().unwrap();
2290 let path = dir.path().join("roundtrip.glb");
2291
2292 let mut writer = crate::gltf_writer::GltfWriter::new();
2293 writer
2294 .add_draco_mesh(&triangle_mesh(), Some("triangle"), None)
2295 .unwrap();
2296 writer.write_glb(&path).unwrap();
2297
2298 let reader = GltfReader::open(&path).unwrap();
2299 let primitives = reader.draco_primitives();
2300 assert_eq!(primitives.len(), 1);
2301 assert_eq!(primitives[0].attributes.get("POSITION"), Some(&0));
2302
2303 let decoded = reader.decode_all_meshes().unwrap().remove(0);
2304 let position = decoded
2305 .named_attribute(GeometryAttributeType::Position)
2306 .expect("missing position");
2307 assert_eq!(position.data_type(), DataType::Float32);
2308 assert_eq!(position.num_components(), 3);
2309 assert_eq!(decoded.num_faces(), 1);
2310 }
2311
2312 #[cfg(feature = "gltf-writer")]
2313 #[test]
2314 fn test_draco_decode_rejects_extension_attribute_not_in_primitive_attributes() {
2315 let mut value = writer_gltf_json_value();
2316 value["meshes"][0]["primitives"][0]["extensions"]["KHR_draco_mesh_compression"]
2317 ["attributes"]["TEXCOORD_0"] = serde_json::json!(0);
2318 let json = serde_json::to_string(&value).unwrap();
2319
2320 let err = GltfReader::from_gltf(json.as_bytes(), None)
2321 .unwrap()
2322 .decode_all_meshes()
2323 .unwrap_err();
2324 assert!(matches!(err, GltfError::InvalidGltf(_)));
2325 }
2326
2327 #[cfg(feature = "gltf-writer")]
2328 #[test]
2329 fn test_draco_decode_rejects_accessor_metadata_mismatch_and_sparse() {
2330 let mut count_mismatch = writer_gltf_json_value();
2331 let pos_accessor = count_mismatch["meshes"][0]["primitives"][0]["attributes"]["POSITION"]
2332 .as_u64()
2333 .unwrap() as usize;
2334 count_mismatch["accessors"][pos_accessor]["count"] = serde_json::json!(99);
2335 let json = serde_json::to_string(&count_mismatch).unwrap();
2336 let err = GltfReader::from_gltf(json.as_bytes(), None)
2337 .unwrap()
2338 .decode_all_meshes()
2339 .unwrap_err();
2340 assert!(matches!(err, GltfError::InvalidGltf(_)));
2341
2342 let mut sparse = writer_gltf_json_value();
2343 let pos_accessor = sparse["meshes"][0]["primitives"][0]["attributes"]["POSITION"]
2344 .as_u64()
2345 .unwrap() as usize;
2346 sparse["accessors"][pos_accessor]["sparse"] = serde_json::json!({
2347 "count": 1,
2348 "indices": {"bufferView": 0, "componentType": 5123},
2349 "values": {"bufferView": 0}
2350 });
2351 let json = serde_json::to_string(&sparse).unwrap();
2352 let err = GltfReader::from_gltf(json.as_bytes(), None)
2353 .unwrap()
2354 .decode_all_meshes()
2355 .unwrap_err();
2356 assert!(matches!(err, GltfError::Unsupported(_)));
2357 }
2358
2359 #[test]
2360 fn test_standard_triangle_rejects_out_of_bounds_indices() {
2361 let indices = [0u16, 1, 3];
2362 let mut bytes = triangle_positions();
2363 bytes.extend(indices.into_iter().flat_map(u16::to_le_bytes));
2364 let data_uri = format!(
2365 "data:application/octet-stream;base64,{}",
2366 base64_for_test(&bytes)
2367 );
2368 let json = format!(
2369 r#"{{
2370 "asset": {{"version": "2.0"}},
2371 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2372 "bufferViews": [
2373 {{"buffer": 0, "byteOffset": 0, "byteLength": 36}},
2374 {{"buffer": 0, "byteOffset": 36, "byteLength": 6}}
2375 ],
2376 "accessors": [
2377 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}},
2378 {{"bufferView": 1, "componentType": 5123, "count": 3, "type": "SCALAR"}}
2379 ],
2380 "meshes": [{{
2381 "primitives": [{{
2382 "attributes": {{"POSITION": 0}},
2383 "indices": 1,
2384 "mode": 4
2385 }}]
2386 }}]
2387 }}"#,
2388 bytes.len(),
2389 data_uri
2390 );
2391
2392 let err = GltfReader::from_gltf(json.as_bytes(), None)
2393 .unwrap()
2394 .decode_all_meshes()
2395 .unwrap_err();
2396 assert!(matches!(err, GltfError::InvalidGltf(_)));
2397 }
2398
2399 #[cfg(feature = "legacy-bitstream-decode")]
2400 #[test]
2401 fn test_draco_legacy_bitstream_data_uri() {
2402 let draco_bytes =
2403 include_bytes!("../../../testdata/legacy_draco/cube_att.mesh_seq.1.1.0.drc");
2404 let data_uri = format!(
2405 "data:application/octet-stream;base64,{}",
2406 base64_for_test(draco_bytes)
2407 );
2408 let json = format!(
2409 r#"{{
2410 "asset": {{"version": "2.0"}},
2411 "extensionsUsed": ["KHR_draco_mesh_compression"],
2412 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2413 "bufferViews": [{{"buffer": 0, "byteOffset": 0, "byteLength": {}}}],
2414 "accessors": [
2415 {{"componentType": 5126, "count": 24, "type": "VEC3"}}
2416 ],
2417 "meshes": [{{
2418 "primitives": [{{
2419 "attributes": {{"POSITION": 0}},
2420 "mode": 4,
2421 "extensions": {{
2422 "KHR_draco_mesh_compression": {{
2423 "bufferView": 0,
2424 "attributes": {{"POSITION": 0}}
2425 }}
2426 }}
2427 }}]
2428 }}]
2429 }}"#,
2430 draco_bytes.len(),
2431 data_uri,
2432 draco_bytes.len()
2433 );
2434
2435 let mesh = GltfReader::from_gltf(json.as_bytes(), None)
2436 .unwrap()
2437 .decode_all_draco_meshes()
2438 .unwrap()
2439 .remove(0)
2440 .1;
2441
2442 assert_eq!(mesh.num_faces(), 12);
2443 assert_eq!(mesh.num_points(), 24);
2444 assert_eq!(
2445 mesh.named_attribute(GeometryAttributeType::Position)
2446 .expect("missing position")
2447 .size(),
2448 24
2449 );
2450 }
2451
2452 fn base64_for_test(bytes: &[u8]) -> String {
2453 const TABLE: &[u8; 64] =
2454 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2455 let mut out = String::new();
2456
2457 for chunk in bytes.chunks(3) {
2458 let b0 = chunk[0];
2459 let b1 = *chunk.get(1).unwrap_or(&0);
2460 let b2 = *chunk.get(2).unwrap_or(&0);
2461 let n = ((b0 as u32) << 16) | ((b1 as u32) << 8) | b2 as u32;
2462
2463 out.push(TABLE[((n >> 18) & 0x3f) as usize] as char);
2464 out.push(TABLE[((n >> 12) & 0x3f) as usize] as char);
2465 if chunk.len() > 1 {
2466 out.push(TABLE[((n >> 6) & 0x3f) as usize] as char);
2467 } else {
2468 out.push('=');
2469 }
2470 if chunk.len() > 2 {
2471 out.push(TABLE[(n & 0x3f) as usize] as char);
2472 } else {
2473 out.push('=');
2474 }
2475 }
2476
2477 out
2478 }
2479}