use crate::base::{Error, Result};
use crate::three::gltf_json::{self, Value};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ComponentType {
I8,
U8,
I16,
U16,
U32,
F32,
}
impl ComponentType {
pub fn from_gl(v: u32) -> Result<ComponentType> {
match v {
5120 => Ok(ComponentType::I8),
5121 => Ok(ComponentType::U8),
5122 => Ok(ComponentType::I16),
5123 => Ok(ComponentType::U16),
5125 => Ok(ComponentType::U32),
5126 => Ok(ComponentType::F32),
_ => Err(Error::Parse(format!("gltf: unknown componentType {v}"))),
}
}
pub fn byte_size(self) -> usize {
match self {
ComponentType::I8 | ComponentType::U8 => 1,
ComponentType::I16 | ComponentType::U16 => 2,
ComponentType::U32 | ComponentType::F32 => 4,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AccessorType {
Scalar,
Vec2,
Vec3,
Vec4,
Mat4,
}
impl AccessorType {
pub fn from_gltf(s: &str) -> Result<AccessorType> {
match s {
"SCALAR" => Ok(AccessorType::Scalar),
"VEC2" => Ok(AccessorType::Vec2),
"VEC3" => Ok(AccessorType::Vec3),
"VEC4" => Ok(AccessorType::Vec4),
"MAT4" => Ok(AccessorType::Mat4),
_ => Err(Error::Parse(format!(
"gltf: unsupported accessor type {s:?}"
))),
}
}
pub fn components(self) -> usize {
match self {
AccessorType::Scalar => 1,
AccessorType::Vec2 => 2,
AccessorType::Vec3 => 3,
AccessorType::Vec4 => 4,
AccessorType::Mat4 => 16,
}
}
}
#[derive(Debug, Clone)]
pub struct BufferView {
pub buffer: usize,
pub byte_offset: usize,
pub byte_length: usize,
pub byte_stride: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct Accessor {
pub buffer_view: Option<usize>,
pub byte_offset: usize,
pub component_type: ComponentType,
pub count: usize,
pub ty: AccessorType,
pub normalized: bool,
pub sparse: bool,
}
#[derive(Debug, Clone)]
pub struct Primitive {
pub position: Option<usize>,
pub normal: Option<usize>,
pub texcoord0: Option<usize>,
pub color0: Option<usize>,
pub joints0: Option<usize>,
pub weights0: Option<usize>,
pub indices: Option<usize>,
pub material: Option<usize>,
pub mode: u32,
}
#[derive(Debug, Clone)]
pub struct Mesh {
pub name: Option<String>,
pub primitives: Vec<Primitive>,
}
#[derive(Debug, Clone)]
pub struct Node {
pub name: Option<String>,
pub mesh: Option<usize>,
pub skin: Option<usize>,
pub children: Vec<usize>,
pub matrix: Option<[f32; 16]>,
pub translation: [f32; 3],
pub rotation: [f32; 4],
pub scale: [f32; 3],
}
#[derive(Debug, Clone)]
pub struct Material {
pub name: Option<String>,
pub base_color: [f32; 4],
pub base_color_texture: Option<usize>,
pub emissive: [f32; 3],
pub has_normal_texture: bool,
}
#[derive(Debug, Clone)]
pub struct Skin {
pub name: Option<String>,
pub inverse_bind_matrices: Option<usize>,
pub joints: Vec<usize>,
pub skeleton: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct AnimSampler {
pub input: usize,
pub output: usize,
pub interpolation: String,
}
#[derive(Debug, Clone)]
pub struct AnimChannel {
pub sampler: usize,
pub target_node: usize,
pub target_path: String,
}
#[derive(Debug, Clone)]
pub struct AnimationDef {
pub name: Option<String>,
pub samplers: Vec<AnimSampler>,
pub channels: Vec<AnimChannel>,
}
#[derive(Debug, Clone)]
pub struct Texture {
pub source: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct Image {
pub buffer_view: Option<usize>,
pub mime_type: Option<String>,
pub uri: Option<String>,
}
#[derive(Debug, Default)]
pub struct Doc {
pub buffers: Vec<usize>,
pub buffer_views: Vec<BufferView>,
pub accessors: Vec<Accessor>,
pub meshes: Vec<Mesh>,
pub nodes: Vec<Node>,
pub materials: Vec<Material>,
pub textures: Vec<Texture>,
pub images: Vec<Image>,
pub animations: Vec<AnimationDef>,
pub skins: Vec<Skin>,
pub scene_roots: Vec<usize>,
}
impl Doc {
pub fn parse(json_chunk: &[u8]) -> Result<Doc> {
let doc = Self::parse_unvalidated(json_chunk)?;
crate::three::validate::validate_doc(&doc)?;
Ok(doc)
}
fn parse_unvalidated(json_chunk: &[u8]) -> Result<Doc> {
let root = gltf_json::parse_bytes(json_chunk)?;
let version = root
.get("asset")
.and_then(|a| a.get("version"))
.and_then(Value::as_str)
.ok_or_else(|| Error::Parse("gltf: missing asset.version".into()))?;
if !version.starts_with("2.") {
return Err(Error::Parse(format!(
"gltf: unsupported version {version:?}"
)));
}
if let Some(reqs) = root.get("extensionsRequired") {
let names: Vec<&str> = reqs.elements().filter_map(Value::as_str).collect();
if !names.is_empty() {
return Err(Error::Parse(format!(
"gltf: required extensions not supported: {}",
names.join(", ")
)));
}
}
let mut doc = Doc::default();
for (i, b) in root
.get("buffers")
.into_iter()
.flat_map(Value::elements)
.enumerate()
{
doc.buffers.push(req_usize(b, "byteLength", i, "buffer")?);
}
for (i, bv) in root
.get("bufferViews")
.into_iter()
.flat_map(Value::elements)
.enumerate()
{
doc.buffer_views.push(BufferView {
buffer: req_usize(bv, "buffer", i, "bufferView")?,
byte_offset: opt_usize(bv, "byteOffset")?.unwrap_or(0),
byte_length: req_usize(bv, "byteLength", i, "bufferView")?,
byte_stride: opt_usize(bv, "byteStride")?,
});
}
for (i, a) in root
.get("accessors")
.into_iter()
.flat_map(Value::elements)
.enumerate()
{
let ct = a
.get("componentType")
.and_then(Value::as_u32)
.ok_or_else(|| Error::Parse(format!("gltf: accessor {i} missing componentType")))?;
let ty = a
.get("type")
.and_then(Value::as_str)
.ok_or_else(|| Error::Parse(format!("gltf: accessor {i} missing type")))?;
doc.accessors.push(Accessor {
buffer_view: opt_usize(a, "bufferView")?,
byte_offset: opt_usize(a, "byteOffset")?.unwrap_or(0),
component_type: ComponentType::from_gl(ct)?,
count: req_usize(a, "count", i, "accessor")?,
ty: AccessorType::from_gltf(ty)?,
normalized: a
.get("normalized")
.and_then(Value::as_bool)
.unwrap_or(false),
sparse: a.get("sparse").is_some(),
});
}
for (mi, m) in root
.get("meshes")
.into_iter()
.flat_map(Value::elements)
.enumerate()
{
let mut prims = Vec::new();
for p in m.get("primitives").into_iter().flat_map(Value::elements) {
let attrs = p.get("attributes");
let attr = |name: &str| -> Result<Option<usize>> {
match attrs.and_then(|a| a.get(name)) {
None => Ok(None),
Some(v) => v.as_usize().map(Some).ok_or_else(|| {
Error::Parse(format!("gltf: mesh {mi} attribute {name} not an index"))
}),
}
};
prims.push(Primitive {
position: attr("POSITION")?,
normal: attr("NORMAL")?,
texcoord0: attr("TEXCOORD_0")?,
color0: attr("COLOR_0")?,
joints0: attr("JOINTS_0")?,
weights0: attr("WEIGHTS_0")?,
indices: opt_usize(p, "indices")?,
material: opt_usize(p, "material")?,
mode: p.get("mode").and_then(Value::as_u32).unwrap_or(4),
});
}
doc.meshes.push(Mesh {
name: name_of(m),
primitives: prims,
});
}
for (ni, n) in root
.get("nodes")
.into_iter()
.flat_map(Value::elements)
.enumerate()
{
let matrix = match n.get("matrix") {
None => None,
Some(v) => Some(f32_array::<16>(v, ni, "matrix")?),
};
doc.nodes.push(Node {
name: name_of(n),
mesh: opt_usize(n, "mesh")?,
skin: opt_usize(n, "skin")?,
children: n
.get("children")
.into_iter()
.flat_map(Value::elements)
.filter_map(Value::as_usize)
.collect(),
matrix,
translation: match n.get("translation") {
None => [0.0; 3],
Some(v) => f32_array::<3>(v, ni, "translation")?,
},
rotation: match n.get("rotation") {
None => [0.0, 0.0, 0.0, 1.0],
Some(v) => f32_array::<4>(v, ni, "rotation")?,
},
scale: match n.get("scale") {
None => [1.0; 3],
Some(v) => f32_array::<3>(v, ni, "scale")?,
},
});
}
for m in root.get("materials").into_iter().flat_map(Value::elements) {
let pbr = m.get("pbrMetallicRoughness");
let base_color = match pbr.and_then(|p| p.get("baseColorFactor")) {
None => [1.0; 4],
Some(v) => f32_array::<4>(v, doc.materials.len(), "baseColorFactor")?,
};
let base_color_texture = pbr
.and_then(|p| p.get("baseColorTexture"))
.and_then(|t| t.get("index"))
.and_then(Value::as_usize);
let emissive = match m.get("emissiveFactor") {
None => [0.0; 3],
Some(v) => f32_array::<3>(v, doc.materials.len(), "emissiveFactor")?,
};
doc.materials.push(Material {
name: name_of(m),
base_color,
base_color_texture,
emissive,
has_normal_texture: m.get("normalTexture").is_some(),
});
}
for t in root.get("textures").into_iter().flat_map(Value::elements) {
doc.textures.push(Texture {
source: opt_usize(t, "source")?,
});
}
for im in root.get("images").into_iter().flat_map(Value::elements) {
doc.images.push(Image {
buffer_view: opt_usize(im, "bufferView")?,
mime_type: im
.get("mimeType")
.and_then(Value::as_str)
.map(str::to_owned),
uri: im.get("uri").and_then(Value::as_str).map(str::to_owned),
});
}
for (si, s) in root
.get("skins")
.into_iter()
.flat_map(Value::elements)
.enumerate()
{
let joints: Vec<usize> = s
.get("joints")
.into_iter()
.flat_map(Value::elements)
.filter_map(Value::as_usize)
.collect();
if joints.is_empty() {
return Err(Error::Parse(format!("gltf: skin {si} has no joints")));
}
doc.skins.push(Skin {
name: name_of(s),
inverse_bind_matrices: opt_usize(s, "inverseBindMatrices")?,
joints,
skeleton: opt_usize(s, "skeleton")?,
});
}
for (ai, a) in root
.get("animations")
.into_iter()
.flat_map(Value::elements)
.enumerate()
{
let mut samplers = Vec::new();
for (si, s) in a
.get("samplers")
.into_iter()
.flat_map(Value::elements)
.enumerate()
{
samplers.push(AnimSampler {
input: req_usize(s, "input", si, "animation sampler")?,
output: req_usize(s, "output", si, "animation sampler")?,
interpolation: s
.get("interpolation")
.and_then(Value::as_str)
.unwrap_or("LINEAR")
.to_owned(),
});
}
let mut channels = Vec::new();
for c in a.get("channels").into_iter().flat_map(Value::elements) {
let target = c.get("target").ok_or_else(|| {
Error::Parse(format!("gltf: animation {ai} channel without target"))
})?;
let Some(node) = target.get("node").and_then(Value::as_usize) else {
continue;
};
channels.push(AnimChannel {
sampler: req_usize(c, "sampler", ai, "animation channel")?,
target_node: node,
target_path: target
.get("path")
.and_then(Value::as_str)
.ok_or_else(|| {
Error::Parse(format!("gltf: animation {ai} channel without path"))
})?
.to_owned(),
});
}
doc.animations.push(AnimationDef {
name: name_of(a),
samplers,
channels,
});
}
let scenes = root.get("scenes");
let scene_idx = root.get("scene").and_then(Value::as_usize).unwrap_or(0);
if let Some(scene) = scenes.and_then(|s| s.idx(scene_idx)) {
doc.scene_roots = scene
.get("nodes")
.into_iter()
.flat_map(Value::elements)
.filter_map(Value::as_usize)
.collect();
}
Ok(doc)
}
}
fn name_of(v: &Value) -> Option<String> {
v.get("name").and_then(Value::as_str).map(str::to_owned)
}
fn req_usize(v: &Value, key: &str, idx: usize, what: &str) -> Result<usize> {
v.get(key)
.and_then(Value::as_usize)
.ok_or_else(|| Error::Parse(format!("gltf: {what} {idx} missing {key}")))
}
fn opt_usize(v: &Value, key: &str) -> Result<Option<usize>> {
match v.get(key) {
None => Ok(None),
Some(x) => x
.as_usize()
.map(Some)
.ok_or_else(|| Error::Parse(format!("gltf: {key} is not a non-negative integer"))),
}
}
fn f32_array<const N: usize>(v: &Value, idx: usize, what: &str) -> Result<[f32; N]> {
let arr = v
.as_array()
.ok_or_else(|| Error::Parse(format!("gltf: node {idx} {what} is not an array")))?;
if arr.len() != N {
return Err(Error::Parse(format!(
"gltf: node {idx} {what} has {} elements (want {N})",
arr.len()
)));
}
let mut out = [0.0f32; N];
for (i, e) in arr.iter().enumerate() {
out[i] = e
.as_f64()
.ok_or_else(|| Error::Parse(format!("gltf: node {idx} {what}[{i}] is not a number")))?
as f32;
}
Ok(out)
}
#[cfg(test)]
#[path = "doc_tests.rs"]
mod tests;