gltf-reader 0.1.0

A simple glTF 2.0 reader using `serde` and `serde_json`
Documentation
//! A simple glTF 2.0 reader using [`serde`] and [`serde_json`]. This crate is pretty much a 1:1
//! implementation of the [glTF schema] in Rust. It is purely structural and performs no validation
//! of the glTF asset.
//!
//! The structures try to borrow from the glTF JSON as much as possible. While not zero-copy, this
//! crate is way more memory efficient than other glTF readers. You can still obtain an owned copy
//! of any structure through `into_owned` methods, though.
//!
//! All of the types implement [`Deserialize`], which in theory allow them to be deserialized from
//! any format. However, the only supported format intended is JSON through [`serde_json`] -
//! anything else has no guarantees of working.
//!
//! You can easily parse a glTF JSON string with [`Root::from_str`], which is just a wrapper around
//! [`serde_json::from_str`].
//!
//! # no-std
//! This is a no-[`std`] crate. It does, however, still require [`alloc`].
//!
//! [glTF schema]: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#appendix-a-json-schema-reference

#![cfg_attr(not(any(test, doc)), no_std)]

extern crate alloc;

pub use serde_json;

pub mod json;

mod accessor;
mod animation;
mod buffer;
mod camera;
mod image;
mod material;
mod mesh;
mod node;
mod sampler;
mod scene;
mod skin;
mod texture;

use alloc::borrow::Cow;
use alloc::vec::Vec;
use core::marker::PhantomData;

use ownable::IntoOwned;
use serde::Deserialize;

pub use crate::accessor::*;
pub use crate::animation::*;
pub use crate::buffer::*;
pub use crate::camera::*;
pub use crate::image::*;
pub use crate::material::*;
pub use crate::mesh::*;
pub use crate::node::*;
pub use crate::sampler::*;
pub use crate::scene::*;
pub use crate::skin::*;
pub use crate::texture::*;

/// A glTF index. Since pratically all structures in the crate have a lifetime that doesn't matter
/// for this type, it's simply set to `static` - in practice, just ignore it.
#[derive(Clone, Copy, Deserialize)]
#[serde(transparent)]
pub struct Idx<T: 'static>(u32, PhantomData<T>);

impl<T> ownable::traits::IntoOwned for Idx<T> {
    type Owned = Self;

    fn into_owned(self) -> Self::Owned {
        self
    }
}

impl<T> core::fmt::Debug for Idx<T> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.0.fmt(f)
    }
}

impl<T: 'static> Idx<T> {
    /// Creates a new index with the given value.
    pub fn new(index: u32) -> Self {
        Self(index, PhantomData)
    }

    /// Gets the value of this index.
    pub fn get(self) -> u32 {
        self.0
    }

    /// Casts this index type to another.
    pub fn cast<U: 'static>(self) -> Idx<U> {
        Idx(self.0, PhantomData)
    }
}

/// Application-specific data.
#[derive(Clone, Deserialize, IntoOwned)]
#[serde(transparent)]
pub struct Extras<'a>(#[serde(borrow)] pub json::Value<'a>);

impl core::fmt::Debug for Extras<'_> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.0.fmt(f)
    }
}

/// JSON object with extension-specific objects.
#[derive(Clone, Deserialize, Default, IntoOwned)]
#[serde(transparent)]
pub struct Extensions<'a>(#[serde(borrow)] pub json::Object<'a>);

impl core::fmt::Debug for Extensions<'_> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.0.fmt(f)
    }
}

/// Metadata about a glTF asset.
#[derive(Debug, Clone, Deserialize, IntoOwned)]
pub struct Asset<'a> {
    /// A copyright message suitable for display to credit the content creator.
    #[serde(borrow)]
    pub copyright: Option<Cow<'a, str>>,
    /// Tool that generated this glTF model. Useful for debugging.
    #[serde(borrow)]
    pub generator: Option<Cow<'a, str>>,
    /// The glTF version in the form of `<major>.<minor>` that this asset targets.
    #[serde(borrow)]
    pub version: Cow<'a, str>,
    /// The minimum glTF version in the form of `<major>.<minor>` that this asset targets.
    #[serde(rename = "minVersion")]
    #[serde(borrow)]
    pub min_version: Option<Cow<'a, str>>,

    #[serde(borrow)]
    pub extensions: Option<Extensions<'a>>,
    #[serde(borrow)]
    pub extras: Option<Extras<'a>>,
}

/// The root object of a glTF asset.
#[derive(Debug, Clone, Deserialize, IntoOwned)]
pub struct Root<'a> {
    /// Metadata about this glTF asset.
    pub asset: Asset<'a>,
    /// Names of glTF extensions used in this asset.
    #[serde(rename = "extensionsUsed")]
    #[serde(default, borrow)]
    pub extensions_used: Vec<Cow<'a, str>>,
    /// Names of glTF extensions required to properly load this asset.
    #[serde(rename = "extensionsRequired")]
    #[serde(default, borrow)]
    pub extensions_required: Vec<Cow<'a, str>>,
    /// The index of the default scene.
    pub scene: Option<Idx<Scene<'static>>>,

    /// An array of accessors.
    #[serde(default, borrow)]
    pub accessors: Vec<Accessor<'a>>,
    /// An array of animations.
    #[serde(default, borrow)]
    pub animations: Vec<Animation<'a>>,
    /// An array of buffers.
    #[serde(default, borrow)]
    pub buffers: Vec<Buffer<'a>>,
    /// An array of buffer views.
    #[serde(rename = "bufferViews")]
    #[serde(default, borrow)]
    pub buffer_views: Vec<BufferView<'a>>,
    /// An array of cameras.
    #[serde(default, borrow)]
    pub cameras: Vec<Camera<'a>>,
    /// An array of images.
    #[serde(default, borrow)]
    pub images: Vec<Image<'a>>,
    /// An array of materials.
    #[serde(default, borrow)]
    pub materials: Vec<Material<'a>>,
    /// An array of meshes.
    #[serde(default, borrow)]
    pub meshes: Vec<Mesh<'a>>,
    /// An array of nodes.
    #[serde(default, borrow)]
    pub nodes: Vec<Node<'a>>,
    /// An array of samplers.
    #[serde(default, borrow)]
    pub samplers: Vec<Sampler<'a>>,
    /// An array of scenes.
    #[serde(default, borrow)]
    pub scenes: Vec<Scene<'a>>,
    /// An array of skins.
    #[serde(default, borrow)]
    pub skins: Vec<Skin<'a>>,
    /// An array of textures.
    #[serde(default, borrow)]
    pub textures: Vec<Texture<'a>>,

    #[serde(borrow)]
    pub extensions: Option<Extensions<'a>>,
    #[serde(borrow)]
    pub extras: Option<Extras<'a>>,
}

impl<'a> Root<'a> {
    /// Reads a glTF JSON from the given string.
    #[expect(
        clippy::should_implement_trait,
        reason = "looks like FromStr but can't be implemented as it because of lifetimes"
    )]
    pub fn from_str(s: &'a str) -> serde_json::Result<Self> {
        serde_json::from_str(s)
    }
}