use std::io;
use draco_core::draco_types::DataType;
use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
use draco_core::mesh::Mesh;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GltfError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("JSON parse error: {0}")]
Json(#[from] serde_json::Error),
#[error("Invalid GLB: {0}")]
InvalidGlb(String),
#[error("Invalid glTF: {0}")]
InvalidGltf(String),
#[error("Draco decode error: {0}")]
DracoDecode(String),
#[error("Unsupported feature: {0}")]
Unsupported(String),
}
pub type Result<T> = std::result::Result<T, GltfError>;
pub(crate) const GLTF_MODE_POINTS: u32 = 0;
pub(crate) const GLTF_MODE_TRIANGLES: u32 = 4;
pub(crate) const GLTF_COMPONENT_BYTE: u32 = 5120;
pub(crate) const GLTF_COMPONENT_UNSIGNED_BYTE: u32 = 5121;
pub(crate) const GLTF_COMPONENT_SHORT: u32 = 5122;
pub(crate) const GLTF_COMPONENT_UNSIGNED_SHORT: u32 = 5123;
#[cfg(feature = "gltf-reader")]
pub(crate) const GLTF_COMPONENT_UNSIGNED_INT: u32 = 5125;
pub(crate) const GLTF_COMPONENT_FLOAT: u32 = 5126;
pub struct DecodedAccessor {
count: usize,
num_components: u8,
data_type: DataType,
normalized: bool,
bytes: Vec<u8>,
}
impl DecodedAccessor {
pub fn new(
count: usize,
num_components: u8,
data_type: DataType,
normalized: bool,
bytes: Vec<u8>,
) -> Self {
Self {
count,
num_components,
data_type,
normalized,
bytes,
}
}
fn gather(&self, indices: &[u32]) -> Result<Self> {
let stride = self.num_components as usize * self.data_type.byte_length();
let mut bytes = Vec::with_capacity(indices.len() * stride);
for &index in indices {
let index = index as usize;
if index >= self.count {
return Err(GltfError::InvalidGltf(format!(
"Accessor index {} out of bounds for {} values",
index, self.count
)));
}
let offset = index * stride;
bytes.extend_from_slice(&self.bytes[offset..offset + stride]);
}
Ok(Self {
count: indices.len(),
num_components: self.num_components,
data_type: self.data_type,
normalized: self.normalized,
bytes,
})
}
}
pub trait AccessorSource {
fn read_attribute(
&self,
accessor: usize,
expected_types: &[&str],
allowed_component_types: &[u32],
) -> Result<DecodedAccessor>;
fn read_indices(&self, accessor: usize) -> Result<Vec<u32>>;
}
pub(crate) struct SemanticSpec {
pub(crate) attribute_type: GeometryAttributeType,
pub(crate) expected_accessor_types: &'static [&'static str],
pub(crate) allowed_component_types: &'static [u32],
}
const FLOAT_ONLY: &[u32] = &[GLTF_COMPONENT_FLOAT];
const TEXCOORD_COMPONENT_TYPES: &[u32] = &[
GLTF_COMPONENT_FLOAT,
GLTF_COMPONENT_UNSIGNED_BYTE,
GLTF_COMPONENT_UNSIGNED_SHORT,
];
const COLOR_COMPONENT_TYPES: &[u32] = &[
GLTF_COMPONENT_FLOAT,
GLTF_COMPONENT_UNSIGNED_BYTE,
GLTF_COMPONENT_UNSIGNED_SHORT,
];
const GENERIC_COMPONENT_TYPES: &[u32] = &[
GLTF_COMPONENT_BYTE,
GLTF_COMPONENT_UNSIGNED_BYTE,
GLTF_COMPONENT_SHORT,
GLTF_COMPONENT_UNSIGNED_SHORT,
GLTF_COMPONENT_FLOAT,
];
pub fn decode_geometry<S: AccessorSource>(
src: &S,
mode: u32,
attributes: &[(String, usize)],
indices: Option<usize>,
) -> Result<(Mesh, Vec<(String, u32)>)> {
use draco_core::geometry_indices::PointIndex;
if mode != GLTF_MODE_TRIANGLES && mode != GLTF_MODE_POINTS {
return Err(GltfError::Unsupported(format!(
"Primitive mode {} not supported (only POINTS=0 and TRIANGLES=4)",
mode
)));
}
let pos_accessor_idx = attributes
.iter()
.find(|(semantic, _)| semantic == "POSITION")
.map(|(_, accessor)| *accessor)
.ok_or_else(|| GltfError::InvalidGltf("primitive has no POSITION attribute".into()))?;
let positions = src.read_attribute(pos_accessor_idx, &["VEC3"], &[GLTF_COMPONENT_FLOAT])?;
let mut mesh = Mesh::new();
let point_indices = if mode == GLTF_MODE_POINTS {
indices.map(|idx| src.read_indices(idx)).transpose()?
} else {
None
};
let positions = if let Some(idx) = &point_indices {
positions.gather(idx)?
} else {
positions
};
mesh.set_num_points(positions.count);
let mut semantics: Vec<(String, u32)> = Vec::new();
let pos_id = add_decoded_attribute(&mut mesh, GeometryAttributeType::Position, positions)?;
semantics.push(("POSITION".to_string(), pos_id as u32));
if mode == GLTF_MODE_TRIANGLES {
if let Some(indices_accessor_idx) = indices {
let indices = src.read_indices(indices_accessor_idx)?;
if indices.len() % 3 != 0 {
return Err(GltfError::InvalidGltf(
"Index count not divisible by 3 for triangles".into(),
));
}
for &index in &indices {
if index as usize >= mesh.num_points() {
return Err(GltfError::InvalidGltf(format!(
"Triangle index {} out of bounds for {} points",
index,
mesh.num_points()
)));
}
}
let num_faces = indices.len() / 3;
for i in 0..num_faces {
mesh.add_face([
PointIndex(indices[i * 3]),
PointIndex(indices[i * 3 + 1]),
PointIndex(indices[i * 3 + 2]),
]);
}
} else {
if !mesh.num_points().is_multiple_of(3) {
return Err(GltfError::InvalidGltf(
"Non-indexed primitive point count not divisible by 3".into(),
));
}
for i in 0..(mesh.num_points() / 3) {
let base = (i * 3) as u32;
mesh.add_face([PointIndex(base), PointIndex(base + 1), PointIndex(base + 2)]);
}
}
}
if let Some(normal_idx) = attributes
.iter()
.find(|(semantic, _)| semantic == "NORMAL")
.map(|(_, accessor)| *accessor)
{
let normal_id = read_and_add_standard_attribute(
&mut mesh,
src,
normal_idx,
GeometryAttributeType::Normal,
&["VEC3"],
&[GLTF_COMPONENT_FLOAT],
point_indices.as_deref(),
)?;
semantics.push(("NORMAL".to_string(), normal_id as u32));
}
let mut sorted: Vec<&(String, usize)> = attributes.iter().collect();
sorted.sort_by(|(left, _), (right, _)| left.cmp(right));
for (semantic, accessor_idx) in sorted {
if semantic == "POSITION" || semantic == "NORMAL" {
continue;
}
let attribute_spec = supported_semantic_spec(semantic)?;
let att_id = read_and_add_standard_attribute(
&mut mesh,
src,
*accessor_idx,
attribute_spec.attribute_type,
attribute_spec.expected_accessor_types,
attribute_spec.allowed_component_types,
point_indices.as_deref(),
)?;
semantics.push((semantic.clone(), att_id as u32));
}
mesh.deduplicate_point_ids();
Ok((mesh, semantics))
}
#[cfg(feature = "gltf-reader")]
pub(crate) fn add_named_attribute<S: AccessorSource>(
mesh: &mut Mesh,
src: &S,
semantic: &str,
accessor_idx: usize,
point_indices: Option<&[u32]>,
) -> Result<i32> {
let spec = supported_semantic_spec(semantic)?;
read_and_add_standard_attribute(
mesh,
src,
accessor_idx,
spec.attribute_type,
spec.expected_accessor_types,
spec.allowed_component_types,
point_indices,
)
}
fn read_and_add_standard_attribute<S: AccessorSource>(
mesh: &mut Mesh,
src: &S,
accessor_idx: usize,
attribute_type: GeometryAttributeType,
expected_types: &[&str],
allowed_component_types: &[u32],
point_indices: Option<&[u32]>,
) -> Result<i32> {
let decoded = src.read_attribute(accessor_idx, expected_types, allowed_component_types)?;
let decoded = if let Some(indices) = point_indices {
decoded.gather(indices)?
} else {
decoded
};
add_decoded_attribute(mesh, attribute_type, decoded)
}
fn add_decoded_attribute(
mesh: &mut Mesh,
attribute_type: GeometryAttributeType,
decoded: DecodedAccessor,
) -> Result<i32> {
if decoded.count != mesh.num_points() {
return Err(GltfError::InvalidGltf(format!(
"Attribute {:?} has {} values but primitive has {} points",
attribute_type,
decoded.count,
mesh.num_points()
)));
}
let mut attribute = PointAttribute::new();
attribute.init(
attribute_type,
decoded.num_components,
decoded.data_type,
decoded.normalized,
decoded.count,
);
attribute.buffer_mut().write(0, &decoded.bytes);
Ok(mesh.add_attribute(attribute))
}
pub(crate) fn supported_semantic_spec(semantic: &str) -> Result<SemanticSpec> {
let spec = if semantic == "POSITION" {
SemanticSpec {
attribute_type: GeometryAttributeType::Position,
expected_accessor_types: &["VEC3"],
allowed_component_types: FLOAT_ONLY,
}
} else if semantic == "NORMAL" {
SemanticSpec {
attribute_type: GeometryAttributeType::Normal,
expected_accessor_types: &["VEC3"],
allowed_component_types: FLOAT_ONLY,
}
} else if semantic.starts_with("TEXCOORD_") {
SemanticSpec {
attribute_type: GeometryAttributeType::TexCoord,
expected_accessor_types: &["VEC2"],
allowed_component_types: TEXCOORD_COMPONENT_TYPES,
}
} else if semantic.starts_with("COLOR_") {
SemanticSpec {
attribute_type: GeometryAttributeType::Color,
expected_accessor_types: &["VEC3", "VEC4"],
allowed_component_types: COLOR_COMPONENT_TYPES,
}
} else {
SemanticSpec {
attribute_type: GeometryAttributeType::Generic,
expected_accessor_types: &["SCALAR", "VEC2", "VEC3", "VEC4"],
allowed_component_types: GENERIC_COMPONENT_TYPES,
}
};
Ok(spec)
}