1use std::collections::{BTreeMap, BTreeSet};
33use std::fs;
34use std::io;
35use std::path::Path;
36
37use draco_core::decoder_buffer::DecoderBuffer;
38use draco_core::draco_types::DataType;
39use draco_core::geometry_attribute::PointAttribute;
40use draco_core::mesh::Mesh;
41use draco_core::mesh_decoder::MeshDecoder;
42#[cfg(feature = "point_cloud_decode")]
43use draco_core::point_cloud::PointCloud;
44#[cfg(feature = "point_cloud_decode")]
45use draco_core::point_cloud_decoder::PointCloudDecoder;
46use serde::{Deserialize, Deserializer};
47
48use crate::gltf_container::{
49 parse_gltf_container, resolve_gltf_buffers, resolve_resource_uri, ExternalFilePolicy,
50 FileResourceResolver, GltfBufferReference, GltfContainerFormat, ResourceLimits,
51 ResourceResolver,
52};
53use crate::traits::ReadFromBytes;
54
55use crate::gltf_geometry::{
59 add_named_attribute, component_type_for_data_type, decode_geometry,
60 gltf_type_for_num_components, supported_semantic_spec, validate_semantic_accessor,
61 AccessorSource, DecodedAccessor, GltfError, Result, GLTF_COMPONENT_BYTE, GLTF_COMPONENT_FLOAT,
62 GLTF_COMPONENT_SHORT, GLTF_COMPONENT_UNSIGNED_BYTE, GLTF_COMPONENT_UNSIGNED_INT,
63 GLTF_COMPONENT_UNSIGNED_SHORT, GLTF_MODE_TRIANGLES,
64};
65use crate::gltf_khr_draco::{
66 validate_khr_draco_contract, validate_khr_draco_document, KhrDracoExtensionContract,
67 KhrDracoPrimitiveContract, KHR_DRACO_MESH_COMPRESSION,
68};
69
70#[allow(dead_code)]
75#[derive(Debug, Deserialize)]
76#[serde(rename_all = "camelCase")]
77struct GltfRoot {
78 asset: Asset,
79 #[serde(default)]
80 accessors: Vec<Accessor>,
81 #[serde(default)]
82 buffer_views: Vec<BufferView>,
83 #[serde(default)]
84 buffers: Vec<Buffer>,
85 #[serde(default)]
86 images: Vec<Image>,
87 #[serde(default)]
88 meshes: Vec<GltfMesh>,
89 #[serde(default)]
90 nodes: Vec<GltfNode>,
91 #[serde(default)]
92 scenes: Vec<GltfScene>,
93 #[serde(default)]
94 skins: Vec<Skin>,
95 #[serde(default)]
96 animations: Vec<Animation>,
97 #[serde(default, deserialize_with = "deserialize_present_option")]
99 scene: Option<usize>,
100 #[serde(default)]
101 extensions_used: Vec<String>,
102 #[serde(default)]
103 extensions_required: Vec<String>,
104}
105
106#[allow(dead_code)]
107#[derive(Debug, Deserialize)]
108#[serde(rename_all = "camelCase")]
109struct Asset {
110 version: String,
111 min_version: Option<String>,
112}
113
114fn deserialize_present_option<'de, D, T>(
115 deserializer: D,
116) -> std::result::Result<Option<T>, D::Error>
117where
118 D: Deserializer<'de>,
119 T: Deserialize<'de>,
120{
121 T::deserialize(deserializer).map(Some)
122}
123
124#[allow(dead_code)]
125#[derive(Debug, Deserialize)]
126#[serde(rename_all = "camelCase")]
127struct Skin {
128 #[serde(default, deserialize_with = "deserialize_present_option")]
129 inverse_bind_matrices: Option<usize>,
130 #[serde(default, deserialize_with = "deserialize_present_option")]
131 skeleton: Option<usize>,
132 joints: Vec<usize>,
133}
134
135#[allow(dead_code)]
136#[derive(Debug, Deserialize)]
137struct Animation {
138 channels: Vec<AnimationChannel>,
139 samplers: Vec<AnimationSampler>,
140}
141
142#[allow(dead_code)]
143#[derive(Debug, Deserialize)]
144struct AnimationChannel {
145 sampler: usize,
146 target: AnimationTarget,
147}
148
149#[allow(dead_code)]
150#[derive(Debug, Deserialize)]
151struct AnimationTarget {
152 #[serde(default, deserialize_with = "deserialize_present_option")]
153 node: Option<usize>,
154 path: String,
155}
156
157#[allow(dead_code)]
158#[derive(Debug, Deserialize)]
159struct AnimationSampler {
160 input: usize,
161 output: usize,
162 #[serde(default)]
163 interpolation: Option<String>,
164}
165
166#[allow(dead_code)]
168#[derive(Debug, Deserialize)]
169#[serde(rename_all = "camelCase")]
170struct GltfScene {
171 name: Option<String>,
172 #[serde(default)]
173 nodes: Vec<usize>,
174}
175
176#[allow(dead_code)]
178#[derive(Debug, Deserialize)]
179#[serde(rename_all = "camelCase")]
180struct GltfNode {
181 name: Option<String>,
182 #[serde(default, deserialize_with = "deserialize_present_option")]
184 mesh: Option<usize>,
185 #[serde(default)]
187 children: Vec<usize>,
188 matrix: Option<[f32; 16]>,
190 translation: Option<[f32; 3]>,
192 rotation: Option<[f32; 4]>,
194 scale: Option<[f32; 3]>,
196 #[serde(default, deserialize_with = "deserialize_present_option")]
197 skin: Option<usize>,
198}
199
200#[allow(dead_code)]
201#[derive(Debug, Deserialize)]
202#[serde(rename_all = "camelCase")]
203struct Accessor {
204 #[serde(default, deserialize_with = "deserialize_present_option")]
205 buffer_view: Option<usize>,
206 #[serde(default, deserialize_with = "deserialize_present_option")]
207 byte_offset: Option<usize>,
208 component_type: u32,
209 #[serde(default)]
210 normalized: bool,
211 count: usize,
212 #[serde(rename = "type")]
213 accessor_type: String,
214 #[serde(default)]
215 min: Vec<f64>,
216 #[serde(default)]
217 max: Vec<f64>,
218 #[serde(default, deserialize_with = "deserialize_present_option")]
219 sparse: Option<SparseAccessor>,
220}
221
222#[allow(dead_code)]
223#[derive(Debug, Deserialize)]
224#[serde(rename_all = "camelCase")]
225struct SparseAccessor {
226 count: usize,
227 indices: SparseIndices,
228 values: SparseValues,
229}
230
231#[allow(dead_code)]
232#[derive(Debug, Deserialize)]
233#[serde(rename_all = "camelCase")]
234struct SparseIndices {
235 buffer_view: usize,
236 #[serde(default, deserialize_with = "deserialize_present_option")]
237 byte_offset: Option<usize>,
238 component_type: u32,
239}
240
241#[allow(dead_code)]
242#[derive(Debug, Deserialize)]
243#[serde(rename_all = "camelCase")]
244struct SparseValues {
245 buffer_view: usize,
246 #[serde(default, deserialize_with = "deserialize_present_option")]
247 byte_offset: Option<usize>,
248}
249
250#[allow(dead_code)]
251#[derive(Debug, Deserialize)]
252#[serde(rename_all = "camelCase")]
253struct BufferView {
254 buffer: usize,
255 byte_offset: Option<usize>,
256 byte_length: usize,
257 byte_stride: Option<usize>,
258 target: Option<u32>,
259}
260
261#[allow(dead_code)]
262#[derive(Debug, Deserialize)]
263#[serde(rename_all = "camelCase")]
264struct Buffer {
265 byte_length: usize,
266 #[serde(default, deserialize_with = "deserialize_present_option")]
267 uri: Option<String>,
268}
269
270#[allow(dead_code)]
271#[derive(Debug, Deserialize)]
272#[serde(rename_all = "camelCase")]
273struct Image {
274 #[serde(default, deserialize_with = "deserialize_present_option")]
275 uri: Option<String>,
276 #[serde(default, deserialize_with = "deserialize_present_option")]
277 buffer_view: Option<usize>,
278 #[serde(default, deserialize_with = "deserialize_present_option")]
279 mime_type: Option<String>,
280}
281
282#[derive(Debug, Deserialize)]
283#[serde(rename_all = "camelCase")]
284struct GltfMesh {
285 name: Option<String>,
286 primitives: Vec<Primitive>,
287}
288
289#[allow(dead_code)]
290#[derive(Debug, Deserialize)]
291#[serde(rename_all = "camelCase")]
292struct Primitive {
293 #[serde(default)]
294 attributes: BTreeMap<String, usize>,
295 #[serde(default, deserialize_with = "deserialize_present_option")]
296 indices: Option<usize>,
297 #[serde(default, deserialize_with = "deserialize_present_option")]
298 mode: Option<u32>,
299 #[serde(default, deserialize_with = "deserialize_present_option")]
300 material: Option<usize>,
301 #[serde(default)]
302 targets: Vec<BTreeMap<String, usize>>,
303 #[serde(default, deserialize_with = "deserialize_present_option")]
304 extensions: Option<PrimitiveExtensions>,
305}
306
307#[derive(Debug, Deserialize)]
308#[serde(rename_all = "camelCase")]
309struct PrimitiveExtensions {
310 #[serde(
311 rename = "KHR_draco_mesh_compression",
312 default,
313 deserialize_with = "deserialize_present_option"
314 )]
315 khr_draco_mesh_compression: Option<DracoExtension>,
316}
317
318#[derive(Debug, Deserialize)]
319#[serde(rename_all = "camelCase", deny_unknown_fields)]
320struct DracoExtension {
321 buffer_view: u32,
322 #[serde(default)]
323 attributes: BTreeMap<String, u32>,
324}
325
326pub struct GltfReader {
332 root: GltfRoot,
333 buffers: Vec<Vec<u8>>,
334}
335
336#[derive(Clone, Debug, Default, PartialEq)]
338pub struct GltfDocumentMetadata {
339 pub primitive_names: Vec<Option<String>>,
341 pub nodes: Vec<GltfNodeMetadata>,
342 pub scenes: Vec<GltfSceneMetadata>,
343 pub default_scene: Option<usize>,
344 pub uses_draco: bool,
345 pub external_resource_uris: Vec<String>,
347}
348
349#[derive(Clone, Debug, Default, PartialEq)]
351pub struct GltfNodeMetadata {
352 pub name: Option<String>,
353 pub mesh: Option<usize>,
354 pub translation: Option<[f32; 3]>,
355 pub rotation: Option<[f32; 4]>,
356 pub scale: Option<[f32; 3]>,
357 pub children: Vec<usize>,
358}
359
360#[derive(Clone, Debug, Default, PartialEq, Eq)]
362pub struct GltfSceneMetadata {
363 pub name: Option<String>,
364 pub nodes: Vec<usize>,
365}
366
367struct GltfAccessorReader<'a> {
370 accessors: &'a [Accessor],
371 buffer_views: &'a [BufferView],
372 buffers: &'a [Vec<u8>],
373}
374
375impl AccessorSource for GltfAccessorReader<'_> {
376 fn read_attribute(
377 &self,
378 accessor: usize,
379 expected_types: &[&str],
380 allowed_component_types: &[u32],
381 ) -> Result<DecodedAccessor> {
382 GltfAccessorReader::read_attribute(self, accessor, expected_types, allowed_component_types)
383 }
384
385 fn read_indices(&self, accessor: usize) -> Result<Vec<u32>> {
386 GltfAccessorReader::read_indices(self, accessor)
387 }
388}
389
390impl<'a> GltfAccessorReader<'a> {
391 fn new(root: &'a GltfRoot, buffers: &'a [Vec<u8>]) -> Self {
392 Self {
393 accessors: &root.accessors,
394 buffer_views: &root.buffer_views,
395 buffers,
396 }
397 }
398
399 fn read_attribute(
400 &self,
401 accessor_idx: usize,
402 expected_types: &[&str],
403 allowed_component_types: &[u32],
404 ) -> Result<DecodedAccessor> {
405 let accessor = self.accessor(accessor_idx)?;
406
407 if !expected_types
408 .iter()
409 .any(|expected| accessor.accessor_type == *expected)
410 {
411 return Err(GltfError::InvalidGltf(format!(
412 "Expected one of {:?} accessor, got {}",
413 expected_types, accessor.accessor_type
414 )));
415 }
416
417 if !allowed_component_types.contains(&accessor.component_type) {
418 return Err(GltfError::Unsupported(format!(
419 "Unsupported {} component type: {}",
420 accessor.accessor_type, accessor.component_type
421 )));
422 }
423
424 let num_components = accessor_num_components(&accessor.accessor_type)?;
425 let data_type = data_type_for_component_type(accessor.component_type)?;
426 let component_size = data_type.byte_length();
427 let row_size = (num_components as usize)
428 .checked_mul(component_size)
429 .ok_or_else(|| GltfError::InvalidGltf("Accessor row size overflow".into()))?;
430 let layout = self.accessor_layout(accessor, row_size, component_size, true, "Accessor")?;
431
432 let byte_len = accessor
433 .count
434 .checked_mul(row_size)
435 .ok_or_else(|| GltfError::InvalidGltf("Accessor byte size overflow".into()))?;
436 let mut bytes = Vec::new();
437 bytes
438 .try_reserve_exact(byte_len)
439 .map_err(|_| GltfError::ResourceLimitExceeded("Accessor allocation failed".into()))?;
440 for i in 0..accessor.count {
441 let relative = i
442 .checked_mul(layout.stride)
443 .ok_or_else(|| GltfError::InvalidGltf("Accessor range overflow".into()))?;
444 let offset = layout
445 .start
446 .checked_add(relative)
447 .ok_or_else(|| GltfError::InvalidGltf("Accessor range overflow".into()))?;
448 let end = offset
449 .checked_add(row_size)
450 .filter(|end| *end <= layout.view_end)
451 .ok_or_else(|| {
452 GltfError::InvalidGltf(format!(
453 "{} accessor out of bounds",
454 accessor.accessor_type
455 ))
456 })?;
457 if end > layout.buffer.len() {
458 return Err(GltfError::InvalidGltf(format!(
459 "{} accessor out of bounds",
460 accessor.accessor_type
461 )));
462 }
463 bytes.extend_from_slice(&layout.buffer[offset..end]);
464 }
465
466 DecodedAccessor::new(
467 accessor.count,
468 num_components,
469 data_type,
470 accessor.normalized,
471 bytes,
472 )
473 }
474
475 fn read_indices(&self, accessor_idx: usize) -> Result<Vec<u32>> {
476 let accessor = self.accessor(accessor_idx)?;
477
478 if accessor.accessor_type != "SCALAR" {
479 return Err(GltfError::InvalidGltf(format!(
480 "Expected SCALAR accessor for indices, got {}",
481 accessor.accessor_type
482 )));
483 }
484 if accessor.normalized {
485 return Err(GltfError::InvalidGltf(
486 "Index accessor must not be normalized".into(),
487 ));
488 }
489
490 let component_size = match accessor.component_type {
491 GLTF_COMPONENT_UNSIGNED_BYTE => 1,
492 GLTF_COMPONENT_UNSIGNED_SHORT => 2,
493 GLTF_COMPONENT_UNSIGNED_INT => 4,
494 _ => {
495 return Err(GltfError::Unsupported(format!(
496 "Unsupported index component type: {}",
497 accessor.component_type
498 )));
499 }
500 };
501 let layout = self.accessor_layout(
502 accessor,
503 component_size,
504 component_size,
505 false,
506 "Index accessor",
507 )?;
508 let mut result = Vec::new();
509 result.try_reserve_exact(accessor.count).map_err(|_| {
510 GltfError::ResourceLimitExceeded("Index accessor allocation failed".into())
511 })?;
512
513 for index in 0..accessor.count {
514 let bytes = layout.element(index, component_size, "Index accessor")?;
515 let value = match accessor.component_type {
516 GLTF_COMPONENT_UNSIGNED_BYTE => bytes[0] as u32,
517 GLTF_COMPONENT_UNSIGNED_SHORT => u16::from_le_bytes([bytes[0], bytes[1]]) as u32,
518 GLTF_COMPONENT_UNSIGNED_INT => {
519 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
520 }
521 component_type => {
522 return Err(GltfError::Unsupported(format!(
523 "Unsupported index component type: {component_type}"
524 )));
525 }
526 };
527 result.push(value);
528 }
529
530 Ok(result)
531 }
532
533 fn accessor(&self, accessor_idx: usize) -> Result<&Accessor> {
534 self.accessors.get(accessor_idx).ok_or_else(|| {
535 GltfError::InvalidGltf(format!("Invalid accessor index: {}", accessor_idx))
536 })
537 }
538
539 fn accessor_layout(
540 &self,
541 accessor: &Accessor,
542 element_size: usize,
543 component_size: usize,
544 vertex_attribute: bool,
545 label: &str,
546 ) -> Result<AccessorLayout<'a>> {
547 if accessor.sparse.is_some() {
548 return Err(GltfError::Unsupported(
549 "Sparse accessors are not supported".into(),
550 ));
551 }
552 if accessor.count == 0 {
553 return Err(GltfError::InvalidGltf(format!(
554 "{} count must be greater than zero",
555 label
556 )));
557 }
558
559 let buffer_view_idx = accessor
560 .buffer_view
561 .ok_or_else(|| GltfError::InvalidGltf(format!("{} has no bufferView", label)))?;
562
563 let buffer_view = self.buffer_views.get(buffer_view_idx).ok_or_else(|| {
564 GltfError::InvalidGltf(format!("Invalid bufferView index: {}", buffer_view_idx))
565 })?;
566
567 let buffer = self.buffers.get(buffer_view.buffer).ok_or_else(|| {
568 GltfError::InvalidGltf(format!("Invalid buffer index: {}", buffer_view.buffer))
569 })?;
570
571 let view_offset = buffer_view.byte_offset.unwrap_or(0);
572 let accessor_offset = accessor.byte_offset.unwrap_or(0);
573 if !accessor_offset.is_multiple_of(component_size) {
574 return Err(GltfError::InvalidGltf(format!(
575 "{} byteOffset is not aligned to component size {}",
576 label, component_size
577 )));
578 }
579
580 let start = view_offset
581 .checked_add(accessor_offset)
582 .ok_or_else(|| GltfError::InvalidGltf("Accessor start overflow".into()))?;
583 if start % component_size != 0 {
584 return Err(GltfError::InvalidGltf(format!(
585 "{} absolute byte offset is not aligned to component size {}",
586 label, component_size
587 )));
588 }
589 if !vertex_attribute && buffer_view.byte_stride.is_some() {
590 return Err(GltfError::InvalidGltf(format!(
591 "{} bufferView must not define byteStride",
592 label
593 )));
594 }
595
596 let stride = buffer_view.byte_stride.unwrap_or(element_size);
597
598 if stride < element_size {
599 return Err(GltfError::InvalidGltf(format!(
600 "{} byteStride {} is smaller than element size {}",
601 label, stride, element_size
602 )));
603 }
604 if stride % component_size != 0 {
605 return Err(GltfError::InvalidGltf(format!(
606 "{} byteStride {} is not aligned to component size {}",
607 label, stride, component_size
608 )));
609 }
610 if let Some(byte_stride) = buffer_view.byte_stride {
611 if !(4..=252).contains(&byte_stride) {
612 return Err(GltfError::InvalidGltf(format!(
613 "{} byteStride {} is outside glTF range 4..=252",
614 label, byte_stride
615 )));
616 }
617 if vertex_attribute && byte_stride % 4 != 0 {
618 return Err(GltfError::InvalidGltf(format!(
619 "{} byteStride {} is not 4-byte aligned",
620 label, byte_stride
621 )));
622 }
623 }
624
625 let view_end = view_offset
626 .checked_add(buffer_view.byte_length)
627 .ok_or_else(|| GltfError::InvalidGltf("Buffer view range overflow".into()))?;
628 if start > view_end {
629 return Err(GltfError::InvalidGltf(format!(
630 "{} starts past bufferView end",
631 label
632 )));
633 }
634 let byte_len = stride
635 .checked_mul(accessor.count - 1)
636 .and_then(|prefix| prefix.checked_add(element_size))
637 .ok_or_else(|| GltfError::InvalidGltf("Accessor byte range overflow".into()))?;
638 let accessor_end = start
639 .checked_add(byte_len)
640 .ok_or_else(|| GltfError::InvalidGltf("Accessor byte range overflow".into()))?;
641 if accessor_end > view_end {
642 return Err(GltfError::InvalidGltf(format!(
643 "{} accessor does not fit its bufferView",
644 label
645 )));
646 }
647 if view_end > buffer.len() {
648 return Err(GltfError::InvalidGltf(
649 "Buffer view extends past buffer end".into(),
650 ));
651 }
652
653 Ok(AccessorLayout {
654 buffer,
655 start,
656 stride,
657 view_end,
658 })
659 }
660}
661
662struct AccessorLayout<'a> {
663 buffer: &'a [u8],
664 start: usize,
665 stride: usize,
666 view_end: usize,
667}
668
669impl<'a> AccessorLayout<'a> {
670 fn element(&self, index: usize, size: usize, label: &str) -> Result<&'a [u8]> {
671 let relative = index
672 .checked_mul(self.stride)
673 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} range overflow")))?;
674 let start = self
675 .start
676 .checked_add(relative)
677 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} range overflow")))?;
678 let end = start
679 .checked_add(size)
680 .filter(|end| *end <= self.view_end)
681 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} out of bounds")))?;
682 self.buffer
683 .get(start..end)
684 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} out of bounds")))
685 }
686}
687
688#[derive(Debug, Clone)]
690pub struct DracoPrimitiveInfo {
691 pub mesh_index: usize,
693 pub mesh_name: Option<String>,
695 pub primitive_index: usize,
697 pub buffer_view: usize,
699 pub attributes: BTreeMap<String, u32>,
701}
702
703impl GltfReader {
704 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
708 let path = path.as_ref();
709 let data = fs::read(path)?;
710 let base = path.parent().unwrap_or_else(|| Path::new("."));
711 let resolver = FileResourceResolver::new(base, ExternalFilePolicy::Allow);
712 Self::from_bytes_with_resolver(&data, &resolver, &ResourceLimits::default())
713 }
714
715 pub fn from_glb(data: &[u8]) -> Result<Self> {
717 let container = parse_gltf_container(data)?;
718 if container.format != GltfContainerFormat::Glb {
719 return Err(GltfError::InvalidGlb("input is not a GLB container".into()));
720 }
721 Self::from_bytes(data)
722 }
723
724 pub fn from_bytes(data: &[u8]) -> Result<Self> {
729 Self::from_bytes_impl(data, None, &ResourceLimits::default(), false)
730 }
731
732 pub fn from_bytes_with_base_path(data: &[u8], base_path: Option<&Path>) -> Result<Self> {
734 if let Some(base) = base_path {
735 let resolver = FileResourceResolver::new(base, ExternalFilePolicy::Allow);
736 Self::from_bytes_with_resolver(data, &resolver, &ResourceLimits::default())
737 } else {
738 Self::from_bytes_impl(data, None, &ResourceLimits::default(), false)
739 }
740 }
741
742 pub fn from_bytes_with_resolver(
744 data: &[u8],
745 resolver: &dyn ResourceResolver,
746 limits: &ResourceLimits,
747 ) -> Result<Self> {
748 Self::from_bytes_impl(data, Some(resolver), limits, false)
749 }
750
751 pub fn from_gltf(json_data: &[u8], base_path: Option<&Path>) -> Result<Self> {
753 let container = parse_gltf_container(json_data)?;
754 if container.format != GltfContainerFormat::Gltf {
755 return Err(GltfError::InvalidGltf("input is a GLB container".into()));
756 }
757 Self::from_bytes_with_base_path(json_data, base_path)
758 }
759
760 pub fn from_bytes_lenient(data: &[u8]) -> Result<Self> {
769 Self::from_bytes_impl(data, None, &ResourceLimits::default(), true)
770 }
771
772 pub fn from_bytes_lenient_with_base_path(
774 data: &[u8],
775 base_path: Option<&Path>,
776 ) -> Result<Self> {
777 if let Some(base) = base_path {
778 let resolver = FileResourceResolver::new(base, ExternalFilePolicy::Allow);
779 Self::from_bytes_lenient_with_resolver(data, &resolver, &ResourceLimits::default())
780 } else {
781 Self::from_bytes_impl(data, None, &ResourceLimits::default(), true)
782 }
783 }
784
785 pub fn from_bytes_lenient_with_resolver(
787 data: &[u8],
788 resolver: &dyn ResourceResolver,
789 limits: &ResourceLimits,
790 ) -> Result<Self> {
791 Self::from_bytes_impl(data, Some(resolver), limits, true)
792 }
793
794 fn from_bytes_impl(
795 data: &[u8],
796 resolver: Option<&dyn ResourceResolver>,
797 limits: &ResourceLimits,
798 lenient: bool,
799 ) -> Result<Self> {
800 let container = parse_gltf_container(data)?;
801 let root: GltfRoot = serde_json::from_slice(container.json)?;
802 validate_typed_khr_draco_document(&root)?;
803 validate_root_metadata(&root)?;
804 if !lenient {
805 reject_unsupported_features(&root)?;
806 }
807 let buffers = load_buffers(
808 &root,
809 container.format == GltfContainerFormat::Glb,
810 container.bin,
811 resolver,
812 limits,
813 )?;
814 validate_images(&root, &buffers, resolver, limits)?;
815 Ok(Self { root, buffers })
816 }
817
818 pub fn from_value(doc: &serde_json::Value, buffers: Vec<Vec<u8>>) -> Result<Self> {
832 validate_khr_draco_document(doc)?;
833 let root: GltfRoot = serde_json::from_value(doc.clone())?;
834 validate_root_metadata(&root)?;
835 Ok(Self { root, buffers })
836 }
837
838 #[cfg(feature = "gltf-writer")]
841 pub(crate) fn buffers(&self) -> &[Vec<u8>] {
842 &self.buffers
843 }
844
845 pub fn decode_primitive_with_semantics(
859 &self,
860 mesh_idx: usize,
861 prim_idx: usize,
862 ) -> Result<(Mesh, Vec<(String, u32)>)> {
863 let gltf_mesh = self.root.meshes.get(mesh_idx).ok_or_else(|| {
864 GltfError::InvalidGltf(format!("Mesh index {} out of range", mesh_idx))
865 })?;
866 let primitive = gltf_mesh.primitives.get(prim_idx).ok_or_else(|| {
867 GltfError::InvalidGltf(format!(
868 "Primitive index {}:{} out of range",
869 mesh_idx, prim_idx
870 ))
871 })?;
872 if primitive
873 .extensions
874 .as_ref()
875 .and_then(|ext| ext.khr_draco_mesh_compression.as_ref())
876 .is_some()
877 {
878 return Err(GltfError::Unsupported(
879 "primitive is already Draco-compressed".into(),
880 ));
881 }
882 self.decode_standard_primitive(mesh_idx, prim_idx, primitive)
883 }
884
885 pub fn has_draco_extension(&self) -> bool {
887 self.root
888 .extensions_used
889 .iter()
890 .any(|ext| ext == KHR_DRACO_MESH_COMPRESSION)
891 }
892
893 pub fn document_metadata(&self) -> GltfDocumentMetadata {
895 let external_resource_uris = self
896 .root
897 .buffers
898 .iter()
899 .filter_map(|buffer| buffer.uri.as_deref())
900 .chain(
901 self.root
902 .images
903 .iter()
904 .filter_map(|image| image.uri.as_deref()),
905 )
906 .filter(|uri| !uri.starts_with("data:"))
907 .map(str::to_owned)
908 .collect::<BTreeSet<_>>()
909 .into_iter()
910 .collect();
911 GltfDocumentMetadata {
912 primitive_names: self
913 .root
914 .meshes
915 .iter()
916 .flat_map(|mesh| std::iter::repeat_n(mesh.name.clone(), mesh.primitives.len()))
917 .collect(),
918 nodes: self
919 .root
920 .nodes
921 .iter()
922 .map(|node| GltfNodeMetadata {
923 name: node.name.clone(),
924 mesh: node.mesh,
925 translation: node.translation,
926 rotation: node.rotation,
927 scale: node.scale,
928 children: node.children.clone(),
929 })
930 .collect(),
931 scenes: self
932 .root
933 .scenes
934 .iter()
935 .map(|scene| GltfSceneMetadata {
936 name: scene.name.clone(),
937 nodes: scene.nodes.clone(),
938 })
939 .collect(),
940 default_scene: self.root.scene,
941 uses_draco: self.has_draco_extension(),
942 external_resource_uris,
943 }
944 }
945
946 pub fn draco_primitives(&self) -> Vec<DracoPrimitiveInfo> {
948 let mut result = Vec::new();
949
950 for (mesh_idx, mesh) in self.root.meshes.iter().enumerate() {
951 for (prim_idx, primitive) in mesh.primitives.iter().enumerate() {
952 if let Some(ext) = &primitive.extensions {
953 if let Some(draco) = &ext.khr_draco_mesh_compression {
954 result.push(DracoPrimitiveInfo {
955 mesh_index: mesh_idx,
956 mesh_name: mesh.name.clone(),
957 primitive_index: prim_idx,
958 buffer_view: draco.buffer_view as usize,
959 attributes: draco.attributes.clone(),
960 });
961 }
962 }
963 }
964 }
965
966 result
967 }
968
969 pub fn get_draco_data(&self, info: &DracoPrimitiveInfo) -> Result<&[u8]> {
971 let buffer_view = self
972 .root
973 .buffer_views
974 .get(info.buffer_view)
975 .ok_or_else(|| {
976 GltfError::InvalidGltf(format!("Invalid buffer view index: {}", info.buffer_view))
977 })?;
978
979 let buffer = self.buffers.get(buffer_view.buffer).ok_or_else(|| {
980 GltfError::InvalidGltf(format!("Invalid buffer index: {}", buffer_view.buffer))
981 })?;
982
983 let offset = buffer_view.byte_offset.unwrap_or(0);
984 let end = offset
985 .checked_add(buffer_view.byte_length)
986 .ok_or_else(|| GltfError::InvalidGltf("Buffer view range overflow".into()))?;
987
988 if end > buffer.len() {
989 return Err(GltfError::InvalidGltf(
990 "Buffer view extends past buffer end".into(),
991 ));
992 }
993
994 Ok(&buffer[offset..end])
995 }
996
997 pub fn decode_draco_mesh(&self, info: &DracoPrimitiveInfo) -> Result<Mesh> {
999 let data = self.get_draco_data(info)?;
1000 let mut decoder_buffer = DecoderBuffer::new(data);
1001 let mut mesh = Mesh::new();
1002 let mut decoder = MeshDecoder::new();
1003
1004 decoder
1005 .decode(&mut decoder_buffer, &mut mesh)
1006 .map_err(GltfError::DracoDecode)?;
1007
1008 let primitive = self.primitive_for_draco_info(info)?;
1009 self.validate_draco_primitive_metadata(info, primitive, &mesh)?;
1010 self.add_draco_side_attributes(&mut mesh, primitive, info)?;
1011
1012 Ok(mesh)
1013 }
1014
1015 #[cfg(feature = "point_cloud_decode")]
1017 pub fn decode_draco_point_cloud(&self, info: &DracoPrimitiveInfo) -> Result<PointCloud> {
1018 let data = self.get_draco_data(info)?;
1019 let mut decoder_buffer = DecoderBuffer::new(data);
1020 let mut point_cloud = PointCloud::new();
1021 let mut decoder = PointCloudDecoder::new();
1022
1023 decoder
1024 .decode(&mut decoder_buffer, &mut point_cloud)
1025 .map_err(GltfError::DracoDecode)?;
1026
1027 Ok(point_cloud)
1028 }
1029
1030 pub fn decode_all_draco_meshes(&self) -> Result<Vec<(DracoPrimitiveInfo, Mesh)>> {
1032 let primitives = self.draco_primitives();
1033 let mut result = Vec::with_capacity(primitives.len());
1034
1035 for info in primitives {
1036 let mesh = self.decode_draco_mesh(&info)?;
1037 result.push((info, mesh));
1038 }
1039
1040 Ok(result)
1041 }
1042
1043 fn decode_standard_primitive(
1054 &self,
1055 _mesh_idx: usize,
1056 _prim_idx: usize,
1057 primitive: &Primitive,
1058 ) -> Result<(Mesh, Vec<(String, u32)>)> {
1059 let mode = primitive.mode.unwrap_or(GLTF_MODE_TRIANGLES);
1060 let attributes: Vec<(String, usize)> = primitive
1061 .attributes
1062 .iter()
1063 .map(|(semantic, accessor)| (semantic.clone(), *accessor))
1064 .collect();
1065 decode_geometry(
1066 &self.accessor_reader(),
1067 mode,
1068 &attributes,
1069 primitive.indices,
1070 )
1071 }
1072
1073 fn accessor_reader(&self) -> GltfAccessorReader<'_> {
1074 GltfAccessorReader::new(&self.root, &self.buffers)
1075 }
1076
1077 fn decode_primitive_mesh(
1078 &self,
1079 mesh_idx: usize,
1080 gltf_mesh: &GltfMesh,
1081 prim_idx: usize,
1082 primitive: &Primitive,
1083 ) -> Result<Mesh> {
1084 if let Some(draco) = primitive
1085 .extensions
1086 .as_ref()
1087 .and_then(|ext| ext.khr_draco_mesh_compression.as_ref())
1088 {
1089 let info = DracoPrimitiveInfo {
1090 mesh_index: mesh_idx,
1091 mesh_name: gltf_mesh.name.clone(),
1092 primitive_index: prim_idx,
1093 buffer_view: draco.buffer_view as usize,
1094 attributes: draco.attributes.clone(),
1095 };
1096 self.decode_draco_mesh(&info)
1097 } else {
1098 self.decode_standard_primitive(mesh_idx, prim_idx, primitive)
1099 .map(|(mesh, _)| mesh)
1100 }
1101 }
1102
1103 fn primitive_for_draco_info(&self, info: &DracoPrimitiveInfo) -> Result<&Primitive> {
1104 let mesh = self.root.meshes.get(info.mesh_index).ok_or_else(|| {
1105 GltfError::InvalidGltf(format!("Invalid mesh index: {}", info.mesh_index))
1106 })?;
1107 let primitive = mesh.primitives.get(info.primitive_index).ok_or_else(|| {
1108 GltfError::InvalidGltf(format!(
1109 "Invalid primitive index {} for mesh {}",
1110 info.primitive_index, info.mesh_index
1111 ))
1112 })?;
1113 let draco = primitive
1114 .extensions
1115 .as_ref()
1116 .and_then(|ext| ext.khr_draco_mesh_compression.as_ref())
1117 .ok_or_else(|| {
1118 GltfError::InvalidGltf(format!(
1119 "Primitive {}:{} does not use {}",
1120 info.mesh_index, info.primitive_index, KHR_DRACO_MESH_COMPRESSION
1121 ))
1122 })?;
1123 if draco.buffer_view as usize != info.buffer_view || draco.attributes != info.attributes {
1124 return Err(GltfError::InvalidGltf(
1125 "Draco primitive info does not match source primitive".into(),
1126 ));
1127 }
1128 Ok(primitive)
1129 }
1130
1131 fn validate_draco_primitive_metadata(
1132 &self,
1133 info: &DracoPrimitiveInfo,
1134 primitive: &Primitive,
1135 mesh: &Mesh,
1136 ) -> Result<()> {
1137 let mode = primitive.mode.unwrap_or(GLTF_MODE_TRIANGLES);
1138 if mode != GLTF_MODE_TRIANGLES && mode != 5 {
1139 return Err(GltfError::Unsupported(format!(
1140 "{} supports only TRIANGLES=4 or TRIANGLE_STRIP=5, got mode {}",
1141 KHR_DRACO_MESH_COMPRESSION, mode
1142 )));
1143 }
1144
1145 for (semantic, &draco_attribute_id) in &info.attributes {
1146 let Some(accessor_idx) = primitive.attributes.get(semantic) else {
1147 return Err(GltfError::InvalidGltf(format!(
1148 "{} attribute {} is not present in primitive.attributes",
1149 KHR_DRACO_MESH_COMPRESSION, semantic
1150 )));
1151 };
1152 let attribute_spec = supported_semantic_spec(semantic)?;
1153 let attribute = mesh
1154 .attribute_by_unique_id(draco_attribute_id)
1155 .ok_or_else(|| {
1156 GltfError::InvalidGltf(format!(
1157 "Draco unique attribute id {} for {} is absent",
1158 draco_attribute_id, semantic
1159 ))
1160 })?;
1161 if attribute.attribute_type() != attribute_spec.attribute_type {
1162 return Err(GltfError::InvalidGltf(format!(
1163 "Draco attribute {} has type {:?}, expected {:?}",
1164 semantic,
1165 attribute.attribute_type(),
1166 attribute_spec.attribute_type
1167 )));
1168 }
1169 self.validate_accessor_matches_attribute(*accessor_idx, semantic, attribute)?;
1170 }
1171
1172 if let Some(indices_accessor_idx) = primitive.indices {
1173 let accessor = self
1174 .root
1175 .accessors
1176 .get(indices_accessor_idx)
1177 .ok_or_else(|| {
1178 GltfError::InvalidGltf(format!(
1179 "Invalid indices accessor index: {}",
1180 indices_accessor_idx
1181 ))
1182 })?;
1183 if accessor.sparse.is_some() {
1184 return Err(GltfError::Unsupported(
1185 "Sparse index accessors are not supported".into(),
1186 ));
1187 }
1188 if accessor.accessor_type != "SCALAR" {
1189 return Err(GltfError::InvalidGltf(format!(
1190 "Expected SCALAR accessor for Draco indices, got {}",
1191 accessor.accessor_type
1192 )));
1193 }
1194 if accessor.normalized {
1195 return Err(GltfError::InvalidGltf(
1196 "Draco indices accessor must not be normalized".into(),
1197 ));
1198 }
1199 if ![
1200 GLTF_COMPONENT_UNSIGNED_BYTE,
1201 GLTF_COMPONENT_UNSIGNED_SHORT,
1202 GLTF_COMPONENT_UNSIGNED_INT,
1203 ]
1204 .contains(&accessor.component_type)
1205 {
1206 return Err(GltfError::Unsupported(format!(
1207 "Unsupported Draco index accessor component type: {}",
1208 accessor.component_type
1209 )));
1210 }
1211 let expected_count = if mode == GLTF_MODE_TRIANGLES {
1212 mesh.num_faces().checked_mul(3)
1213 } else {
1214 mesh.num_faces().checked_add(2)
1215 }
1216 .ok_or_else(|| GltfError::InvalidGltf("decoded index count overflow".into()))?;
1217 if accessor.count != expected_count {
1218 return Err(GltfError::InvalidGltf(format!(
1219 "Draco indices accessor count {} does not match decoded index count {}",
1220 accessor.count, expected_count
1221 )));
1222 }
1223 }
1224
1225 Ok(())
1226 }
1227
1228 fn validate_accessor_matches_attribute(
1229 &self,
1230 accessor_idx: usize,
1231 semantic: &str,
1232 attribute: &PointAttribute,
1233 ) -> Result<()> {
1234 let accessor = self.root.accessors.get(accessor_idx).ok_or_else(|| {
1235 GltfError::InvalidGltf(format!("Invalid accessor index: {}", accessor_idx))
1236 })?;
1237 validate_semantic_accessor(
1238 semantic,
1239 &accessor.accessor_type,
1240 accessor.component_type,
1241 accessor.normalized,
1242 )?;
1243 if accessor.sparse.is_some() {
1244 return Err(GltfError::Unsupported(format!(
1245 "Sparse accessor for {} is not supported",
1246 semantic
1247 )));
1248 }
1249 let expected_accessor_type = gltf_type_for_num_components(attribute.num_components())?;
1250 if accessor.accessor_type != expected_accessor_type {
1251 return Err(GltfError::InvalidGltf(format!(
1252 "{} accessor type {} does not match decoded attribute type {}",
1253 semantic, accessor.accessor_type, expected_accessor_type
1254 )));
1255 }
1256 let expected_component_type = component_type_for_data_type(attribute.data_type())?;
1257 let attribute_spec = supported_semantic_spec(semantic)?;
1258 if !attribute_spec
1259 .allowed_component_types
1260 .contains(&expected_component_type)
1261 {
1262 return Err(GltfError::Unsupported(format!(
1263 "{} decoded component type {} is not supported by draco-io glTF",
1264 semantic, expected_component_type
1265 )));
1266 }
1267 if accessor.component_type != expected_component_type {
1268 return Err(GltfError::InvalidGltf(format!(
1269 "{} accessor componentType {} does not match decoded componentType {}",
1270 semantic, accessor.component_type, expected_component_type
1271 )));
1272 }
1273 if accessor.normalized != attribute.normalized() {
1274 return Err(GltfError::InvalidGltf(format!(
1275 "{} accessor normalized={} does not match decoded normalized={}",
1276 semantic,
1277 accessor.normalized,
1278 attribute.normalized()
1279 )));
1280 }
1281 if accessor.count != attribute.size() {
1282 return Err(GltfError::InvalidGltf(format!(
1283 "{} accessor count {} does not match decoded attribute count {}",
1284 semantic,
1285 accessor.count,
1286 attribute.size()
1287 )));
1288 }
1289 Ok(())
1290 }
1291
1292 fn add_draco_side_attributes(
1293 &self,
1294 mesh: &mut Mesh,
1295 primitive: &Primitive,
1296 info: &DracoPrimitiveInfo,
1297 ) -> Result<()> {
1298 let accessor_reader = self.accessor_reader();
1299 let mut attributes: Vec<_> = primitive.attributes.iter().collect();
1300 attributes.sort_by_key(|(left, _)| *left);
1301
1302 for (semantic, accessor_idx) in attributes {
1303 if info.attributes.contains_key(semantic) {
1304 continue;
1305 }
1306 add_named_attribute(mesh, &accessor_reader, semantic, *accessor_idx, None)?;
1307 }
1308 Ok(())
1309 }
1310
1311 pub fn num_meshes(&self) -> usize {
1313 self.root.meshes.len()
1314 }
1315
1316 pub fn num_buffers(&self) -> usize {
1318 self.buffers.len()
1319 }
1320
1321 pub fn extensions_used(&self) -> &[String] {
1323 &self.root.extensions_used
1324 }
1325
1326 pub fn extensions_required(&self) -> &[String] {
1328 &self.root.extensions_required
1329 }
1330}
1331
1332fn validate_typed_khr_draco_document(root: &GltfRoot) -> Result<()> {
1337 let used = root
1338 .extensions_used
1339 .iter()
1340 .any(|extension| extension == KHR_DRACO_MESH_COMPRESSION);
1341 let required = root
1342 .extensions_required
1343 .iter()
1344 .any(|extension| extension == KHR_DRACO_MESH_COMPRESSION);
1345 if required && !used {
1346 return Err(GltfError::InvalidGltf(format!(
1347 "{KHR_DRACO_MESH_COMPRESSION} is required but is not listed in extensionsUsed"
1348 )));
1349 }
1350
1351 for mesh in &root.meshes {
1352 for primitive in &mesh.primitives {
1353 let Some(extension) = primitive
1354 .extensions
1355 .as_ref()
1356 .and_then(|extensions| extensions.khr_draco_mesh_compression.as_ref())
1357 else {
1358 continue;
1359 };
1360 validate_khr_draco_contract(
1361 KhrDracoPrimitiveContract {
1362 extension: KhrDracoExtensionContract {
1363 buffer_view: extension.buffer_view as usize,
1364 attributes: &extension.attributes,
1365 },
1366 primitive_attributes: primitive
1367 .attributes
1368 .iter()
1369 .map(|(semantic, accessor)| (semantic.as_str(), *accessor)),
1370 indices: primitive.indices,
1371 mode: primitive.mode.unwrap_or(GLTF_MODE_TRIANGLES),
1372 extension_used: used,
1373 extension_required: required,
1374 buffer_view_count: root.buffer_views.len(),
1375 },
1376 |accessor| {
1377 root.accessors
1378 .get(accessor)
1379 .map(|accessor| accessor.buffer_view.is_some() || accessor.sparse.is_some())
1380 },
1381 )?;
1382 }
1383 }
1384 Ok(())
1385}
1386
1387fn validate_root_metadata(root: &GltfRoot) -> Result<()> {
1388 if root.asset.version != "2.0" {
1389 return Err(GltfError::Unsupported(format!(
1390 "Unsupported glTF asset version: {}",
1391 root.asset.version
1392 )));
1393 }
1394 if let Some(min_version) = &root.asset.min_version {
1395 if min_version != "2.0" {
1396 return Err(GltfError::Unsupported(format!(
1397 "Unsupported glTF minimum version: {}",
1398 min_version
1399 )));
1400 }
1401 }
1402
1403 for required in &root.extensions_required {
1409 if !root.extensions_used.iter().any(|used| used == required) {
1410 return Err(GltfError::InvalidGltf(format!(
1411 "Required extension {} is not listed in extensionsUsed",
1412 required
1413 )));
1414 }
1415 }
1416
1417 let mut has_draco_primitive = false;
1418 for mesh in &root.meshes {
1419 for primitive in &mesh.primitives {
1420 if primitive
1421 .extensions
1422 .as_ref()
1423 .and_then(|ext| ext.khr_draco_mesh_compression.as_ref())
1424 .is_some()
1425 {
1426 has_draco_primitive = true;
1427 }
1428 }
1429 }
1430 if has_draco_primitive
1431 && !root
1432 .extensions_used
1433 .iter()
1434 .any(|used| used == KHR_DRACO_MESH_COMPRESSION)
1435 {
1436 return Err(GltfError::InvalidGltf(format!(
1437 "Primitive uses {} but extensionsUsed does not list it",
1438 KHR_DRACO_MESH_COMPRESSION
1439 )));
1440 }
1441
1442 validate_document_references(root)?;
1443
1444 Ok(())
1445}
1446
1447fn validate_reference(index: usize, count: usize, label: &str) -> Result<()> {
1448 if index >= count {
1449 return Err(GltfError::InvalidGltf(format!(
1450 "{label} {index} is out of range for {count} entries"
1451 )));
1452 }
1453 Ok(())
1454}
1455
1456fn validate_document_references(root: &GltfRoot) -> Result<()> {
1457 if let Some(scene) = root.scene {
1458 validate_reference(scene, root.scenes.len(), "default scene")?;
1459 }
1460 for (scene_index, scene) in root.scenes.iter().enumerate() {
1461 for &node in &scene.nodes {
1462 validate_reference(node, root.nodes.len(), &format!("scene {scene_index} node"))?;
1463 }
1464 }
1465
1466 for (node_index, node) in root.nodes.iter().enumerate() {
1467 if let Some(mesh) = node.mesh {
1468 validate_reference(mesh, root.meshes.len(), &format!("node {node_index} mesh"))?;
1469 }
1470 if let Some(skin) = node.skin {
1471 validate_reference(skin, root.skins.len(), &format!("node {node_index} skin"))?;
1472 }
1473 for &child in &node.children {
1474 validate_reference(child, root.nodes.len(), &format!("node {node_index} child"))?;
1475 if child == node_index {
1476 return Err(GltfError::InvalidGltf(format!(
1477 "node {node_index} cannot be its own child"
1478 )));
1479 }
1480 }
1481 }
1482
1483 for (mesh_index, mesh) in root.meshes.iter().enumerate() {
1484 for (primitive_index, primitive) in mesh.primitives.iter().enumerate() {
1485 for (semantic, &accessor) in &primitive.attributes {
1486 validate_reference(
1487 accessor,
1488 root.accessors.len(),
1489 &format!("primitive {mesh_index}:{primitive_index} {semantic} accessor"),
1490 )?;
1491 }
1492 if let Some(indices) = primitive.indices {
1493 validate_reference(
1494 indices,
1495 root.accessors.len(),
1496 &format!("primitive {mesh_index}:{primitive_index} indices accessor"),
1497 )?;
1498 }
1499 let vertex_count = primitive
1500 .attributes
1501 .values()
1502 .next()
1503 .and_then(|accessor| root.accessors.get(*accessor))
1504 .map(|accessor| accessor.count);
1505 for (target_index, target) in primitive.targets.iter().enumerate() {
1506 for (semantic, &accessor_index) in target {
1507 validate_reference(
1508 accessor_index,
1509 root.accessors.len(),
1510 &format!(
1511 "primitive {mesh_index}:{primitive_index} morph target {target_index} {semantic} accessor"
1512 ),
1513 )?;
1514 let accessor = &root.accessors[accessor_index];
1515 if vertex_count.is_some_and(|count| accessor.count != count) {
1516 return Err(GltfError::InvalidGltf(format!(
1517 "primitive {mesh_index}:{primitive_index} morph target accessor count {} does not match vertex count {}",
1518 accessor.count,
1519 vertex_count.unwrap_or_default()
1520 )));
1521 }
1522 if !matches!(semantic.as_str(), "POSITION" | "NORMAL" | "TANGENT")
1523 || accessor.accessor_type != "VEC3"
1524 || accessor.component_type != GLTF_COMPONENT_FLOAT
1525 || accessor.normalized
1526 {
1527 return Err(GltfError::InvalidGltf(format!(
1528 "primitive {mesh_index}:{primitive_index} morph target {semantic} accessor has an invalid contract"
1529 )));
1530 }
1531 }
1532 }
1533 }
1534 }
1535
1536 for (skin_index, skin) in root.skins.iter().enumerate() {
1537 if skin.joints.is_empty() {
1538 return Err(GltfError::InvalidGltf(format!(
1539 "skin {skin_index} has no joints"
1540 )));
1541 }
1542 for &joint in &skin.joints {
1543 validate_reference(joint, root.nodes.len(), &format!("skin {skin_index} joint"))?;
1544 }
1545 if let Some(skeleton) = skin.skeleton {
1546 validate_reference(
1547 skeleton,
1548 root.nodes.len(),
1549 &format!("skin {skin_index} skeleton"),
1550 )?;
1551 }
1552 if let Some(accessor_index) = skin.inverse_bind_matrices {
1553 validate_reference(
1554 accessor_index,
1555 root.accessors.len(),
1556 &format!("skin {skin_index} inverseBindMatrices accessor"),
1557 )?;
1558 let accessor = &root.accessors[accessor_index];
1559 if accessor.accessor_type != "MAT4"
1560 || accessor.component_type != GLTF_COMPONENT_FLOAT
1561 || accessor.normalized
1562 || accessor.count < skin.joints.len()
1563 {
1564 return Err(GltfError::InvalidGltf(format!(
1565 "skin {skin_index} inverseBindMatrices accessor has an invalid contract"
1566 )));
1567 }
1568 }
1569 }
1570
1571 for (animation_index, animation) in root.animations.iter().enumerate() {
1572 if animation.channels.is_empty() || animation.samplers.is_empty() {
1573 return Err(GltfError::InvalidGltf(format!(
1574 "animation {animation_index} must contain channels and samplers"
1575 )));
1576 }
1577 for (sampler_index, sampler) in animation.samplers.iter().enumerate() {
1578 validate_reference(
1579 sampler.input,
1580 root.accessors.len(),
1581 &format!("animation {animation_index} sampler {sampler_index} input accessor"),
1582 )?;
1583 validate_reference(
1584 sampler.output,
1585 root.accessors.len(),
1586 &format!("animation {animation_index} sampler {sampler_index} output accessor"),
1587 )?;
1588 let input = &root.accessors[sampler.input];
1589 if input.accessor_type != "SCALAR"
1590 || input.component_type != GLTF_COMPONENT_FLOAT
1591 || input.normalized
1592 {
1593 return Err(GltfError::InvalidGltf(format!(
1594 "animation {animation_index} sampler {sampler_index} input accessor has an invalid contract"
1595 )));
1596 }
1597 if sampler
1598 .interpolation
1599 .as_deref()
1600 .is_some_and(|interpolation| {
1601 !matches!(interpolation, "LINEAR" | "STEP" | "CUBICSPLINE")
1602 })
1603 {
1604 return Err(GltfError::InvalidGltf(format!(
1605 "animation {animation_index} sampler {sampler_index} has invalid interpolation"
1606 )));
1607 }
1608 }
1609 for (channel_index, channel) in animation.channels.iter().enumerate() {
1610 validate_reference(
1611 channel.sampler,
1612 animation.samplers.len(),
1613 &format!("animation {animation_index} channel {channel_index} sampler"),
1614 )?;
1615 if channel.target.path.is_empty() {
1616 return Err(GltfError::InvalidGltf(format!(
1617 "animation {animation_index} channel {channel_index} target path is empty"
1618 )));
1619 }
1620 if let Some(node) = channel.target.node {
1621 validate_reference(
1622 node,
1623 root.nodes.len(),
1624 &format!("animation {animation_index} channel {channel_index} target node"),
1625 )?;
1626 }
1627 }
1628 }
1629 Ok(())
1630}
1631
1632fn reject_unsupported_features(root: &GltfRoot) -> Result<()> {
1639 for required in &root.extensions_required {
1643 if required != KHR_DRACO_MESH_COMPRESSION {
1644 return Err(GltfError::Unsupported(format!(
1645 "Unsupported required extension: {}",
1646 required
1647 )));
1648 }
1649 }
1650
1651 for (mesh_idx, mesh) in root.meshes.iter().enumerate() {
1652 for (prim_idx, primitive) in mesh.primitives.iter().enumerate() {
1653 if !primitive.targets.is_empty() {
1654 return Err(GltfError::Unsupported(format!(
1655 "Morph targets are not supported on primitive {}:{}",
1656 mesh_idx, prim_idx
1657 )));
1658 }
1659 }
1660 }
1661
1662 if !root.skins.is_empty() {
1663 return Err(GltfError::Unsupported("Skins are not supported".into()));
1664 }
1665 if root.nodes.iter().any(|node| node.skin.is_some()) {
1666 return Err(GltfError::Unsupported(
1667 "Skinned nodes are not supported".into(),
1668 ));
1669 }
1670 if !root.animations.is_empty() {
1671 return Err(GltfError::Unsupported(
1672 "Animations are not supported".into(),
1673 ));
1674 }
1675
1676 Ok(())
1677}
1678
1679fn load_buffers(
1680 root: &GltfRoot,
1681 is_glb: bool,
1682 glb_bin_chunk: Option<&[u8]>,
1683 resolver: Option<&dyn ResourceResolver>,
1684 limits: &ResourceLimits,
1685) -> Result<Vec<Vec<u8>>> {
1686 let mut references = Vec::new();
1687 references
1688 .try_reserve_exact(root.buffers.len())
1689 .map_err(|_| GltfError::ResourceLimitExceeded("buffer table allocation failed".into()))?;
1690 for buffer in &root.buffers {
1691 references.push(GltfBufferReference {
1692 uri: buffer.uri.as_deref(),
1693 byte_length: buffer.byte_length,
1694 });
1695 }
1696 let format = if is_glb {
1697 GltfContainerFormat::Glb
1698 } else {
1699 GltfContainerFormat::Gltf
1700 };
1701 resolve_gltf_buffers(&references, format, glb_bin_chunk, resolver, limits)
1702}
1703
1704fn validate_images(
1705 root: &GltfRoot,
1706 buffers: &[Vec<u8>],
1707 resolver: Option<&dyn ResourceResolver>,
1708 limits: &ResourceLimits,
1709) -> Result<()> {
1710 for (index, image) in root.images.iter().enumerate() {
1711 match (image.uri.as_deref(), image.buffer_view) {
1712 (Some(_), Some(_)) => {
1713 return Err(GltfError::InvalidGltf(format!(
1714 "Image {index} defines both uri and bufferView"
1715 )));
1716 }
1717 (None, None) => {
1718 return Err(GltfError::InvalidGltf(format!(
1719 "Image {index} defines neither uri nor bufferView"
1720 )));
1721 }
1722 (Some(uri), None) => {
1723 let _ = resolve_resource_uri(uri, resolver, limits.max_resource_bytes)?;
1727 }
1728 (None, Some(view_index)) => {
1729 if image.mime_type.is_none() {
1730 return Err(GltfError::InvalidGltf(format!(
1731 "Buffer-view image {index} has no mimeType"
1732 )));
1733 }
1734 let view = root.buffer_views.get(view_index).ok_or_else(|| {
1735 GltfError::InvalidGltf(format!(
1736 "Image {index} references invalid bufferView {view_index}"
1737 ))
1738 })?;
1739 let buffer = buffers.get(view.buffer).ok_or_else(|| {
1740 GltfError::InvalidGltf(format!(
1741 "Image {index} bufferView references invalid buffer {}",
1742 view.buffer
1743 ))
1744 })?;
1745 let start = view.byte_offset.unwrap_or(0);
1746 let end = start.checked_add(view.byte_length).ok_or_else(|| {
1747 GltfError::InvalidGltf(format!("Image {index} byte range overflow"))
1748 })?;
1749 if end > buffer.len() {
1750 return Err(GltfError::InvalidGltf(format!(
1751 "Image {index} extends past its buffer"
1752 )));
1753 }
1754 }
1755 }
1756 }
1757 Ok(())
1758}
1759
1760fn accessor_num_components(accessor_type: &str) -> Result<u8> {
1761 match accessor_type {
1762 "SCALAR" => Ok(1),
1763 "VEC2" => Ok(2),
1764 "VEC3" => Ok(3),
1765 "VEC4" => Ok(4),
1766 _ => Err(GltfError::Unsupported(format!(
1767 "Unsupported accessor type: {}",
1768 accessor_type
1769 ))),
1770 }
1771}
1772
1773fn data_type_for_component_type(component_type: u32) -> Result<DataType> {
1774 match component_type {
1775 GLTF_COMPONENT_BYTE => Ok(DataType::Int8),
1776 GLTF_COMPONENT_UNSIGNED_BYTE => Ok(DataType::Uint8),
1777 GLTF_COMPONENT_SHORT => Ok(DataType::Int16),
1778 GLTF_COMPONENT_UNSIGNED_SHORT => Ok(DataType::Uint16),
1779 GLTF_COMPONENT_UNSIGNED_INT => Ok(DataType::Uint32),
1780 GLTF_COMPONENT_FLOAT => Ok(DataType::Float32),
1781 _ => Err(GltfError::Unsupported(format!(
1782 "Unsupported component type: {}",
1783 component_type
1784 ))),
1785 }
1786}
1787
1788impl crate::traits::Reader for GltfReader {
1791 fn open<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
1792 GltfReader::open(path).map_err(|e| std::io::Error::other(e.to_string()))
1793 }
1794
1795 fn read_meshes(&mut self) -> std::io::Result<Vec<draco_core::mesh::Mesh>> {
1796 self.decode_all_meshes()
1797 .map_err(|e| std::io::Error::other(e.to_string()))
1798 }
1799}
1800
1801impl ReadFromBytes for GltfReader {
1802 fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
1803 GltfReader::from_bytes(bytes).map_err(|e| io::Error::other(e.to_string()))
1804 }
1805}
1806
1807impl GltfReader {
1808 pub fn decode_all_meshes(&self) -> Result<Vec<Mesh>> {
1810 let mut result = Vec::new();
1811
1812 for (mesh_idx, gltf_mesh) in self.root.meshes.iter().enumerate() {
1813 for (prim_idx, primitive) in gltf_mesh.primitives.iter().enumerate() {
1814 let mesh = self.decode_primitive_mesh(mesh_idx, gltf_mesh, prim_idx, primitive)?;
1815 result.push(mesh);
1816 }
1817 }
1818
1819 Ok(result)
1820 }
1821
1822 fn compute_node_transform(node: &GltfNode) -> Option<crate::scene::Transform> {
1824 if let Some(m) = &node.matrix {
1825 Some(crate::scene::Transform {
1827 matrix: [
1828 [m[0], m[4], m[8], m[12]],
1829 [m[1], m[5], m[9], m[13]],
1830 [m[2], m[6], m[10], m[14]],
1831 [m[3], m[7], m[11], m[15]],
1832 ],
1833 })
1834 } else if node.translation.is_some() || node.rotation.is_some() || node.scale.is_some() {
1835 let t = node.translation.unwrap_or([0.0, 0.0, 0.0]);
1837 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]);
1839
1840 let (qx, qy, qz, qw) = (r[0], r[1], r[2], r[3]);
1842 let xx = qx * qx;
1843 let yy = qy * qy;
1844 let zz = qz * qz;
1845 let xy = qx * qy;
1846 let xz = qx * qz;
1847 let yz = qy * qz;
1848 let wx = qw * qx;
1849 let wy = qw * qy;
1850 let wz = qw * qz;
1851
1852 let rot = [
1854 [1.0 - 2.0 * (yy + zz), 2.0 * (xy - wz), 2.0 * (xz + wy)],
1855 [2.0 * (xy + wz), 1.0 - 2.0 * (xx + zz), 2.0 * (yz - wx)],
1856 [2.0 * (xz - wy), 2.0 * (yz + wx), 1.0 - 2.0 * (xx + yy)],
1857 ];
1858
1859 Some(crate::scene::Transform {
1861 matrix: [
1862 [rot[0][0] * s[0], rot[0][1] * s[1], rot[0][2] * s[2], t[0]],
1863 [rot[1][0] * s[0], rot[1][1] * s[1], rot[1][2] * s[2], t[1]],
1864 [rot[2][0] * s[0], rot[2][1] * s[1], rot[2][2] * s[2], t[2]],
1865 [0.0, 0.0, 0.0, 1.0],
1866 ],
1867 })
1868 } else {
1869 None
1870 }
1871 }
1872
1873 fn build_scene_node(
1875 &self,
1876 node_idx: usize,
1877 visited: &mut Vec<bool>,
1878 ) -> Result<crate::scene::SceneNode> {
1879 if node_idx >= self.root.nodes.len() {
1880 return Err(GltfError::InvalidGltf(format!(
1881 "Invalid node index: {}",
1882 node_idx
1883 )));
1884 }
1885
1886 if visited[node_idx] {
1888 return Err(GltfError::InvalidGltf(format!(
1889 "Cycle detected at node {}",
1890 node_idx
1891 )));
1892 }
1893 visited[node_idx] = true;
1894
1895 let gltf_node = &self.root.nodes[node_idx];
1896
1897 let mut scene_node = crate::scene::SceneNode::new(gltf_node.name.clone());
1898 scene_node.transform = Self::compute_node_transform(gltf_node);
1899
1900 if let Some(mesh_idx) = gltf_node.mesh {
1902 if let Some(gltf_mesh) = self.root.meshes.get(mesh_idx) {
1903 for (prim_idx, primitive) in gltf_mesh.primitives.iter().enumerate() {
1904 let mesh =
1905 self.decode_primitive_mesh(mesh_idx, gltf_mesh, prim_idx, primitive)?;
1906
1907 let mesh_instance_name = if gltf_mesh.primitives.len() > 1 {
1908 gltf_mesh
1909 .name
1910 .as_ref()
1911 .map(|n| format!("{}_{}", n, prim_idx))
1912 } else {
1913 gltf_mesh.name.clone()
1914 };
1915
1916 scene_node.mesh_instances.push(crate::scene::MeshInstance {
1917 name: mesh_instance_name,
1918 mesh,
1919 transform: None, });
1921 }
1922 }
1923 }
1924
1925 for &child_idx in &gltf_node.children {
1927 let child_node = self.build_scene_node(child_idx, visited)?;
1928 scene_node.children.push(child_node);
1929 }
1930
1931 Ok(scene_node)
1932 }
1933
1934 fn root_node_indices_without_scenes(&self) -> Vec<usize> {
1935 let mut is_child = vec![false; self.root.nodes.len()];
1936 for node in &self.root.nodes {
1937 for &child_idx in &node.children {
1938 if child_idx < is_child.len() {
1939 is_child[child_idx] = true;
1940 }
1941 }
1942 }
1943
1944 (0..self.root.nodes.len())
1945 .filter(|&i| !is_child[i])
1946 .collect()
1947 }
1948
1949 fn build_scene_from_roots(
1950 &self,
1951 name: Option<String>,
1952 root_node_indices: &[usize],
1953 ) -> Result<crate::scene::Scene> {
1954 let mut visited = vec![false; self.root.nodes.len()];
1955 let mut root_nodes = Vec::with_capacity(root_node_indices.len());
1956 for &node_idx in root_node_indices {
1957 root_nodes.push(self.build_scene_node(node_idx, &mut visited)?);
1958 }
1959
1960 Ok(crate::scene::Scene { name, root_nodes })
1961 }
1962
1963 fn read_scene_result(&self) -> Result<crate::scene::Scene> {
1964 let scene_idx = self.root.scene.or({
1965 if self.root.scenes.is_empty() {
1966 None
1967 } else {
1968 Some(0)
1969 }
1970 });
1971
1972 if let Some(idx) = scene_idx {
1973 let gltf_scene =
1974 self.root.scenes.get(idx).ok_or_else(|| {
1975 GltfError::InvalidGltf(format!("Invalid scene index: {}", idx))
1976 })?;
1977 self.build_scene_from_roots(gltf_scene.name.clone(), &gltf_scene.nodes)
1978 } else {
1979 let roots = self.root_node_indices_without_scenes();
1980 self.build_scene_from_roots(None, &roots)
1981 }
1982 }
1983
1984 fn read_scenes_result(&self) -> Result<Vec<crate::scene::Scene>> {
1985 if self.root.scenes.is_empty() {
1986 let roots = self.root_node_indices_without_scenes();
1987 return self
1988 .build_scene_from_roots(None, &roots)
1989 .map(|scene| vec![scene]);
1990 }
1991
1992 self.root
1993 .scenes
1994 .iter()
1995 .map(|scene| self.build_scene_from_roots(scene.name.clone(), &scene.nodes))
1996 .collect()
1997 }
1998}
1999
2000impl crate::scene::SceneReader for GltfReader {
2001 fn read_scene(&mut self) -> std::io::Result<crate::scene::Scene> {
2002 self.read_scene_result()
2003 .map_err(|e| std::io::Error::other(e.to_string()))
2004 }
2005
2006 fn read_scenes(&mut self) -> std::io::Result<Vec<crate::scene::Scene>> {
2007 self.read_scenes_result()
2008 .map_err(|e| std::io::Error::other(e.to_string()))
2009 }
2010}
2011#[cfg(test)]
2016mod tests {
2017 use super::*;
2018 use draco_core::draco_types::DataType;
2019 use draco_core::geometry_attribute::GeometryAttributeType;
2020 #[cfg(feature = "gltf-writer")]
2021 use draco_core::geometry_attribute::PointAttribute;
2022 use draco_core::mesh::Mesh;
2023 use serde_json::Value;
2024 use tempfile::tempdir;
2025
2026 fn build_glb(json: &str) -> Vec<u8> {
2027 let document: serde_json::Value = serde_json::from_str(json).unwrap();
2028 crate::gltf_container::build_glb_container(&document, &[]).unwrap()
2029 }
2030
2031 fn triangle_positions() -> Vec<u8> {
2032 [
2033 0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0,
2036 ]
2037 .into_iter()
2038 .flat_map(f32::to_le_bytes)
2039 .collect()
2040 }
2041
2042 #[cfg(feature = "gltf-writer")]
2043 fn triangle_mesh() -> Mesh {
2044 let mut mesh = Mesh::new();
2045 mesh.set_num_points(3);
2046 mesh.add_face([
2047 draco_core::geometry_indices::PointIndex(0),
2048 draco_core::geometry_indices::PointIndex(1),
2049 draco_core::geometry_indices::PointIndex(2),
2050 ]);
2051
2052 let mut positions = PointAttribute::new();
2053 positions.init(
2054 GeometryAttributeType::Position,
2055 3,
2056 DataType::Float32,
2057 false,
2058 3,
2059 );
2060 positions.buffer_mut().write(0, &triangle_positions());
2061 mesh.add_attribute(positions);
2062 mesh
2063 }
2064
2065 #[cfg(feature = "gltf-writer")]
2066 fn writer_gltf_json_value() -> serde_json::Value {
2067 let mut writer = crate::gltf_writer::GltfWriter::new();
2068 writer
2069 .add_draco_mesh(&triangle_mesh(), Some("triangle"), None)
2070 .unwrap();
2071 serde_json::from_str(&writer.to_gltf_embedded().unwrap()).unwrap()
2072 }
2073
2074 fn read_attribute_bytes(mesh: &Mesh, attribute_type: GeometryAttributeType) -> Vec<u8> {
2075 mesh.named_attribute(attribute_type)
2076 .expect("missing attribute")
2077 .buffer()
2078 .data()
2079 .to_vec()
2080 }
2081
2082 #[test]
2083 fn test_minimal_gltf_json() {
2084 let json = r#"{
2085 "asset": {"version": "2.0"},
2086 "meshes": [],
2087 "buffers": [],
2088 "bufferViews": [],
2089 "accessors": []
2090 }"#;
2091
2092 let root: GltfRoot = serde_json::from_str(json).unwrap();
2093 assert!(root.meshes.is_empty());
2094 assert!(root.buffers.is_empty());
2095 }
2096
2097 #[test]
2098 fn test_read_scenes_returns_all_gltf_scenes() {
2099 use crate::scene::SceneReader;
2100
2101 let json = r#"{
2102 "asset": {"version": "2.0"},
2103 "scene": 1,
2104 "scenes": [
2105 {"name": "Preview", "nodes": [0]},
2106 {"name": "Full", "nodes": [1, 2]}
2107 ],
2108 "nodes": [
2109 {"name": "PreviewRoot"},
2110 {"name": "FullRootA"},
2111 {"name": "FullRootB"}
2112 ]
2113 }"#;
2114
2115 let mut reader = GltfReader::from_gltf(json.as_bytes(), None).unwrap();
2116 let default_scene = reader.read_scene().unwrap();
2117 assert_eq!(default_scene.name, Some("Full".to_string()));
2118 assert_eq!(default_scene.root_nodes.len(), 2);
2119 assert_eq!(
2120 default_scene.root_nodes[0].name,
2121 Some("FullRootA".to_string())
2122 );
2123
2124 let scenes = reader.read_scenes().unwrap();
2125 assert_eq!(scenes.len(), 2);
2126 assert_eq!(scenes[0].name, Some("Preview".to_string()));
2127 assert_eq!(scenes[0].root_nodes.len(), 1);
2128 assert_eq!(
2129 scenes[0].root_nodes[0].name,
2130 Some("PreviewRoot".to_string())
2131 );
2132 assert_eq!(scenes[1].name, Some("Full".to_string()));
2133 assert_eq!(scenes[1].root_nodes.len(), 2);
2134 }
2135
2136 #[test]
2137 fn test_gltf_with_draco_extension() {
2138 let json = r#"{
2139 "asset": {"version": "2.0"},
2140 "extensionsUsed": ["KHR_draco_mesh_compression"],
2141 "extensionsRequired": ["KHR_draco_mesh_compression"],
2142 "meshes": [{
2143 "name": "TestMesh",
2144 "primitives": [{
2145 "attributes": {"POSITION": 0},
2146 "extensions": {
2147 "KHR_draco_mesh_compression": {
2148 "bufferView": 0,
2149 "attributes": {"POSITION": 0}
2150 }
2151 }
2152 }]
2153 }],
2154 "buffers": [{"byteLength": 3, "uri": "data:application/octet-stream;base64,AAAA"}],
2155 "bufferViews": [{"buffer": 0, "byteLength": 3}],
2156 "accessors": [{"componentType": 5126, "count": 1, "type": "VEC3"}]
2157 }"#;
2158
2159 let reader = GltfReader::from_gltf(json.as_bytes(), None).unwrap();
2160 assert!(reader.has_draco_extension());
2161 assert_eq!(reader.num_meshes(), 1);
2162
2163 let primitives = reader.draco_primitives();
2164 assert_eq!(primitives.len(), 1);
2165 assert_eq!(primitives[0].mesh_name, Some("TestMesh".to_string()));
2166 assert_eq!(primitives[0].buffer_view, 0);
2167 }
2168
2169 #[test]
2170 fn typed_khr_parser_rejects_malformed_schema_and_missing_side_fallback() {
2171 let base = serde_json::json!({
2172 "asset": {"version": "2.0"},
2173 "extensionsUsed": [KHR_DRACO_MESH_COMPRESSION],
2174 "extensionsRequired": [KHR_DRACO_MESH_COMPRESSION],
2175 "meshes": [{"primitives": [{
2176 "attributes": {"POSITION": 0},
2177 "extensions": {KHR_DRACO_MESH_COMPRESSION: {
2178 "bufferView": 0,
2179 "attributes": {"POSITION": 10}
2180 }}
2181 }]}],
2182 "buffers": [{"byteLength": 4, "uri": "data:;base64,AAAAAA=="}],
2183 "bufferViews": [{"buffer": 0, "byteLength": 4}],
2184 "accessors": [{"componentType": 5126, "count": 1, "type": "VEC3"}]
2185 });
2186
2187 let mut malformed = base.clone();
2188 malformed["meshes"][0]["primitives"][0]["extensions"][KHR_DRACO_MESH_COMPRESSION]
2189 ["unexpected"] = serde_json::json!(true);
2190 assert!(GltfReader::from_gltf(&serde_json::to_vec(&malformed).unwrap(), None).is_err());
2191
2192 let mut too_large = base.clone();
2193 too_large["meshes"][0]["primitives"][0]["extensions"][KHR_DRACO_MESH_COMPRESSION]
2194 ["attributes"]["POSITION"] = serde_json::Value::from(u64::from(u32::MAX) + 1);
2195 assert!(GltfReader::from_gltf(&serde_json::to_vec(&too_large).unwrap(), None).is_err());
2196
2197 let mut empty = base.clone();
2198 empty["meshes"][0]["primitives"][0]["extensions"][KHR_DRACO_MESH_COMPRESSION]
2199 ["attributes"] = serde_json::json!({});
2200 assert!(GltfReader::from_gltf(&serde_json::to_vec(&empty).unwrap(), None).is_err());
2201
2202 let mut side_attribute = base;
2203 side_attribute["meshes"][0]["primitives"][0]["attributes"]["NORMAL"] =
2204 serde_json::Value::from(1);
2205 side_attribute["accessors"]
2206 .as_array_mut()
2207 .unwrap()
2208 .push(serde_json::json!({
2209 "componentType": 5126,
2210 "count": 1,
2211 "type": "VEC3"
2212 }));
2213 assert!(
2214 GltfReader::from_gltf(&serde_json::to_vec(&side_attribute).unwrap(), None).is_err()
2215 );
2216 }
2217
2218 #[test]
2219 fn test_rejects_unknown_required_extension() {
2220 let json = r#"{
2221 "asset": {"version": "2.0"},
2222 "extensionsUsed": ["EXT_required"],
2223 "extensionsRequired": ["EXT_required"]
2224 }"#;
2225
2226 assert!(matches!(
2227 GltfReader::from_gltf(json.as_bytes(), None),
2228 Err(GltfError::Unsupported(_))
2229 ));
2230 }
2231
2232 #[test]
2233 fn test_rejects_draco_primitive_missing_extensions_used() {
2234 let json = r#"{
2235 "asset": {"version": "2.0"},
2236 "meshes": [{
2237 "primitives": [{
2238 "attributes": {"POSITION": 0},
2239 "extensions": {
2240 "KHR_draco_mesh_compression": {
2241 "bufferView": 0,
2242 "attributes": {"POSITION": 0}
2243 }
2244 }
2245 }]
2246 }],
2247 "buffers": [{"byteLength": 3, "uri": "data:application/octet-stream;base64,AAAA"}],
2248 "bufferViews": [{"buffer": 0, "byteLength": 3}],
2249 "accessors": []
2250 }"#;
2251
2252 assert!(matches!(
2253 GltfReader::from_gltf(json.as_bytes(), None),
2254 Err(GltfError::InvalidGltf(_))
2255 ));
2256 }
2257
2258 #[test]
2259 fn test_glb_open_loads_relative_external_buffer() {
2260 let dir = tempdir().unwrap();
2261 let bin_path = dir.path().join("mesh.bin");
2262 std::fs::write(&bin_path, triangle_positions()).unwrap();
2263
2264 let json = r#"{
2265 "asset": {"version": "2.0"},
2266 "buffers": [{"byteLength": 36, "uri": "mesh.bin"}],
2267 "bufferViews": [{"buffer": 0, "byteOffset": 0, "byteLength": 36}],
2268 "accessors": [{
2269 "bufferView": 0,
2270 "componentType": 5126,
2271 "count": 3,
2272 "type": "VEC3"
2273 }],
2274 "meshes": [{
2275 "primitives": [{
2276 "attributes": {"POSITION": 0},
2277 "mode": 4
2278 }]
2279 }]
2280 }"#;
2281 let glb = build_glb(json);
2282 let glb_path = dir.path().join("external.glb");
2283 std::fs::write(&glb_path, &glb).unwrap();
2284
2285 let reader = GltfReader::open(&glb_path).unwrap();
2286 let meshes = reader.decode_all_meshes().unwrap();
2287
2288 assert_eq!(meshes.len(), 1);
2289 assert_eq!(meshes[0].num_points(), 3);
2290 assert_eq!(meshes[0].num_faces(), 1);
2291 assert_eq!(
2292 read_attribute_bytes(&meshes[0], GeometryAttributeType::Position),
2293 triangle_positions()
2294 );
2295 }
2296
2297 #[test]
2298 fn test_from_glb_rejects_external_buffer_without_base_path() {
2299 let json = r#"{
2300 "asset": {"version": "2.0"},
2301 "buffers": [{"byteLength": 36, "uri": "mesh.bin"}],
2302 "bufferViews": [{"buffer": 0, "byteOffset": 0, "byteLength": 36}],
2303 "accessors": [],
2304 "meshes": []
2305 }"#;
2306
2307 let err = match GltfReader::from_glb(&build_glb(json)) {
2308 Ok(_) => panic!("external buffer unexpectedly loaded without a base path"),
2309 Err(err) => err,
2310 };
2311 assert!(matches!(err, GltfError::ExternalResourceDenied(_)));
2312 }
2313
2314 #[test]
2315 fn external_images_are_resolved_and_reported_without_reparsing() {
2316 let json = br#"{
2317 "asset": {"version": "2.0"},
2318 "images": [{"uri": "textures%2Falbedo.png"}]
2319 }"#;
2320 assert!(matches!(
2321 GltfReader::from_gltf(json, None),
2322 Err(GltfError::ExternalResourceDenied(uri)) if uri == "textures%2Falbedo.png"
2323 ));
2324
2325 let resolver = |uri: &str| -> Result<Vec<u8>> {
2326 if uri == "textures%2Falbedo.png" {
2327 Ok(vec![1, 2, 3])
2328 } else {
2329 Err(GltfError::ExternalResourceDenied(uri.to_owned()))
2330 }
2331 };
2332 let reader =
2333 GltfReader::from_bytes_with_resolver(json, &resolver, &ResourceLimits::default())
2334 .unwrap();
2335 assert_eq!(
2336 reader.document_metadata().external_resource_uris,
2337 ["textures%2Falbedo.png"]
2338 );
2339
2340 let limits = ResourceLimits {
2341 max_resource_bytes: Some(2),
2342 ..ResourceLimits::default()
2343 };
2344 assert!(matches!(
2345 GltfReader::from_bytes_with_resolver(json, &resolver, &limits),
2346 Err(GltfError::ResourceLimitExceeded(_))
2347 ));
2348 }
2349
2350 #[test]
2351 fn test_texcoord_unsigned_short_normalized_vec2() {
2352 let mut bytes = triangle_positions();
2353 let texcoords = [0u16, 0, 65535, 0, 0, 65535];
2354 bytes.extend(texcoords.into_iter().flat_map(u16::to_le_bytes));
2355 let data_uri = format!(
2356 "data:application/octet-stream;base64,{}",
2357 base64_for_test(&bytes)
2358 );
2359 let json = format!(
2360 r#"{{
2361 "asset": {{"version": "2.0"}},
2362 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2363 "bufferViews": [
2364 {{"buffer": 0, "byteOffset": 0, "byteLength": 36}},
2365 {{"buffer": 0, "byteOffset": 36, "byteLength": 12}}
2366 ],
2367 "accessors": [
2368 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}},
2369 {{
2370 "bufferView": 1,
2371 "componentType": 5123,
2372 "normalized": true,
2373 "count": 3,
2374 "type": "VEC2"
2375 }}
2376 ],
2377 "meshes": [{{
2378 "primitives": [{{
2379 "attributes": {{"POSITION": 0, "TEXCOORD_0": 1}},
2380 "mode": 4
2381 }}]
2382 }}]
2383 }}"#,
2384 bytes.len(),
2385 data_uri
2386 );
2387
2388 let mesh = GltfReader::from_gltf(json.as_bytes(), None)
2389 .unwrap()
2390 .decode_all_meshes()
2391 .unwrap()
2392 .remove(0);
2393 let texcoord = mesh
2394 .named_attribute(GeometryAttributeType::TexCoord)
2395 .expect("missing texcoord");
2396
2397 assert_eq!(texcoord.data_type(), DataType::Uint16);
2398 assert!(texcoord.normalized());
2399 assert_eq!(texcoord.num_components(), 2);
2400 assert_eq!(texcoord.buffer().data(), &bytes[36..48]);
2401 }
2402
2403 #[test]
2404 fn test_color_unsigned_byte_normalized_vec3() {
2405 let mut bytes = triangle_positions();
2406 let colors = [255u8, 0, 0, 0, 255, 0, 0, 0, 255];
2407 bytes.extend(colors);
2408 let data_uri = format!(
2409 "data:application/octet-stream;base64,{}",
2410 base64_for_test(&bytes)
2411 );
2412 let json = format!(
2413 r#"{{
2414 "asset": {{"version": "2.0"}},
2415 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2416 "bufferViews": [
2417 {{"buffer": 0, "byteOffset": 0, "byteLength": 36}},
2418 {{"buffer": 0, "byteOffset": 36, "byteLength": 9}}
2419 ],
2420 "accessors": [
2421 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}},
2422 {{
2423 "bufferView": 1,
2424 "componentType": 5121,
2425 "normalized": true,
2426 "count": 3,
2427 "type": "VEC3"
2428 }}
2429 ],
2430 "meshes": [{{
2431 "primitives": [{{
2432 "attributes": {{"POSITION": 0, "COLOR_0": 1}},
2433 "mode": 4
2434 }}]
2435 }}]
2436 }}"#,
2437 bytes.len(),
2438 data_uri
2439 );
2440
2441 let mesh = GltfReader::from_gltf(json.as_bytes(), None)
2442 .unwrap()
2443 .decode_all_meshes()
2444 .unwrap()
2445 .remove(0);
2446 let color = mesh
2447 .named_attribute(GeometryAttributeType::Color)
2448 .expect("missing color");
2449
2450 assert_eq!(color.data_type(), DataType::Uint8);
2451 assert!(color.normalized());
2452 assert_eq!(color.num_components(), 3);
2453 assert_eq!(color.buffer().data(), &bytes[36..45]);
2454 }
2455
2456 #[test]
2457 fn test_points_primitive_decodes_without_faces() {
2458 let indices = [2u16, 0];
2459 let mut bytes = triangle_positions();
2460 bytes.extend(indices.into_iter().flat_map(u16::to_le_bytes));
2461 let data_uri = format!(
2462 "data:application/octet-stream;base64,{}",
2463 base64_for_test(&bytes)
2464 );
2465 let json = format!(
2466 r#"{{
2467 "asset": {{"version": "2.0"}},
2468 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2469 "bufferViews": [
2470 {{"buffer": 0, "byteOffset": 0, "byteLength": 36}},
2471 {{"buffer": 0, "byteOffset": 36, "byteLength": 4}}
2472 ],
2473 "accessors": [
2474 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}},
2475 {{"bufferView": 1, "componentType": 5123, "count": 2, "type": "SCALAR"}}
2476 ],
2477 "meshes": [{{
2478 "primitives": [{{
2479 "attributes": {{"POSITION": 0}},
2480 "indices": 1,
2481 "mode": 0
2482 }}]
2483 }}]
2484 }}"#,
2485 bytes.len(),
2486 data_uri
2487 );
2488
2489 let mesh = GltfReader::from_gltf(json.as_bytes(), None)
2490 .unwrap()
2491 .decode_all_meshes()
2492 .unwrap()
2493 .remove(0);
2494
2495 assert_eq!(mesh.num_points(), 2);
2496 assert_eq!(mesh.num_faces(), 0);
2497 let positions = read_attribute_bytes(&mesh, GeometryAttributeType::Position);
2498 assert_eq!(&positions[0..12], &triangle_positions()[24..36]);
2499 assert_eq!(&positions[12..24], &triangle_positions()[0..12]);
2500 }
2501
2502 #[cfg(feature = "gltf-writer")]
2503 #[test]
2504 fn test_writer_glb_roundtrips_through_reader() {
2505 let dir = tempdir().unwrap();
2506 let path = dir.path().join("roundtrip.glb");
2507
2508 let mut writer = crate::gltf_writer::GltfWriter::new();
2509 writer
2510 .add_draco_mesh(&triangle_mesh(), Some("triangle"), None)
2511 .unwrap();
2512 writer.write_glb(&path).unwrap();
2513
2514 let reader = GltfReader::open(&path).unwrap();
2515 let primitives = reader.draco_primitives();
2516 assert_eq!(primitives.len(), 1);
2517 assert_eq!(primitives[0].attributes.get("POSITION"), Some(&0));
2518
2519 let decoded = reader.decode_all_meshes().unwrap().remove(0);
2520 let position = decoded
2521 .named_attribute(GeometryAttributeType::Position)
2522 .expect("missing position");
2523 assert_eq!(position.data_type(), DataType::Float32);
2524 assert_eq!(position.num_components(), 3);
2525 assert_eq!(decoded.num_faces(), 1);
2526 }
2527
2528 #[cfg(feature = "gltf-writer")]
2529 #[test]
2530 fn test_draco_decode_rejects_extension_attribute_not_in_primitive_attributes() {
2531 let mut value = writer_gltf_json_value();
2532 value["meshes"][0]["primitives"][0]["extensions"]["KHR_draco_mesh_compression"]
2533 ["attributes"]["TEXCOORD_0"] = serde_json::json!(0);
2534 let json = serde_json::to_string(&value).unwrap();
2535
2536 let err = match GltfReader::from_gltf(json.as_bytes(), None) {
2537 Err(error) => error,
2538 Ok(_) => panic!("malformed extension unexpectedly parsed"),
2539 };
2540 assert!(matches!(err, GltfError::InvalidGltf(_)));
2541 }
2542
2543 #[cfg(feature = "gltf-writer")]
2544 #[test]
2545 fn test_draco_decode_rejects_accessor_metadata_mismatch_and_sparse() {
2546 let mut count_mismatch = writer_gltf_json_value();
2547 let pos_accessor = count_mismatch["meshes"][0]["primitives"][0]["attributes"]["POSITION"]
2548 .as_u64()
2549 .unwrap() as usize;
2550 count_mismatch["accessors"][pos_accessor]["count"] = serde_json::json!(99);
2551 let json = serde_json::to_string(&count_mismatch).unwrap();
2552 let err = GltfReader::from_gltf(json.as_bytes(), None)
2553 .unwrap()
2554 .decode_all_meshes()
2555 .unwrap_err();
2556 assert!(matches!(err, GltfError::InvalidGltf(_)));
2557
2558 let mut sparse = writer_gltf_json_value();
2559 let pos_accessor = sparse["meshes"][0]["primitives"][0]["attributes"]["POSITION"]
2560 .as_u64()
2561 .unwrap() as usize;
2562 sparse["accessors"][pos_accessor]["sparse"] = serde_json::json!({
2563 "count": 1,
2564 "indices": {"bufferView": 0, "componentType": 5123},
2565 "values": {"bufferView": 0}
2566 });
2567 let json = serde_json::to_string(&sparse).unwrap();
2568 let err = GltfReader::from_gltf(json.as_bytes(), None)
2569 .unwrap()
2570 .decode_all_meshes()
2571 .unwrap_err();
2572 assert!(matches!(err, GltfError::Unsupported(_)));
2573 }
2574
2575 #[test]
2576 fn test_standard_triangle_rejects_out_of_bounds_indices() {
2577 let indices = [0u16, 1, 3];
2578 let mut bytes = triangle_positions();
2579 bytes.extend(indices.into_iter().flat_map(u16::to_le_bytes));
2580 let data_uri = format!(
2581 "data:application/octet-stream;base64,{}",
2582 base64_for_test(&bytes)
2583 );
2584 let json = format!(
2585 r#"{{
2586 "asset": {{"version": "2.0"}},
2587 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2588 "bufferViews": [
2589 {{"buffer": 0, "byteOffset": 0, "byteLength": 36}},
2590 {{"buffer": 0, "byteOffset": 36, "byteLength": 6}}
2591 ],
2592 "accessors": [
2593 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}},
2594 {{"bufferView": 1, "componentType": 5123, "count": 3, "type": "SCALAR"}}
2595 ],
2596 "meshes": [{{
2597 "primitives": [{{
2598 "attributes": {{"POSITION": 0}},
2599 "indices": 1,
2600 "mode": 4
2601 }}]
2602 }}]
2603 }}"#,
2604 bytes.len(),
2605 data_uri
2606 );
2607
2608 let err = GltfReader::from_gltf(json.as_bytes(), None)
2609 .unwrap()
2610 .decode_all_meshes()
2611 .unwrap_err();
2612 assert!(matches!(err, GltfError::InvalidGltf(_)));
2613 }
2614
2615 #[test]
2616 fn semantic_and_index_normalized_contracts_are_strict() {
2617 fn document_with_attribute(
2618 semantic: &str,
2619 accessor_type: &str,
2620 component_type: u32,
2621 normalized: bool,
2622 ) -> Vec<u8> {
2623 let components = match accessor_type {
2624 "VEC3" => 3,
2625 "VEC4" => 4,
2626 _ => 1,
2627 };
2628 let component_size = if component_type == GLTF_COMPONENT_FLOAT {
2629 4
2630 } else {
2631 1
2632 };
2633 let mut bytes = triangle_positions();
2634 let extra_offset = bytes.len();
2635 bytes.resize(extra_offset + 3 * components * component_size, 0);
2636 let document = serde_json::json!({
2637 "asset": {"version": "2.0"},
2638 "buffers": [{
2639 "byteLength": bytes.len(),
2640 "uri": format!("data:application/octet-stream;base64,{}", base64_for_test(&bytes))
2641 }],
2642 "bufferViews": [
2643 {"buffer": 0, "byteOffset": 0, "byteLength": 36},
2644 {"buffer": 0, "byteOffset": extra_offset, "byteLength": bytes.len() - extra_offset}
2645 ],
2646 "accessors": [
2647 {"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"},
2648 {"bufferView": 1, "componentType": component_type, "normalized": normalized,
2649 "count": 3, "type": accessor_type}
2650 ],
2651 "meshes": [{"primitives": [{
2652 "attributes": {"POSITION": 0, (semantic): 1}, "mode": 4
2653 }]}]
2654 });
2655 serde_json::to_vec(&document).unwrap()
2656 }
2657
2658 for document in [
2659 document_with_attribute("TANGENT", "VEC3", 5126, false),
2660 document_with_attribute("JOINTS_0", "VEC4", 5126, false),
2661 document_with_attribute("WEIGHTS_0", "VEC4", 5121, false),
2662 ] {
2663 let error = GltfReader::from_bytes_lenient(&document)
2664 .unwrap()
2665 .decode_all_meshes()
2666 .unwrap_err();
2667 assert!(matches!(
2668 error,
2669 GltfError::InvalidGltf(_) | GltfError::Unsupported(_)
2670 ));
2671 }
2672
2673 let mut bytes = triangle_positions();
2674 let indices_offset = bytes.len();
2675 bytes.extend([0u16, 1, 2].into_iter().flat_map(u16::to_le_bytes));
2676 let document = serde_json::json!({
2677 "asset": {"version": "2.0"},
2678 "buffers": [{
2679 "byteLength": bytes.len(),
2680 "uri": format!("data:application/octet-stream;base64,{}", base64_for_test(&bytes))
2681 }],
2682 "bufferViews": [
2683 {"buffer": 0, "byteOffset": 0, "byteLength": 36},
2684 {"buffer": 0, "byteOffset": indices_offset, "byteLength": 6}
2685 ],
2686 "accessors": [
2687 {"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"},
2688 {"bufferView": 1, "componentType": 5123, "normalized": true,
2689 "count": 3, "type": "SCALAR"}
2690 ],
2691 "meshes": [{"primitives": [{
2692 "attributes": {"POSITION": 0}, "indices": 1, "mode": 4
2693 }]}]
2694 });
2695 let error = GltfReader::from_bytes_lenient(&serde_json::to_vec(&document).unwrap())
2696 .unwrap()
2697 .decode_all_meshes()
2698 .unwrap_err();
2699 assert!(matches!(error, GltfError::InvalidGltf(_)));
2700 }
2701
2702 #[test]
2703 fn lenient_reader_validates_scene_animation_and_skin_references() {
2704 let valid = serde_json::json!({
2705 "asset": {"version": "2.0"},
2706 "scene": 0,
2707 "scenes": [{"nodes": [0]}],
2708 "nodes": [{"mesh": 0, "skin": 0, "children": [1]}, {}],
2709 "meshes": [{"primitives": [{"attributes": {"POSITION": 0}, "mode": 4}]}],
2710 "accessors": [
2711 {"componentType": 5126, "count": 3, "type": "VEC3"},
2712 {"componentType": 5126, "count": 1, "type": "MAT4"},
2713 {"componentType": 5126, "count": 1, "type": "SCALAR"},
2714 {"componentType": 5126, "count": 1, "type": "VEC3"}
2715 ],
2716 "skins": [{"inverseBindMatrices": 1, "skeleton": 0, "joints": [0]}],
2717 "animations": [{
2718 "samplers": [{"input": 2, "output": 3}],
2719 "channels": [{"sampler": 0, "target": {"node": 0, "path": "translation"}}]
2720 }]
2721 });
2722 let valid_bytes = serde_json::to_vec(&valid).unwrap();
2723 GltfReader::from_bytes_lenient(&valid_bytes).unwrap();
2724 assert!(matches!(
2725 GltfReader::from_bytes(&valid_bytes),
2726 Err(GltfError::Unsupported(_))
2727 ));
2728
2729 for path in [
2730 "/scene",
2731 "/scenes/0/nodes/0",
2732 "/nodes/0/mesh",
2733 "/nodes/0/children/0",
2734 "/nodes/0/skin",
2735 "/skins/0/joints/0",
2736 "/skins/0/inverseBindMatrices",
2737 "/animations/0/samplers/0/input",
2738 "/animations/0/samplers/0/output",
2739 "/animations/0/channels/0/sampler",
2740 "/animations/0/channels/0/target/node",
2741 ] {
2742 let mut invalid = valid.clone();
2743 *invalid.pointer_mut(path).unwrap() = Value::from(99);
2744 let invalid_bytes = serde_json::to_vec(&invalid).unwrap();
2745 let error = match GltfReader::from_bytes_lenient(&invalid_bytes) {
2746 Ok(_) => panic!("invalid reference at {path} unexpectedly parsed"),
2747 Err(error) => error,
2748 };
2749 assert!(
2750 matches!(error, GltfError::InvalidGltf(_)),
2751 "path {path}: {error}"
2752 );
2753 }
2754 }
2755
2756 #[cfg(feature = "legacy-bitstream-decode")]
2757 #[test]
2758 fn test_draco_legacy_bitstream_data_uri() {
2759 let draco_bytes =
2760 include_bytes!("../../../testdata/legacy_draco/cube_att.mesh_seq.1.1.0.drc");
2761 let data_uri = format!(
2762 "data:application/octet-stream;base64,{}",
2763 base64_for_test(draco_bytes)
2764 );
2765 let json = format!(
2766 r#"{{
2767 "asset": {{"version": "2.0"}},
2768 "extensionsUsed": ["KHR_draco_mesh_compression"],
2769 "buffers": [{{"byteLength": {}, "uri": "{}"}}],
2770 "bufferViews": [{{"buffer": 0, "byteOffset": 0, "byteLength": {}}}],
2771 "accessors": [
2772 {{"componentType": 5126, "count": 24, "type": "VEC3"}}
2773 ],
2774 "meshes": [{{
2775 "primitives": [{{
2776 "attributes": {{"POSITION": 0}},
2777 "mode": 4,
2778 "extensions": {{
2779 "KHR_draco_mesh_compression": {{
2780 "bufferView": 0,
2781 "attributes": {{"POSITION": 0}}
2782 }}
2783 }}
2784 }}]
2785 }}]
2786 }}"#,
2787 draco_bytes.len(),
2788 data_uri,
2789 draco_bytes.len()
2790 );
2791
2792 let mesh = GltfReader::from_gltf(json.as_bytes(), None)
2793 .unwrap()
2794 .decode_all_draco_meshes()
2795 .unwrap()
2796 .remove(0)
2797 .1;
2798
2799 assert_eq!(mesh.num_faces(), 12);
2800 assert_eq!(mesh.num_points(), 24);
2801 assert_eq!(
2802 mesh.named_attribute(GeometryAttributeType::Position)
2803 .expect("missing position")
2804 .size(),
2805 24
2806 );
2807 }
2808
2809 fn base64_for_test(bytes: &[u8]) -> String {
2810 const TABLE: &[u8; 64] =
2811 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2812 let mut out = String::new();
2813
2814 for chunk in bytes.chunks(3) {
2815 let b0 = chunk[0];
2816 let b1 = *chunk.get(1).unwrap_or(&0);
2817 let b2 = *chunk.get(2).unwrap_or(&0);
2818 let n = ((b0 as u32) << 16) | ((b1 as u32) << 8) | b2 as u32;
2819
2820 out.push(TABLE[((n >> 18) & 0x3f) as usize] as char);
2821 out.push(TABLE[((n >> 12) & 0x3f) as usize] as char);
2822 if chunk.len() > 1 {
2823 out.push(TABLE[((n >> 6) & 0x3f) as usize] as char);
2824 } else {
2825 out.push('=');
2826 }
2827 if chunk.len() > 2 {
2828 out.push(TABLE[(n & 0x3f) as usize] as char);
2829 } else {
2830 out.push('=');
2831 }
2832 }
2833
2834 out
2835 }
2836}