use std::sync::Arc;
use crate::json::Value;
use draco_core::Mesh;
#[cfg(feature = "draco-decode")]
use draco_core::{DecoderBuffer, MeshDecoder};
use crate::{Document, Error, PrimitiveRef, Result};
pub const KHR_DRACO_MESH_COMPRESSION: &str = "KHR_draco_mesh_compression";
pub const EXT_MESHOPT_COMPRESSION: &str = "EXT_meshopt_compression";
pub const KHR_MESHOPT_COMPRESSION: &str = "KHR_meshopt_compression";
pub fn meshopt_extension(extensions: Option<&Value>) -> Option<(&'static str, &Value)> {
let extensions = extensions?;
for name in [EXT_MESHOPT_COMPRESSION, KHR_MESHOPT_COMPRESSION] {
if let Some(value) = extensions.get(name) {
return Some((name, value));
}
}
None
}
pub fn meshopt_extension_mut(extensions: Option<&mut Value>) -> Option<(&'static str, &mut Value)> {
let extensions = extensions?;
let name = if extensions.get(EXT_MESHOPT_COMPRESSION).is_some() {
EXT_MESHOPT_COMPRESSION
} else if extensions.get(KHR_MESHOPT_COMPRESSION).is_some() {
KHR_MESHOPT_COMPRESSION
} else {
return None;
};
extensions.get_mut(name).map(|value| (name, value))
}
pub const BINARY_FREE_EXTENSIONS: &[&str] = &[
"KHR_materials_unlit",
"KHR_materials_emissive_strength",
"KHR_materials_ior",
"KHR_materials_specular",
"KHR_materials_anisotropy",
"KHR_materials_transmission",
"KHR_materials_dispersion",
"KHR_materials_volume",
"KHR_materials_iridescence",
"KHR_materials_sheen",
"KHR_materials_clearcoat",
"KHR_materials_pbrSpecularGlossiness",
"KHR_texture_transform",
"EXT_texture_webp",
"EXT_texture_avif",
"KHR_texture_basisu",
"KHR_lights_punctual",
"KHR_materials_variants",
"KHR_mesh_quantization",
"CESIUM_RTC",
"EXT_mesh_features",
];
pub const EXT_MESH_GPU_INSTANCING: &str = "EXT_mesh_gpu_instancing";
pub const EXT_STRUCTURAL_METADATA: &str = "EXT_structural_metadata";
const PROPERTY_TABLE_SLOTS: [&str; 3] = ["values", "arrayOffsets", "stringOffsets"];
fn instancing_accessors(root: &Value) -> impl Iterator<Item = &Value> {
root.get("nodes")
.and_then(Value::as_array)
.unwrap_or(&[])
.iter()
.filter_map(|node| {
node.get("extensions")?
.get(EXT_MESH_GPU_INSTANCING)?
.get("attributes")?
.as_object()
})
.flatten()
.map(|(_, value)| value)
}
fn instancing_accessors_mut(root: &mut Value) -> impl Iterator<Item = &mut Value> {
root.get_mut("nodes")
.and_then(Value::as_array_mut)
.map(|nodes| nodes.iter_mut())
.into_iter()
.flatten()
.filter_map(|node| {
node.get_mut("extensions")?
.get_mut(EXT_MESH_GPU_INSTANCING)?
.get_mut("attributes")?
.as_object_mut()
})
.flatten()
.map(|(_, value)| value)
}
fn property_table_views(root: &Value) -> impl Iterator<Item = &Value> {
root.get("extensions")
.and_then(|extensions| extensions.get(EXT_STRUCTURAL_METADATA))
.and_then(|metadata| metadata.get("propertyTables"))
.and_then(Value::as_array)
.unwrap_or(&[])
.iter()
.filter_map(|table| table.get("properties")?.as_object())
.flatten()
.flat_map(|(_, property)| {
PROPERTY_TABLE_SLOTS
.iter()
.filter_map(|slot| property.get(slot))
})
}
fn property_table_views_mut(root: &mut Value) -> impl Iterator<Item = &mut Value> {
root.get_mut("extensions")
.and_then(|extensions| extensions.get_mut(EXT_STRUCTURAL_METADATA))
.and_then(|metadata| metadata.get_mut("propertyTables"))
.and_then(Value::as_array_mut)
.map(|tables| tables.iter_mut())
.into_iter()
.flatten()
.filter_map(|table| table.get_mut("properties")?.as_object_mut())
.flatten()
.flat_map(|(_, property)| {
property
.as_object_mut()
.map(|entries| {
entries
.iter_mut()
.filter(|(key, _)| PROPERTY_TABLE_SLOTS.contains(&key.as_str()))
.map(|(_, value)| value)
})
.into_iter()
.flatten()
})
}
fn keep_reference(value: &Value, used: &mut [bool], kind: &str) -> Result<()> {
let index = value
.as_u64()
.and_then(|value| usize::try_from(value).ok())
.filter(|index| *index < used.len())
.ok_or_else(|| Error::Extension(format!("{kind} is invalid")))?;
used[index] = true;
Ok(())
}
#[derive(Clone, Copy, Debug, Default)]
pub struct MeshGpuInstancingExtension;
impl ExtensionHandler for MeshGpuInstancingExtension {
fn name(&self) -> &'static str {
EXT_MESH_GPU_INSTANCING
}
fn allows_binary_transform(&self) -> bool {
true
}
fn collect_binary_references(
&self,
document: &Document,
accessors: &mut [bool],
_buffer_views: &mut [bool],
) -> Result<()> {
for value in instancing_accessors(document.as_value()) {
keep_reference(value, accessors, "EXT_mesh_gpu_instancing accessor")?;
}
Ok(())
}
fn remap_binary_references(
&self,
document: &mut Document,
accessors: &[Option<usize>],
_buffer_views: &[Option<usize>],
) -> Result<()> {
for value in instancing_accessors_mut(document.as_value_mut()) {
remap_reference(value, accessors, "EXT_mesh_gpu_instancing accessor")?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct StructuralMetadataExtension;
impl ExtensionHandler for StructuralMetadataExtension {
fn name(&self) -> &'static str {
EXT_STRUCTURAL_METADATA
}
fn allows_binary_transform(&self) -> bool {
true
}
fn collect_binary_references(
&self,
document: &Document,
_accessors: &mut [bool],
buffer_views: &mut [bool],
) -> Result<()> {
for value in property_table_views(document.as_value()) {
keep_reference(value, buffer_views, "EXT_structural_metadata bufferView")?;
}
Ok(())
}
fn remap_binary_references(
&self,
document: &mut Document,
_accessors: &[Option<usize>],
buffer_views: &[Option<usize>],
) -> Result<()> {
for value in property_table_views_mut(document.as_value_mut()) {
remap_reference(value, buffer_views, "EXT_structural_metadata bufferView")?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub struct BinaryFreeExtension(pub &'static str);
impl ExtensionHandler for BinaryFreeExtension {
fn name(&self) -> &'static str {
self.0
}
fn allows_binary_transform(&self) -> bool {
true
}
}
#[derive(Clone, Debug, Default)]
pub struct ResourceStore {
pub buffers: Vec<Vec<u8>>,
}
#[derive(Default)]
pub struct ExtensionValidationContext {
accessors_without_buffer_view: Vec<usize>,
}
impl ExtensionValidationContext {
pub fn allow_accessor_without_buffer_view(&mut self, index: usize) {
if !self.accessors_without_buffer_view.contains(&index) {
self.accessors_without_buffer_view.push(index);
}
}
pub fn allows_accessor_without_buffer_view(&self, index: usize) -> bool {
self.accessors_without_buffer_view.contains(&index)
}
}
pub trait ExtensionHandler: Send + Sync {
fn name(&self) -> &'static str;
fn validate(
&self,
_document: &Document,
_context: &mut ExtensionValidationContext,
) -> Result<()> {
Ok(())
}
fn allows_binary_transform(&self) -> bool {
false
}
fn collect_binary_references(
&self,
_document: &Document,
_accessors: &mut [bool],
_buffer_views: &mut [bool],
) -> Result<()> {
Ok(())
}
fn remap_binary_references(
&self,
_document: &mut Document,
_accessors: &[Option<usize>],
_buffer_views: &[Option<usize>],
) -> Result<()> {
Ok(())
}
fn decode_primitive(
&self,
_document: &Document,
_resources: &ResourceStore,
_primitive: PrimitiveRef<'_>,
) -> Option<Result<Mesh>> {
None
}
}
#[derive(Clone)]
pub struct ExtensionRegistry {
handlers: Vec<Arc<dyn ExtensionHandler>>,
}
impl ExtensionRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register<H: ExtensionHandler + 'static>(&mut self, handler: H) -> Result<()> {
if self
.handlers
.iter()
.any(|existing| existing.name() == handler.name())
{
return Err(Error::Extension(format!(
"extension handler {} is already registered",
handler.name()
)));
}
self.handlers.push(Arc::new(handler));
Ok(())
}
pub fn contains(&self, name: &str) -> bool {
self.handlers.iter().any(|handler| handler.name() == name)
}
pub fn allows_binary_transform(&self, name: &str) -> bool {
self.handlers
.iter()
.any(|handler| handler.name() == name && handler.allows_binary_transform())
}
pub fn validate(&self, document: &Document) -> Result<ExtensionValidationContext> {
let mut context = ExtensionValidationContext::default();
for handler in &self.handlers {
handler.validate(document, &mut context)?;
}
Ok(context)
}
#[cfg(feature = "draco-encode")]
pub(crate) fn collect_binary_references(
&self,
document: &Document,
accessors: &mut [bool],
buffer_views: &mut [bool],
) -> Result<()> {
for handler in &self.handlers {
if handler.allows_binary_transform() {
handler.collect_binary_references(document, accessors, buffer_views)?;
}
}
Ok(())
}
#[cfg(feature = "draco-encode")]
pub(crate) fn remap_binary_references(
&self,
document: &mut Document,
accessors: &[Option<usize>],
buffer_views: &[Option<usize>],
) -> Result<()> {
for handler in &self.handlers {
if handler.allows_binary_transform() {
handler.remap_binary_references(document, accessors, buffer_views)?;
}
}
Ok(())
}
pub fn decode_primitive(
&self,
document: &Document,
resources: &ResourceStore,
primitive: PrimitiveRef<'_>,
) -> Result<Mesh> {
for handler in &self.handlers {
if let Some(result) = handler.decode_primitive(document, resources, primitive) {
return result;
}
}
Err(Error::Extension(
"primitive has no registered geometry extension decoder".into(),
))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DracoExtension;
impl ExtensionHandler for DracoExtension {
fn name(&self) -> &'static str {
KHR_DRACO_MESH_COMPRESSION
}
fn allows_binary_transform(&self) -> bool {
true
}
fn collect_binary_references(
&self,
document: &Document,
_accessors: &mut [bool],
buffer_views: &mut [bool],
) -> Result<()> {
if buffer_views.is_empty() {
return Ok(());
}
for mesh in document.meshes() {
for primitive in mesh
.value()
.get("primitives")
.and_then(Value::as_array)
.unwrap_or(&[])
{
let Some(extension) = primitive
.get("extensions")
.and_then(|value| value.get(KHR_DRACO_MESH_COMPRESSION))
else {
continue;
};
let index = extension
.get("bufferView")
.and_then(Value::as_u64)
.and_then(|value| usize::try_from(value).ok())
.filter(|index| *index < buffer_views.len())
.ok_or_else(|| Error::Extension("Draco bufferView is invalid".into()))?;
buffer_views[index] = true;
}
}
Ok(())
}
fn remap_binary_references(
&self,
document: &mut Document,
_accessors: &[Option<usize>],
buffer_views: &[Option<usize>],
) -> Result<()> {
if buffer_views.is_empty() {
return Ok(());
}
let Some(meshes) = document
.as_value_mut()
.get_mut("meshes")
.and_then(Value::as_array_mut)
else {
return Ok(());
};
for mesh in meshes {
let Some(primitives) = mesh.get_mut("primitives").and_then(Value::as_array_mut) else {
continue;
};
for primitive in primitives {
let Some(value) = primitive
.get_mut("extensions")
.and_then(|value| value.get_mut(KHR_DRACO_MESH_COMPRESSION))
.and_then(|value| value.get_mut("bufferView"))
else {
continue;
};
remap_reference(value, buffer_views, "Draco bufferView")?;
}
}
Ok(())
}
fn validate(
&self,
document: &Document,
context: &mut ExtensionValidationContext,
) -> Result<()> {
let accessors = document
.as_value()
.get("accessors")
.and_then(Value::as_array)
.unwrap_or(&[]);
for mesh in document.meshes() {
for primitive_index in mesh
.value()
.get("primitives")
.and_then(Value::as_array)
.into_iter()
.flatten()
.enumerate()
{
let primitive = primitive_index.1;
let Some(_parsed) = parse_draco_extension(
primitive
.get("extensions")
.and_then(|extensions| extensions.get(KHR_DRACO_MESH_COMPRESSION)),
)?
else {
continue;
};
for accessor in primitive
.get("attributes")
.and_then(Value::as_object)
.into_iter()
.flat_map(|attrs| attrs.iter().map(|(_, value)| value))
.chain(primitive.get("indices"))
{
if let Some(index) = accessor
.as_u64()
.and_then(|value| usize::try_from(value).ok())
{
if accessors.get(index).is_some_and(|value| {
value.get("bufferView").is_none() && value.get("sparse").is_none()
}) {
context.allow_accessor_without_buffer_view(index);
}
}
}
}
}
Ok(())
}
#[cfg(feature = "draco-decode")]
fn decode_primitive(
&self,
document: &Document,
resources: &ResourceStore,
primitive: PrimitiveRef<'_>,
) -> Option<Result<Mesh>> {
let extension = primitive.extension(self.name())?;
Some((|| {
let parsed = parse_draco_extension(Some(extension))?
.ok_or_else(|| Error::Extension("missing Draco extension".into()))?;
let view = document.as_value()["bufferViews"]
.as_array()
.and_then(|views| views.get(parsed.buffer_view))
.ok_or_else(|| Error::Extension("Draco bufferView out of range".into()))?;
let buffer = view
.get("buffer")
.and_then(Value::as_u64)
.and_then(|value| usize::try_from(value).ok())
.and_then(|index| resources.buffers.get(index))
.ok_or_else(|| Error::Extension("Draco buffer is not resolved".into()))?;
let start = view.get("byteOffset").and_then(Value::as_u64).unwrap_or(0) as usize;
let length = view
.get("byteLength")
.and_then(Value::as_u64)
.and_then(|value| usize::try_from(value).ok())
.ok_or_else(|| Error::Extension("Draco bufferView length is invalid".into()))?;
let end = start
.checked_add(length)
.filter(|end| *end <= buffer.len())
.ok_or_else(|| Error::Extension("Draco bufferView out of bounds".into()))?;
let mut mesh = Mesh::new();
MeshDecoder::new()
.decode(&mut DecoderBuffer::new(&buffer[start..end]), &mut mesh)
.map_err(Error::Decode)?;
Ok(mesh)
})())
}
}
fn remap_reference(value: &mut Value, map: &[Option<usize>], kind: &str) -> Result<()> {
let old = value
.as_u64()
.and_then(|value| usize::try_from(value).ok())
.ok_or_else(|| Error::Extension(format!("{kind} is invalid")))?;
let new = map
.get(old)
.and_then(|value| *value)
.ok_or_else(|| Error::Extension(format!("{kind} was removed")))?;
*value = Value::from(new);
Ok(())
}
#[cfg_attr(not(feature = "draco-decode"), allow(dead_code))]
#[derive(Clone, Debug)]
pub(crate) struct DracoContract {
pub buffer_view: usize,
pub attributes: Vec<(String, u32)>,
}
pub(crate) fn parse_draco_extension(value: Option<&Value>) -> Result<Option<DracoContract>> {
let Some(value) = value else {
return Ok(None);
};
let buffer_view = value
.get("bufferView")
.and_then(Value::as_u64)
.and_then(|value| usize::try_from(value).ok())
.ok_or_else(|| Error::Extension("Draco bufferView is invalid".into()))?;
let attributes = value
.get("attributes")
.and_then(Value::as_object)
.ok_or_else(|| Error::Extension("Draco attributes is invalid".into()))?
.iter()
.map(|(name, value)| {
value
.as_u64()
.and_then(|value| u32::try_from(value).ok())
.map(|value| (name.clone(), value))
.ok_or_else(|| Error::Extension(format!("Draco attribute {name} is invalid")))
})
.collect::<Result<Vec<_>>>()?;
Ok(Some(DracoContract {
buffer_view,
attributes,
}))
}
impl Default for ExtensionRegistry {
fn default() -> Self {
let mut registry = Self {
handlers: Vec::new(),
};
registry
.register(DracoExtension)
.expect("built-in extension names are unique");
#[cfg(feature = "write")]
{
registry
.register(MeshGpuInstancingExtension)
.expect("built-in extension names are unique");
registry
.register(StructuralMetadataExtension)
.expect("built-in extension names are unique");
for name in BINARY_FREE_EXTENSIONS {
registry
.register(BinaryFreeExtension(name))
.expect("built-in extension names are unique");
}
}
registry
}
}