1use std::marker::PhantomData;
7
8use crate::json::Value;
9
10use crate::{Error, Result};
11
12macro_rules! index {
13 ($name:ident) => {
14 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
15 #[doc = concat!("Typed index into the glTF `", stringify!($name), "[]` array.")]
16 pub struct $name(pub usize);
17 impl $name {
18 pub const fn index(self) -> usize {
20 self.0
21 }
22 }
23 };
24}
25
26index!(AccessorIndex);
27index!(AnimationIndex);
28index!(BufferIndex);
29index!(BufferViewIndex);
30index!(CameraIndex);
31index!(ExternalAssetIndex);
32index!(FileIndex);
33index!(ImageIndex);
34index!(MaterialIndex);
35index!(MeshIndex);
36index!(NodeIndex);
37index!(SamplerIndex);
38index!(SceneIndex);
39index!(ShapeIndex);
40index!(SkinIndex);
41index!(TextureIndex);
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum ValidationProfile {
46 Gltf20,
48 Gltf21Draft,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum ComponentType {
55 I8 = 5120,
57 U8 = 5121,
59 I16 = 5122,
61 U16 = 5123,
63 U32 = 5125,
65 F32 = 5126,
67 I32 = 5124,
69 F16 = 5131,
71 F64 = 5130,
73 I64 = 5134,
75 U64 = 5135,
77}
78
79impl ComponentType {
80 pub fn from_gltf(value: u64) -> Option<Self> {
82 Some(match value {
83 5120 => Self::I8,
84 5121 => Self::U8,
85 5122 => Self::I16,
86 5123 => Self::U16,
87 5125 => Self::U32,
88 5126 => Self::F32,
89 5124 => Self::I32,
90 5131 => Self::F16,
91 5130 => Self::F64,
92 5134 => Self::I64,
93 5135 => Self::U64,
94 _ => return None,
95 })
96 }
97
98 pub const fn byte_width(self) -> usize {
100 match self {
101 Self::I8 | Self::U8 => 1,
102 Self::I16 | Self::U16 | Self::F16 => 2,
103 Self::I32 | Self::U32 | Self::F32 => 4,
104 Self::F64 | Self::I64 | Self::U64 => 8,
105 }
106 }
107
108 pub const fn to_gltf(self) -> u32 {
110 self as u32
111 }
112}
113
114#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
116pub struct PrimitiveIndex {
117 pub mesh: MeshIndex,
119 pub primitive: usize,
121}
122
123impl PrimitiveIndex {
124 pub const fn new(mesh: MeshIndex, primitive: usize) -> Self {
126 Self { mesh, primitive }
127 }
128}
129
130#[derive(Clone, Debug)]
132pub struct Document {
133 root: Value,
134 original_json: Option<Vec<u8>>,
135}
136
137impl Document {
138 pub fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
148 let root = Value::parse(bytes).map_err(Error::Json)?;
149 if !root.is_object() {
150 return Err(Error::Validation(vec!["glTF root is not an object".into()]));
151 }
152 Ok(Self {
153 root,
154 original_json: Some(bytes.to_vec()),
155 })
156 }
157
158 pub fn from_value(root: Value) -> Result<Self> {
160 if !root.is_object() {
161 return Err(Error::Validation(vec!["glTF root is not an object".into()]));
162 }
163 Ok(Self {
164 root,
165 original_json: None,
166 })
167 }
168
169 pub fn as_value(&self) -> &Value {
171 &self.root
172 }
173
174 pub fn as_value_mut(&mut self) -> &mut Value {
176 self.original_json = None;
177 &mut self.root
178 }
179
180 pub fn to_json_bytes(&self) -> Result<Vec<u8>> {
190 match &self.original_json {
191 Some(bytes) => Ok(bytes.clone()),
192 None => Ok(self.root.to_vec()),
193 }
194 }
195
196 pub fn to_minified_json_bytes(&self) -> Vec<u8> {
214 self.root.to_vec()
215 }
216
217 pub fn validate(&self, profile: ValidationProfile) -> Result<()> {
223 let asset = self
224 .root
225 .get("asset")
226 .ok_or_else(|| Error::Validation(vec!["asset is missing or not an object".into()]))?;
227 let version = asset
228 .get("version")
229 .and_then(Value::as_str)
230 .ok_or_else(|| {
231 Error::Validation(vec!["asset.version is missing or not a string".into()])
232 })?;
233 match profile {
234 ValidationProfile::Gltf20 if !version.starts_with("2.0") => {
235 return Err(Error::Validation(vec![format!(
236 "asset.version {version:?} is not glTF 2.0"
237 )]))
238 }
239 ValidationProfile::Gltf21Draft if !version.starts_with("2.") => {
240 return Err(Error::Validation(vec![format!(
241 "asset.version {version:?} is not glTF 2.x"
242 )]))
243 }
244 _ => {}
245 }
246 for name in [
247 "accessors",
248 "animations",
249 "buffers",
250 "bufferViews",
251 "cameras",
252 "externalAssets",
253 "files",
254 "images",
255 "materials",
256 "meshes",
257 "nodes",
258 "samplers",
259 "scenes",
260 "shapes",
261 "skins",
262 "textures",
263 ] {
264 if let Some(value) = self.root.get(name) {
265 let array = value
266 .as_array()
267 .ok_or_else(|| Error::Validation(vec![format!("{name} is not an array")]))?;
268 if array.iter().any(|object| !object.is_object()) {
269 return Err(Error::Validation(vec![format!(
270 "{name} contains a non-object entry"
271 )]));
272 }
273 }
274 }
275 if profile == ValidationProfile::Gltf20
276 && (self.root.get("externalAssets").is_some()
277 || self.root.get("files").is_some()
278 || self.root.get("shapes").is_some())
279 {
280 return Err(Error::Validation(vec![
281 "glTF 2.1 fields require the draft profile".into(),
282 ]));
283 }
284 #[cfg(feature = "strict-validation")]
285 validate_references(&self.root, profile)?;
286 Ok(())
287 }
288
289 pub fn accessors(&self) -> Objects<'_, AccessorIndex> {
291 self.objects("accessors")
292 }
293 pub fn accessor(&self, index: AccessorIndex) -> Option<Accessor<'_>> {
295 self.accessors().get(index).map(Accessor)
296 }
297 pub fn animations(&self) -> Objects<'_, AnimationIndex> {
299 self.objects("animations")
300 }
301 pub fn animation(&self, index: AnimationIndex) -> Option<Animation<'_>> {
303 self.animations().get(index).map(Animation)
304 }
305 pub fn buffers(&self) -> Objects<'_, BufferIndex> {
307 self.objects("buffers")
308 }
309 pub fn buffer(&self, index: BufferIndex) -> Option<Buffer<'_>> {
311 self.buffers().get(index).map(Buffer)
312 }
313 pub fn buffer_views(&self) -> Objects<'_, BufferViewIndex> {
315 self.objects("bufferViews")
316 }
317 pub fn buffer_view(&self, index: BufferViewIndex) -> Option<BufferView<'_>> {
319 self.buffer_views().get(index).map(BufferView)
320 }
321 pub fn cameras(&self) -> Objects<'_, CameraIndex> {
323 self.objects("cameras")
324 }
325 pub fn external_assets(&self) -> Objects<'_, ExternalAssetIndex> {
327 self.objects("externalAssets")
328 }
329 pub fn external_asset(&self, index: ExternalAssetIndex) -> Option<ExternalAsset<'_>> {
331 self.external_assets().get(index).map(ExternalAsset)
332 }
333 pub fn camera(&self, index: CameraIndex) -> Option<Camera<'_>> {
335 self.cameras().get(index).map(Camera)
336 }
337 pub fn files(&self) -> Objects<'_, FileIndex> {
339 self.objects("files")
340 }
341 pub fn file(&self, index: FileIndex) -> Option<File<'_>> {
343 self.files().get(index).map(File)
344 }
345 pub fn images(&self) -> Objects<'_, ImageIndex> {
347 self.objects("images")
348 }
349 pub fn image(&self, index: ImageIndex) -> Option<Image<'_>> {
351 self.images().get(index).map(Image)
352 }
353 pub fn materials(&self) -> Objects<'_, MaterialIndex> {
355 self.objects("materials")
356 }
357 pub fn material(&self, index: MaterialIndex) -> Option<Material<'_>> {
359 self.materials().get(index).map(Material)
360 }
361 pub fn meshes(&self) -> Objects<'_, MeshIndex> {
363 self.objects("meshes")
364 }
365 pub fn mesh(&self, index: MeshIndex) -> Option<Mesh<'_>> {
367 self.meshes().get(index).map(Mesh)
368 }
369 pub fn nodes(&self) -> Objects<'_, NodeIndex> {
371 self.objects("nodes")
372 }
373 pub fn node(&self, index: NodeIndex) -> Option<Node<'_>> {
375 self.nodes().get(index).map(Node)
376 }
377 pub fn samplers(&self) -> Objects<'_, SamplerIndex> {
379 self.objects("samplers")
380 }
381 pub fn sampler(&self, index: SamplerIndex) -> Option<Sampler<'_>> {
383 self.samplers().get(index).map(Sampler)
384 }
385 pub fn scenes(&self) -> Objects<'_, SceneIndex> {
387 self.objects("scenes")
388 }
389 pub fn scene(&self, index: SceneIndex) -> Option<Scene<'_>> {
391 self.scenes().get(index).map(Scene)
392 }
393 pub fn default_scene(&self) -> Option<SceneIndex> {
395 index_value(&self.root, "scene").map(SceneIndex)
396 }
397 pub fn thumbnail(&self) -> Option<ImageIndex> {
399 self.root
400 .get("asset")
401 .and_then(|asset| index_value(asset, "thumbnail"))
402 .map(ImageIndex)
403 }
404 pub fn shapes(&self) -> Objects<'_, ShapeIndex> {
406 self.objects("shapes")
407 }
408 pub fn shape(&self, index: ShapeIndex) -> Option<Shape<'_>> {
410 self.shapes().get(index).map(Shape)
411 }
412 pub fn skins(&self) -> Objects<'_, SkinIndex> {
414 self.objects("skins")
415 }
416 pub fn skin(&self, index: SkinIndex) -> Option<Skin<'_>> {
418 self.skins().get(index).map(Skin)
419 }
420 pub fn textures(&self) -> Objects<'_, TextureIndex> {
422 self.objects("textures")
423 }
424 pub fn texture(&self, index: TextureIndex) -> Option<Texture<'_>> {
426 self.textures().get(index).map(Texture)
427 }
428
429 pub fn primitive(&self, mesh: MeshIndex, primitive: usize) -> Option<PrimitiveRef<'_>> {
431 self.meshes()
432 .get(mesh)?
433 .value()
434 .get("primitives")?
435 .as_array()?
436 .get(primitive)?;
437 Some(PrimitiveRef {
438 document: self,
439 mesh,
440 primitive,
441 })
442 }
443
444 fn objects<I>(&self, key: &'static str) -> Objects<'_, I> {
445 Objects {
446 values: self.root.get(key).and_then(Value::as_array).unwrap_or(&[]),
447 marker: PhantomData,
448 }
449 }
450}
451
452#[cfg(feature = "strict-validation")]
453fn validate_references(root: &Value, profile: ValidationProfile) -> Result<()> {
454 let len = |name: &str| -> usize {
455 root.get(name)
456 .and_then(Value::as_array)
457 .map_or(0, <[Value]>::len)
458 };
459 let check = |value: &Value, field: &str, target: &str| -> Result<()> {
460 if let Some(raw) = value.get(field) {
461 let index = raw
462 .as_u64()
463 .ok_or_else(|| Error::Validation(vec![format!("{field} is not an index")]))?;
464 let index = usize::try_from(index).map_err(|_| {
465 Error::Validation(vec![format!("{field} does not fit the platform index")])
466 })?;
467 if index >= len(target) {
468 return Err(Error::Validation(vec![format!(
469 "{field} references missing {target}[{index}]"
470 )]));
471 }
472 }
473 Ok(())
474 };
475 let required_index = |value: &Value, field: &str, target: &str| -> Result<()> {
476 if value.get(field).is_none() {
477 return Err(Error::Validation(vec![format!("{field} is missing")]));
478 }
479 check(value, field, target)
480 };
481 for view in root
482 .get("bufferViews")
483 .and_then(Value::as_array)
484 .unwrap_or(&[])
485 {
486 check(view, "buffer", "buffers")?;
487 }
488 for accessor in root
489 .get("accessors")
490 .and_then(Value::as_array)
491 .unwrap_or(&[])
492 {
493 check(accessor, "bufferView", "bufferViews")?;
494 let component = accessor
495 .get("componentType")
496 .and_then(Value::as_u64)
497 .ok_or_else(|| Error::Validation(vec!["accessor componentType is missing".into()]))?;
498 let component = ComponentType::from_gltf(component).ok_or_else(|| {
499 Error::Validation(vec![format!(
500 "unsupported accessor componentType {component}"
501 )])
502 })?;
503 if profile == ValidationProfile::Gltf20
504 && !matches!(
505 component,
506 ComponentType::I8
507 | ComponentType::U8
508 | ComponentType::I16
509 | ComponentType::U16
510 | ComponentType::U32
511 | ComponentType::F32
512 )
513 {
514 return Err(Error::Validation(vec![format!(
515 "accessor componentType {component:?} requires the glTF 2.1 draft profile"
516 )]));
517 }
518 let kind = accessor
519 .get("type")
520 .and_then(Value::as_str)
521 .ok_or_else(|| Error::Validation(vec!["accessor type is missing".into()]))?;
522 if !matches!(
523 kind,
524 "SCALAR" | "VEC2" | "VEC3" | "VEC4" | "MAT2" | "MAT3" | "MAT4"
525 ) {
526 return Err(Error::Validation(vec![format!(
527 "unsupported accessor type {kind:?}"
528 )]));
529 }
530 if accessor.get("count").and_then(Value::as_u64).is_none() {
531 return Err(Error::Validation(vec![
532 "accessor count is missing or invalid".into(),
533 ]));
534 }
535 }
536 for image in root.get("images").and_then(Value::as_array).unwrap_or(&[]) {
537 check(image, "bufferView", "bufferViews")?;
538 }
539 if let Some(asset) = root.get("asset") {
540 check(asset, "thumbnail", "images")?;
541 }
542 for file in root.get("files").and_then(Value::as_array).unwrap_or(&[]) {
543 if file.get("mimeType").and_then(Value::as_str).is_none() {
544 return Err(Error::Validation(vec![
545 "file mimeType is missing or not a string".into(),
546 ]));
547 }
548 let has_uri = match file.get("uri") {
549 Some(value) if value.as_str().is_some() => true,
550 Some(_) => {
551 return Err(Error::Validation(vec!["file uri is not a string".into()]));
552 }
553 None => false,
554 };
555 let has_buffer_view = match file.get("bufferView") {
556 Some(value) if value.as_u64().is_some() => {
557 check(file, "bufferView", "bufferViews")?;
558 true
559 }
560 Some(_) => {
561 return Err(Error::Validation(vec![
562 "file bufferView is not an index".into()
563 ]));
564 }
565 None => false,
566 };
567 if has_uri == has_buffer_view {
568 return Err(Error::Validation(vec![
569 "file must contain exactly one of uri or bufferView".into(),
570 ]));
571 }
572 }
573 for asset in root
574 .get("externalAssets")
575 .and_then(Value::as_array)
576 .unwrap_or(&[])
577 {
578 if asset.get("file").and_then(Value::as_u64).is_none() {
579 return Err(Error::Validation(vec![
580 "external asset file is missing or not an index".into(),
581 ]));
582 }
583 check(asset, "file", "files")?;
584 }
585 for texture in root
586 .get("textures")
587 .and_then(Value::as_array)
588 .unwrap_or(&[])
589 {
590 check(texture, "sampler", "samplers")?;
591 check(texture, "source", "images")?;
592 }
593 for mesh in root.get("meshes").and_then(Value::as_array).unwrap_or(&[]) {
594 for primitive in mesh
595 .get("primitives")
596 .and_then(Value::as_array)
597 .unwrap_or(&[])
598 {
599 check(primitive, "indices", "accessors")?;
600 check(primitive, "material", "materials")?;
601 if let Some(attributes) = primitive.get("attributes").and_then(Value::as_object) {
602 for (semantic, index) in attributes {
603 let index = index.as_u64().ok_or_else(|| {
604 Error::Validation(vec![format!(
605 "attribute {semantic} is not an accessor index"
606 )])
607 })?;
608 if usize::try_from(index)
609 .ok()
610 .is_none_or(|index| index >= len("accessors"))
611 {
612 return Err(Error::Validation(vec![format!(
613 "attribute {semantic} references missing accessors[{index}]"
614 )]));
615 }
616 if semantic == "POSITION" {
617 validate_position_accessor(
618 root,
619 usize::try_from(index).expect("accessor index was range checked"),
620 )?;
621 }
622 }
623 }
624 }
625 }
626 for node in root.get("nodes").and_then(Value::as_array).unwrap_or(&[]) {
627 for field in ["camera", "mesh", "skin"] {
628 let target = match field {
629 "camera" => "cameras",
630 "mesh" => "meshes",
631 _ => "skins",
632 };
633 check(node, field, target)?;
634 }
635 check(node, "externalAsset", "externalAssets")?;
636 if let Some(volume) = node.get("boundingVolume") {
637 if !volume.is_object() {
638 return Err(Error::Validation(vec![
639 "node boundingVolume is not an object".into(),
640 ]));
641 }
642 if volume.get("shape").and_then(Value::as_u64).is_none() {
643 return Err(Error::Validation(vec![
644 "node boundingVolume shape is missing or not an index".into(),
645 ]));
646 }
647 check(volume, "shape", "shapes")?;
648 }
649 if let Some(children) = node.get("children").and_then(Value::as_array) {
650 for child in children {
651 if child.as_u64().is_none_or(|index| {
652 usize::try_from(index)
653 .ok()
654 .is_none_or(|index| index >= len("nodes"))
655 }) {
656 return Err(Error::Validation(vec![
657 "node child references a missing node".into(),
658 ]));
659 }
660 }
661 }
662 }
663 for scene in root.get("scenes").and_then(Value::as_array).unwrap_or(&[]) {
664 if let Some(nodes) = scene.get("nodes").and_then(Value::as_array) {
665 for node in nodes {
666 if node.as_u64().is_none_or(|index| {
667 usize::try_from(index)
668 .ok()
669 .is_none_or(|index| index >= len("nodes"))
670 }) {
671 return Err(Error::Validation(vec![
672 "scene references a missing node".into()
673 ]));
674 }
675 }
676 }
677 }
678 validate_node_hierarchy(root)?;
679 check(root, "scene", "scenes")?;
680 for skin in root.get("skins").and_then(Value::as_array).unwrap_or(&[]) {
681 check(skin, "inverseBindMatrices", "accessors")?;
682 check(skin, "skeleton", "nodes")?;
683 if let Some(joints) = skin.get("joints").and_then(Value::as_array) {
684 for joint in joints {
685 if joint.as_u64().is_none_or(|index| {
686 usize::try_from(index)
687 .ok()
688 .is_none_or(|index| index >= len("nodes"))
689 }) {
690 return Err(Error::Validation(vec![
691 "skin references a missing joint node".into(),
692 ]));
693 }
694 }
695 }
696 }
697 for mesh in root.get("meshes").and_then(Value::as_array).unwrap_or(&[]) {
698 for primitive in mesh
699 .get("primitives")
700 .and_then(Value::as_array)
701 .unwrap_or(&[])
702 {
703 for target in primitive
704 .get("targets")
705 .and_then(Value::as_array)
706 .unwrap_or(&[])
707 {
708 if let Some(attributes) = target.as_object() {
709 for (semantic, index) in attributes {
710 if index.as_u64().is_none_or(|index| {
711 usize::try_from(index)
712 .ok()
713 .is_none_or(|index| index >= len("accessors"))
714 }) {
715 return Err(Error::Validation(vec![format!(
716 "morph target attribute {semantic} references a missing accessor"
717 )]));
718 }
719 }
720 }
721 }
722 }
723 }
724 for animation in root
725 .get("animations")
726 .and_then(Value::as_array)
727 .unwrap_or(&[])
728 {
729 let samplers = animation
730 .get("samplers")
731 .and_then(Value::as_array)
732 .unwrap_or(&[]);
733 for sampler in samplers {
734 required_index(sampler, "input", "accessors")?;
735 required_index(sampler, "output", "accessors")?;
736 if let Some(interpolation) = sampler.get("interpolation").and_then(Value::as_str) {
737 if !matches!(interpolation, "LINEAR" | "STEP" | "CUBICSPLINE") {
738 return Err(Error::Validation(vec![format!(
739 "animation sampler interpolation {interpolation:?} is invalid"
740 )]));
741 }
742 }
743 }
744 for channel in animation
745 .get("channels")
746 .and_then(Value::as_array)
747 .unwrap_or(&[])
748 {
749 let sampler = channel.get("sampler").and_then(Value::as_u64);
750 if sampler.is_none_or(|index| {
751 usize::try_from(index)
752 .ok()
753 .is_none_or(|index| index >= samplers.len())
754 }) {
755 return Err(Error::Validation(vec![
756 "animation channel references a missing sampler".into(),
757 ]));
758 }
759 if let Some(target) = channel.get("target") {
760 check(target, "node", "nodes")?;
761 let path = target.get("path").and_then(Value::as_str).ok_or_else(|| {
762 Error::Validation(vec!["animation channel target path is missing".into()])
763 })?;
764 let pointed = path == "pointer"
771 && target
772 .get("extensions")
773 .and_then(|extensions| extensions.get("KHR_animation_pointer"))
774 .is_some();
775 if !pointed && !matches!(path, "translation" | "rotation" | "scale" | "weights") {
776 return Err(Error::Validation(vec![format!(
777 "animation channel target path {path:?} is invalid"
778 )]));
779 }
780 } else {
781 return Err(Error::Validation(vec![
782 "animation channel target is missing".into(),
783 ]));
784 }
785 }
786 }
787 validate_draco_extension(root, &check)?;
788 if profile == ValidationProfile::Gltf21Draft {
789 validate_shapes(root)?;
790 validate_uids(root)?;
791 }
792 Ok(())
793}
794
795#[cfg(feature = "strict-validation")]
796fn validate_position_accessor(root: &Value, index: usize) -> Result<()> {
797 let accessor = root
798 .get("accessors")
799 .and_then(Value::as_array)
800 .and_then(|accessors| accessors.get(index))
801 .ok_or_else(|| Error::Validation(vec![format!("POSITION accessor {index} is missing")]))?;
802 if accessor.get("type").and_then(Value::as_str) != Some("VEC3") {
803 return Err(Error::Validation(vec![format!(
804 "POSITION accessor {index} must have type VEC3"
805 )]));
806 }
807
808 let mut bounds = Vec::with_capacity(2);
809 for field in ["min", "max"] {
810 let values = accessor
811 .get(field)
812 .and_then(Value::as_array)
813 .filter(|values| values.len() == 3)
814 .ok_or_else(|| {
815 Error::Validation(vec![format!(
816 "POSITION accessor {index} must define three-component {field} bounds"
817 )])
818 })?;
819 let values = values
820 .iter()
821 .map(|value| value.as_f64().filter(|value| value.is_finite()))
822 .collect::<Option<Vec<_>>>()
823 .ok_or_else(|| {
824 Error::Validation(vec![format!(
825 "POSITION accessor {index} {field} bounds must be finite numbers"
826 )])
827 })?;
828 bounds.push(values);
829 }
830 if bounds[0].iter().zip(&bounds[1]).any(|(min, max)| min > max) {
831 return Err(Error::Validation(vec![format!(
832 "POSITION accessor {index} min bounds exceed max bounds"
833 )]));
834 }
835 Ok(())
836}
837
838#[cfg(feature = "strict-validation")]
839fn validate_node_hierarchy(root: &Value) -> Result<()> {
840 let nodes = root.get("nodes").and_then(Value::as_array).unwrap_or(&[]);
841 let mut parents = vec![None; nodes.len()];
842 let mut edges = vec![Vec::new(); nodes.len()];
843
844 for (parent, node) in nodes.iter().enumerate() {
845 for child in node
846 .get("children")
847 .and_then(Value::as_array)
848 .unwrap_or(&[])
849 {
850 let child = child
851 .as_u64()
852 .and_then(|value| usize::try_from(value).ok())
853 .filter(|child| *child < nodes.len())
854 .ok_or_else(|| {
855 Error::Validation(vec!["node child references a missing node".into()])
856 })?;
857 if let Some(existing) = parents[child] {
858 let message = if existing == parent {
859 format!("nodes[{parent}] lists child nodes[{child}] more than once")
860 } else {
861 format!(
862 "nodes[{child}] has multiple parents: nodes[{existing}] and nodes[{parent}]"
863 )
864 };
865 return Err(Error::Validation(vec![message]));
866 }
867 parents[child] = Some(parent);
868 edges[parent].push(child);
869 }
870 }
871
872 let mut state = vec![0u8; nodes.len()];
875 for start in 0..nodes.len() {
876 if state[start] != 0 {
877 continue;
878 }
879 state[start] = 1;
880 let mut stack = vec![(start, 0usize)];
881 while let Some((node, next_child)) = stack.last_mut() {
882 if *next_child == edges[*node].len() {
883 state[*node] = 2;
884 stack.pop();
885 continue;
886 }
887 let child = edges[*node][*next_child];
888 *next_child += 1;
889 match state[child] {
890 0 => {
891 state[child] = 1;
892 stack.push((child, 0));
893 }
894 1 => {
895 return Err(Error::Validation(vec![format!(
896 "node hierarchy contains a cycle through nodes[{child}]"
897 )]))
898 }
899 _ => {}
900 }
901 }
902 }
903
904 for (scene_index, scene) in root
905 .get("scenes")
906 .and_then(Value::as_array)
907 .unwrap_or(&[])
908 .iter()
909 .enumerate()
910 {
911 for root_node in scene.get("nodes").and_then(Value::as_array).unwrap_or(&[]) {
912 let root_node = root_node
913 .as_u64()
914 .and_then(|value| usize::try_from(value).ok())
915 .filter(|node| *node < nodes.len())
916 .ok_or_else(|| Error::Validation(vec!["scene references a missing node".into()]))?;
917 if let Some(parent) = parents[root_node] {
918 return Err(Error::Validation(vec![format!(
919 "scenes[{scene_index}] uses nodes[{root_node}] as a root, but it is a child of nodes[{parent}]"
920 )]));
921 }
922 }
923 }
924
925 Ok(())
926}
927
928#[cfg(feature = "strict-validation")]
929fn validate_draco_extension(
930 root: &Value,
931 check: &impl Fn(&Value, &str, &str) -> Result<()>,
932) -> Result<()> {
933 const NAME: &str = crate::KHR_DRACO_MESH_COMPRESSION;
934 let listed = |field: &str| {
935 root.get(field)
936 .and_then(Value::as_array)
937 .is_some_and(|values| values.iter().any(|value| value.as_str() == Some(NAME)))
938 };
939 let required = listed("extensionsRequired");
940 for mesh in root.get("meshes").and_then(Value::as_array).unwrap_or(&[]) {
941 for primitive in mesh
942 .get("primitives")
943 .and_then(Value::as_array)
944 .unwrap_or(&[])
945 {
946 let Some(extension) = primitive
947 .get("extensions")
948 .and_then(|value| value.get(NAME))
949 else {
950 continue;
951 };
952 if !listed("extensionsUsed") {
953 return Err(Error::Validation(vec![
954 "KHR_draco_mesh_compression is missing from extensionsUsed".into(),
955 ]));
956 }
957 let mode = primitive.get("mode").and_then(Value::as_u64).unwrap_or(4);
958 if !matches!(mode, 4 | 5) {
959 return Err(Error::Validation(vec![
960 "KHR_draco_mesh_compression requires TRIANGLES or TRIANGLE_STRIP".into(),
961 ]));
962 }
963 check(extension, "bufferView", "bufferViews")?;
964 let attributes = extension
965 .get("attributes")
966 .and_then(Value::as_object)
967 .ok_or_else(|| {
968 Error::Validation(vec!["Draco extension attributes is not an object".into()])
969 })?;
970 let primitive_attributes = primitive
971 .get("attributes")
972 .and_then(Value::as_object)
973 .ok_or_else(|| {
974 Error::Validation(vec!["Draco primitive attributes is not an object".into()])
975 })?;
976 let mut unique_ids = std::collections::BTreeSet::new();
977 for (semantic, unique_id) in attributes {
978 let unique_id = unique_id
979 .as_u64()
980 .and_then(|value| u32::try_from(value).ok())
981 .ok_or_else(|| {
982 Error::Validation(vec![format!(
983 "Draco attribute {semantic:?} unique id is not a u32"
984 )])
985 })?;
986 if !unique_ids.insert(unique_id) {
987 return Err(Error::Validation(vec![format!(
988 "Draco unique id {unique_id} is mapped more than once"
989 )]));
990 }
991 if primitive_attributes
992 .iter()
993 .all(|(name, _)| name != semantic)
994 {
995 return Err(Error::Validation(vec![format!(
996 "Draco attribute {semantic:?} is absent from primitive attributes"
997 )]));
998 }
999 }
1000 if required {
1001 for (semantic, _) in attributes {
1002 let accessor = primitive_attributes
1003 .iter()
1004 .find(|(name, _)| name == semantic)
1005 .and_then(|(_, value)| value.as_u64())
1006 .and_then(|value| usize::try_from(value).ok())
1007 .and_then(|index| {
1008 root.get("accessors").and_then(Value::as_array)?.get(index)
1009 })
1010 .ok_or_else(|| {
1011 Error::Validation(vec!["Draco accessor is invalid".into()])
1012 })?;
1013 if accessor.get("bufferView").is_some() || accessor.get("sparse").is_some() {
1014 return Err(Error::Validation(vec![
1015 "Draco-only accessor must not retain raw buffer data".into(),
1016 ]));
1017 }
1018 }
1019 if let Some(index) = primitive.get("indices") {
1020 let accessor = index
1021 .as_u64()
1022 .and_then(|value| usize::try_from(value).ok())
1023 .and_then(|index| {
1024 root.get("accessors").and_then(Value::as_array)?.get(index)
1025 })
1026 .ok_or_else(|| {
1027 Error::Validation(vec!["Draco index accessor is invalid".into()])
1028 })?;
1029 if accessor.get("bufferView").is_some() || accessor.get("sparse").is_some() {
1030 return Err(Error::Validation(vec![
1031 "Draco-only index accessor must not retain raw buffer data".into(),
1032 ]));
1033 }
1034 }
1035 }
1036 }
1037 }
1038 Ok(())
1039}
1040
1041#[cfg(feature = "strict-validation")]
1042fn validate_shapes(root: &Value) -> Result<()> {
1043 const CORE_TYPES: [&str; 5] = ["box", "capsule", "cylinder", "plane", "sphere"];
1044 for shape in root.get("shapes").and_then(Value::as_array).unwrap_or(&[]) {
1045 let kind = shape.get("type").and_then(Value::as_str).ok_or_else(|| {
1046 Error::Validation(vec!["shape type is missing or not a string".into()])
1047 })?;
1048 if CORE_TYPES.contains(&kind) && !shape.get(kind).is_some_and(Value::is_object) {
1049 return Err(Error::Validation(vec![format!(
1050 "shape {kind:?} is missing its {kind:?} definition object"
1051 )]));
1052 }
1053 }
1054 Ok(())
1055}
1056
1057#[cfg(feature = "strict-validation")]
1058fn validate_uids(root: &Value) -> Result<()> {
1059 use std::collections::BTreeMap;
1060
1061 let mut names = BTreeMap::new();
1062 let mut uids = BTreeMap::new();
1063 for kind in [
1064 "accessors",
1065 "animations",
1066 "buffers",
1067 "bufferViews",
1068 "cameras",
1069 "externalAssets",
1070 "files",
1071 "images",
1072 "materials",
1073 "meshes",
1074 "nodes",
1075 "samplers",
1076 "scenes",
1077 "shapes",
1078 "skins",
1079 "textures",
1080 ] {
1081 for (index, value) in root
1082 .get(kind)
1083 .and_then(Value::as_array)
1084 .unwrap_or(&[])
1085 .iter()
1086 .enumerate()
1087 {
1088 let location = format!("{kind}[{index}]");
1089 if let Some(name) = value.get("name").and_then(Value::as_str) {
1090 names.insert(name, location.clone());
1091 }
1092 if let Some(uid) = value.get("uid") {
1093 let uid = uid.as_str().ok_or_else(|| {
1094 Error::Validation(vec![format!("{location}.uid is not a string")])
1095 })?;
1096 if let Some(previous) = uids.insert(uid, location.clone()) {
1097 return Err(Error::Validation(vec![format!(
1098 "{location}.uid duplicates {previous}.uid"
1099 )]));
1100 }
1101 }
1102 }
1103 }
1104 for (uid, location) in &uids {
1105 if let Some(named) = names.get(uid) {
1106 if named != location {
1107 return Err(Error::Validation(vec![format!(
1108 "{location}.uid conflicts with {named}.name"
1109 )]));
1110 }
1111 }
1112 }
1113 Ok(())
1114}
1115
1116#[derive(Clone, Copy)]
1118pub struct ObjectRef<'a, I> {
1119 index: I,
1120 value: &'a Value,
1121}
1122impl<'a, I: Copy> ObjectRef<'a, I> {
1123 pub fn index(self) -> I {
1125 self.index
1126 }
1127 pub fn value(self) -> &'a Value {
1129 self.value
1130 }
1131 pub fn name(self) -> Option<&'a str> {
1133 self.value.get("name").and_then(Value::as_str)
1134 }
1135 pub fn uid(self) -> Option<&'a str> {
1137 self.value.get("uid").and_then(Value::as_str)
1138 }
1139 pub fn extensions(self) -> Option<&'a [(String, Value)]> {
1141 self.value.get("extensions").and_then(Value::as_object)
1142 }
1143 pub fn extras(self) -> Option<&'a Value> {
1145 self.value.get("extras")
1146 }
1147}
1148
1149macro_rules! typed_object {
1150 ($name:ident, $index:ident) => {
1151 #[derive(Clone, Copy)]
1152 #[doc = concat!("Typed view of a glTF `", stringify!($name), "` object.")]
1153 pub struct $name<'a>(ObjectRef<'a, $index>);
1154 impl<'a> $name<'a> {
1155 pub fn index(self) -> $index {
1157 self.0.index()
1158 }
1159 pub fn value(self) -> &'a Value {
1161 self.0.value()
1162 }
1163 pub fn name(self) -> Option<&'a str> {
1165 self.0.name()
1166 }
1167 pub fn uid(self) -> Option<&'a str> {
1169 self.0.uid()
1170 }
1171 pub fn extras(self) -> Option<&'a Value> {
1173 self.0.extras()
1174 }
1175 pub fn extensions(self) -> Option<&'a [(String, Value)]> {
1177 self.0.extensions()
1178 }
1179 }
1180 };
1181}
1182
1183typed_object!(Accessor, AccessorIndex);
1184typed_object!(Animation, AnimationIndex);
1185typed_object!(Buffer, BufferIndex);
1186typed_object!(BufferView, BufferViewIndex);
1187typed_object!(Camera, CameraIndex);
1188typed_object!(ExternalAsset, ExternalAssetIndex);
1189typed_object!(File, FileIndex);
1190typed_object!(Image, ImageIndex);
1191typed_object!(Material, MaterialIndex);
1192typed_object!(Mesh, MeshIndex);
1193typed_object!(Node, NodeIndex);
1194typed_object!(Sampler, SamplerIndex);
1195typed_object!(Scene, SceneIndex);
1196typed_object!(Shape, ShapeIndex);
1197typed_object!(Skin, SkinIndex);
1198typed_object!(Texture, TextureIndex);
1199
1200impl<'a> Buffer<'a> {
1201 pub fn byte_length(self) -> Option<u64> {
1203 self.value().get("byteLength").and_then(Value::as_u64)
1204 }
1205 pub fn uri(self) -> Option<&'a str> {
1207 self.value().get("uri").and_then(Value::as_str)
1208 }
1209}
1210
1211impl<'a> BufferView<'a> {
1212 pub fn buffer(self) -> Option<BufferIndex> {
1214 index_value(self.value(), "buffer").map(BufferIndex)
1215 }
1216 pub fn byte_offset(self) -> u64 {
1218 self.value()
1219 .get("byteOffset")
1220 .and_then(Value::as_u64)
1221 .unwrap_or(0)
1222 }
1223 pub fn byte_length(self) -> Option<u64> {
1225 self.value().get("byteLength").and_then(Value::as_u64)
1226 }
1227 pub fn byte_stride(self) -> Option<u64> {
1229 self.value().get("byteStride").and_then(Value::as_u64)
1230 }
1231}
1232
1233impl<'a> Accessor<'a> {
1234 pub fn buffer_view(self) -> Option<BufferViewIndex> {
1236 index_value(self.value(), "bufferView").map(BufferViewIndex)
1237 }
1238 pub fn count(self) -> Option<u64> {
1240 self.value().get("count").and_then(Value::as_u64)
1241 }
1242 pub fn component_type(self) -> Option<ComponentType> {
1244 self.value()
1245 .get("componentType")
1246 .and_then(Value::as_u64)
1247 .and_then(ComponentType::from_gltf)
1248 }
1249 pub fn accessor_type(self) -> Option<&'a str> {
1251 self.value().get("type").and_then(Value::as_str)
1252 }
1253 pub fn normalized(self) -> bool {
1255 matches!(self.value().get("normalized"), Some(Value::Bool(true)))
1256 }
1257}
1258
1259impl<'a> Image<'a> {
1260 pub fn uri(self) -> Option<&'a str> {
1262 self.value().get("uri").and_then(Value::as_str)
1263 }
1264 pub fn buffer_view(self) -> Option<BufferViewIndex> {
1266 index_value(self.value(), "bufferView").map(BufferViewIndex)
1267 }
1268}
1269
1270impl<'a> Texture<'a> {
1271 pub fn source(self) -> Option<ImageIndex> {
1273 index_value(self.value(), "source").map(ImageIndex)
1274 }
1275 pub fn sampler(self) -> Option<SamplerIndex> {
1277 index_value(self.value(), "sampler").map(SamplerIndex)
1278 }
1279}
1280
1281impl<'a> Node<'a> {
1282 pub fn mesh(self) -> Option<MeshIndex> {
1284 index_value(self.value(), "mesh").map(MeshIndex)
1285 }
1286 pub fn camera(self) -> Option<CameraIndex> {
1288 index_value(self.value(), "camera").map(CameraIndex)
1289 }
1290 pub fn skin(self) -> Option<SkinIndex> {
1292 index_value(self.value(), "skin").map(SkinIndex)
1293 }
1294 pub fn external_asset(self) -> Option<ExternalAssetIndex> {
1296 index_value(self.value(), "externalAsset").map(ExternalAssetIndex)
1297 }
1298 pub fn bounding_volume(self) -> Option<BoundingVolume<'a>> {
1300 self.value()
1301 .get("boundingVolume")
1302 .filter(|value| value.is_object())
1303 .map(BoundingVolume)
1304 }
1305 pub fn children(self) -> impl Iterator<Item = NodeIndex> + 'a {
1307 self.value()
1308 .get("children")
1309 .and_then(Value::as_array)
1310 .unwrap_or(&[])
1311 .iter()
1312 .filter_map(Value::as_u64)
1313 .filter_map(|index| usize::try_from(index).ok())
1314 .map(NodeIndex)
1315 }
1316}
1317
1318impl<'a> Scene<'a> {
1319 pub fn nodes(self) -> impl Iterator<Item = NodeIndex> + 'a {
1321 self.value()
1322 .get("nodes")
1323 .and_then(Value::as_array)
1324 .unwrap_or(&[])
1325 .iter()
1326 .filter_map(Value::as_u64)
1327 .filter_map(|index| usize::try_from(index).ok())
1328 .map(NodeIndex)
1329 }
1330}
1331
1332impl<'a> File<'a> {
1333 pub fn mime_type(self) -> Option<&'a str> {
1335 self.value().get("mimeType").and_then(Value::as_str)
1336 }
1337 pub fn uri(self) -> Option<&'a str> {
1339 self.value().get("uri").and_then(Value::as_str)
1340 }
1341 pub fn buffer_view(self) -> Option<BufferViewIndex> {
1343 index_value(self.value(), "bufferView").map(BufferViewIndex)
1344 }
1345}
1346
1347impl<'a> ExternalAsset<'a> {
1348 pub fn file(self) -> Option<FileIndex> {
1350 index_value(self.value(), "file").map(FileIndex)
1351 }
1352}
1353
1354#[derive(Clone, Copy)]
1356pub struct BoundingVolume<'a>(&'a Value);
1357impl<'a> BoundingVolume<'a> {
1358 pub fn value(self) -> &'a Value {
1360 self.0
1361 }
1362 pub fn shape(self) -> Option<ShapeIndex> {
1364 index_value(self.0, "shape").map(ShapeIndex)
1365 }
1366}
1367
1368impl<'a> Shape<'a> {
1369 pub fn shape_type(self) -> Option<&'a str> {
1371 self.value().get("type").and_then(Value::as_str)
1372 }
1373 pub fn definition(self) -> Option<&'a Value> {
1375 self.shape_type().and_then(|kind| self.value().get(kind))
1376 }
1377}
1378
1379impl<'a> Mesh<'a> {
1380 pub fn primitive_count(self) -> usize {
1382 self.value()
1383 .get("primitives")
1384 .and_then(Value::as_array)
1385 .map_or(0, <[Value]>::len)
1386 }
1387}
1388
1389fn index_value(value: &Value, name: &str) -> Option<usize> {
1390 value
1391 .get(name)
1392 .and_then(Value::as_u64)
1393 .and_then(|index| usize::try_from(index).ok())
1394}
1395
1396pub struct Objects<'a, I> {
1398 values: &'a [Value],
1399 marker: PhantomData<I>,
1400}
1401impl<'a, I: From<usize> + Into<usize> + Copy> Objects<'a, I> {
1402 pub fn len(&self) -> usize {
1404 self.values.len()
1405 }
1406 pub fn is_empty(&self) -> bool {
1408 self.values.is_empty()
1409 }
1410 pub fn get(&self, index: I) -> Option<ObjectRef<'a, I>> {
1412 let index = index.into();
1413 self.values.get(index).map(|value| ObjectRef {
1414 index: I::from(index),
1415 value,
1416 })
1417 }
1418}
1419impl<'a, I: From<usize> + Into<usize> + Copy> IntoIterator for Objects<'a, I> {
1420 type Item = ObjectRef<'a, I>;
1421 type IntoIter = std::iter::Map<
1422 std::iter::Enumerate<std::slice::Iter<'a, Value>>,
1423 fn((usize, &'a Value)) -> ObjectRef<'a, I>,
1424 >;
1425 fn into_iter(self) -> Self::IntoIter {
1426 fn make<I: From<usize> + Into<usize> + Copy>(
1427 (index, value): (usize, &Value),
1428 ) -> ObjectRef<'_, I> {
1429 ObjectRef {
1430 index: I::from(index),
1431 value,
1432 }
1433 }
1434 self.values.iter().enumerate().map(make::<I>)
1435 }
1436}
1437
1438macro_rules! index_conversions { ($($name:ident),+ $(,)?) => { $(impl From<usize> for $name { fn from(value: usize) -> Self { Self(value) } } impl From<$name> for usize { fn from(value: $name) -> Self { value.0 } })+ }; }
1439index_conversions!(
1440 AccessorIndex,
1441 AnimationIndex,
1442 BufferIndex,
1443 BufferViewIndex,
1444 CameraIndex,
1445 ExternalAssetIndex,
1446 FileIndex,
1447 ImageIndex,
1448 MaterialIndex,
1449 MeshIndex,
1450 NodeIndex,
1451 SamplerIndex,
1452 SceneIndex,
1453 ShapeIndex,
1454 SkinIndex,
1455 TextureIndex
1456);
1457
1458#[derive(Clone, Copy)]
1460pub struct PrimitiveRef<'a> {
1461 document: &'a Document,
1462 mesh: MeshIndex,
1463 primitive: usize,
1464}
1465impl<'a> PrimitiveRef<'a> {
1466 pub fn mesh_index(self) -> MeshIndex {
1468 self.mesh
1469 }
1470 pub fn primitive_index(self) -> usize {
1472 self.primitive
1473 }
1474 pub fn value(self) -> &'a Value {
1476 &self.document.as_value()["meshes"][self.mesh.0]["primitives"][self.primitive]
1477 }
1478 pub fn attributes(self) -> Option<&'a [(String, Value)]> {
1480 self.value().get("attributes").and_then(Value::as_object)
1481 }
1482 pub fn attribute_indices(self) -> impl Iterator<Item = (&'a str, AccessorIndex)> + 'a {
1484 self.attributes()
1485 .unwrap_or(&[])
1486 .iter()
1487 .filter_map(|(semantic, value)| {
1488 value
1489 .as_u64()
1490 .and_then(|index| usize::try_from(index).ok())
1491 .map(|index| (semantic.as_str(), AccessorIndex(index)))
1492 })
1493 }
1494 pub fn indices(self) -> Option<AccessorIndex> {
1496 index_value(self.value(), "indices").map(AccessorIndex)
1497 }
1498 pub fn material(self) -> Option<MaterialIndex> {
1500 index_value(self.value(), "material").map(MaterialIndex)
1501 }
1502 pub fn mode(self) -> u32 {
1504 self.value()
1505 .get("mode")
1506 .and_then(Value::as_u64)
1507 .and_then(|mode| u32::try_from(mode).ok())
1508 .unwrap_or(4)
1509 }
1510 pub fn morph_targets(self) -> impl Iterator<Item = &'a [(String, Value)]> + 'a {
1512 self.value()
1513 .get("targets")
1514 .and_then(Value::as_array)
1515 .unwrap_or(&[])
1516 .iter()
1517 .filter_map(Value::as_object)
1518 }
1519 pub fn extension(self, name: &str) -> Option<&'a Value> {
1521 self.value().get("extensions")?.get(name)
1522 }
1523}