mod khr_materials_anisotropy;
mod khr_materials_clearcoat;
mod khr_materials_specular;
use alloc::sync::Arc;
use async_lock::RwLock;
use bevy_asset::{Handle, LoadContext};
use bevy_ecs::{
entity::Entity,
resource::Resource,
world::{EntityWorldMut, World},
};
use bevy_mesh::{Mesh, MeshVertexAttribute};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use gltf::Node;
use bevy_platform::collections::HashMap;
#[cfg(feature = "bevy_animation")]
use {bevy_animation::AnimationClip, bevy_platform::collections::HashSet};
use crate::{GltfLoaderSettings, GltfMaterial, GltfMesh};
pub(crate) use self::{
khr_materials_anisotropy::AnisotropyExtension, khr_materials_clearcoat::ClearcoatExtension,
khr_materials_specular::SpecularExtension,
};
#[derive(Resource, Default)]
pub struct GltfExtensionHandlers(pub Arc<RwLock<Vec<Box<dyn ErasedGltfExtensionHandler>>>>);
pub trait GltfExtensionHandler: Send + Sync + 'static {
fn dyn_clone(&self) -> Box<dyn ErasedGltfExtensionHandler>;
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_root(
&mut self,
load_context: &mut LoadContext<'_>,
gltf: &gltf::Gltf,
settings: &GltfLoaderSettings,
) {
}
#[cfg(feature = "bevy_animation")]
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_animation(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_animation: &gltf::Animation,
animation_clip: &mut AnimationClip,
) {
}
#[cfg(feature = "bevy_animation")]
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_animations_collected(
&mut self,
load_context: &mut LoadContext<'_>,
animations: &[Handle<AnimationClip>],
named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,
animation_roots: &HashSet<usize>,
) {
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_texture(&mut self, gltf_texture: &gltf::Texture, texture: Handle<bevy_image::Image>) {}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_material(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_material: &gltf::Material,
material: Handle<GltfMaterial>,
material_asset: &GltfMaterial,
material_label: &str,
) {
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_gltf_primitive(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_document: &gltf::Gltf,
gltf_mesh: &gltf::Mesh,
gltf_primitive: &gltf::Primitive,
buffer_data: &[Vec<u8>],
custom_vertex_attributes: &HashMap<Box<str>, MeshVertexAttribute>,
gltf_mesh_on_skinned_nodes: bool,
gltf_mesh_on_non_skinned_nodes: bool,
user_mesh: &mut Option<Mesh>,
) -> impl ConditionalSendFuture<Output = ()> {
async {}
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_gltf_mesh(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_mesh: &gltf::Mesh,
mesh: Handle<GltfMesh>,
) {
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_spawn_mesh_and_material(
&mut self,
load_context: &mut LoadContext<'_>,
primitive: &gltf::Primitive,
mesh: &gltf::Mesh,
material: &gltf::Material,
entity: &mut EntityWorldMut,
material_label: &str,
) {
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_scene_completed(
&mut self,
load_context: &mut LoadContext<'_>,
scene: &gltf::Scene,
world_root_id: Entity,
scene_world: &mut World,
) {
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_gltf_node(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
) {
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_spawn_light_directional(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
) {
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_spawn_light_point(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
) {
}
#[expect(
unused,
reason = "default trait implementations do not use the arguments because they are no-ops"
)]
fn on_spawn_light_spot(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
) {
}
}
pub trait ErasedGltfExtensionHandler: Send + Sync + 'static {
fn dyn_clone(&self) -> Box<dyn ErasedGltfExtensionHandler>;
fn on_root(
&mut self,
load_context: &mut LoadContext<'_>,
gltf: &gltf::Gltf,
settings: &GltfLoaderSettings,
);
#[cfg(feature = "bevy_animation")]
fn on_animation(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_animation: &gltf::Animation,
animation_clip: &mut AnimationClip,
);
#[cfg(feature = "bevy_animation")]
fn on_animations_collected(
&mut self,
load_context: &mut LoadContext<'_>,
animations: &[Handle<AnimationClip>],
named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,
animation_roots: &HashSet<usize>,
);
fn on_texture(&mut self, gltf_texture: &gltf::Texture, texture: Handle<bevy_image::Image>);
fn on_material(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_material: &gltf::Material,
material: Handle<GltfMaterial>,
material_asset: &GltfMaterial,
material_label: &str,
);
fn on_gltf_primitive<'a>(
&'a mut self,
load_context: &'a mut LoadContext<'_>,
gltf_document: &'a gltf::Gltf,
gltf_mesh: &'a gltf::Mesh,
gltf_primitive: &'a gltf::Primitive,
buffer_data: &'a [Vec<u8>],
custom_vertex_attributes: &'a HashMap<Box<str>, MeshVertexAttribute>,
gltf_mesh_on_skinned_nodes: bool,
gltf_mesh_on_non_skinned_nodes: bool,
user_mesh: &'a mut Option<Mesh>,
) -> BoxedFuture<'a, ()>;
fn on_gltf_mesh(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_mesh: &gltf::Mesh,
mesh: Handle<GltfMesh>,
);
fn on_spawn_mesh_and_material(
&mut self,
load_context: &mut LoadContext<'_>,
primitive: &gltf::Primitive,
mesh: &gltf::Mesh,
material: &gltf::Material,
entity: &mut EntityWorldMut,
material_label: &str,
);
fn on_scene_completed(
&mut self,
load_context: &mut LoadContext<'_>,
scene: &gltf::Scene,
world_root_id: Entity,
scene_world: &mut World,
);
fn on_gltf_node(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
);
fn on_spawn_light_directional(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
);
fn on_spawn_light_point(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
);
fn on_spawn_light_spot(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
);
}
impl<H: GltfExtensionHandler> ErasedGltfExtensionHandler for H {
fn dyn_clone(&self) -> Box<dyn ErasedGltfExtensionHandler> {
Self::dyn_clone(self)
}
fn on_root(
&mut self,
load_context: &mut LoadContext<'_>,
gltf: &gltf::Gltf,
settings: &GltfLoaderSettings,
) {
Self::on_root(self, load_context, gltf, settings);
}
#[cfg(feature = "bevy_animation")]
fn on_animation(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_animation: &gltf::Animation,
animation_clip: &mut AnimationClip,
) {
Self::on_animation(self, load_context, gltf_animation, animation_clip);
}
#[cfg(feature = "bevy_animation")]
fn on_animations_collected(
&mut self,
load_context: &mut LoadContext<'_>,
animations: &[Handle<AnimationClip>],
named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,
animation_roots: &HashSet<usize>,
) {
Self::on_animations_collected(
self,
load_context,
animations,
named_animations,
animation_roots,
);
}
fn on_texture(&mut self, gltf_texture: &gltf::Texture, texture: Handle<bevy_image::Image>) {
Self::on_texture(self, gltf_texture, texture);
}
fn on_material(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_material: &gltf::Material,
material: Handle<GltfMaterial>,
material_asset: &GltfMaterial,
material_label: &str,
) {
Self::on_material(
self,
load_context,
gltf_material,
material,
material_asset,
material_label,
);
}
fn on_gltf_primitive<'a>(
&'a mut self,
load_context: &'a mut LoadContext<'_>,
gltf_document: &'a gltf::Gltf,
gltf_mesh: &'a gltf::Mesh,
gltf_primitive: &'a gltf::Primitive,
buffer_data: &'a [Vec<u8>],
custom_vertex_attributes: &'a HashMap<Box<str>, MeshVertexAttribute>,
gltf_mesh_on_skinned_nodes: bool,
gltf_mesh_on_non_skinned_nodes: bool,
user_mesh: &'a mut Option<Mesh>,
) -> BoxedFuture<'a, ()> {
Box::pin(async move {
Self::on_gltf_primitive(
self,
load_context,
gltf_document,
gltf_mesh,
gltf_primitive,
buffer_data,
custom_vertex_attributes,
gltf_mesh_on_skinned_nodes,
gltf_mesh_on_non_skinned_nodes,
user_mesh,
)
.await;
})
}
fn on_gltf_mesh(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_mesh: &gltf::Mesh,
mesh: Handle<GltfMesh>,
) {
Self::on_gltf_mesh(self, load_context, gltf_mesh, mesh);
}
fn on_spawn_mesh_and_material(
&mut self,
load_context: &mut LoadContext<'_>,
primitive: &gltf::Primitive,
mesh: &gltf::Mesh,
material: &gltf::Material,
entity: &mut EntityWorldMut,
material_label: &str,
) {
Self::on_spawn_mesh_and_material(
self,
load_context,
primitive,
mesh,
material,
entity,
material_label,
);
}
fn on_scene_completed(
&mut self,
load_context: &mut LoadContext<'_>,
scene: &gltf::Scene,
world_root_id: Entity,
scene_world: &mut World,
) {
Self::on_scene_completed(self, load_context, scene, world_root_id, scene_world);
}
fn on_gltf_node(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
) {
Self::on_gltf_node(self, load_context, gltf_node, entity);
}
fn on_spawn_light_directional(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
) {
Self::on_spawn_light_directional(self, load_context, gltf_node, entity);
}
fn on_spawn_light_point(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
) {
Self::on_spawn_light_point(self, load_context, gltf_node, entity);
}
fn on_spawn_light_spot(
&mut self,
load_context: &mut LoadContext<'_>,
gltf_node: &Node,
entity: &mut EntityWorldMut,
) {
Self::on_spawn_light_spot(self, load_context, gltf_node, entity);
}
}
impl Clone for Box<dyn ErasedGltfExtensionHandler> {
fn clone(&self) -> Self {
self.dyn_clone()
}
}