use std::{
collections::{HashMap, HashSet},
sync::{Arc, Mutex},
};
use awsm_renderer_core::texture::texture_pool::TextureColorInfo;
use glam::Mat4;
use crate::materials::MaterialKey;
use crate::{meshes::MeshKey, textures::TextureKey, transforms::TransformKey, AwsmRenderer};
use super::{data::GltfData, error::AwsmGltfError};
mod animation;
mod extensions;
pub mod material;
mod mesh;
mod skin;
pub(super) mod transforms;
pub struct GltfPopulateContext {
pub data: Arc<GltfData>,
pub textures: Mutex<HashMap<GltfTextureKey, TextureKey>>,
pub(super) material_keys: Mutex<HashMap<GltfMaterialLookupKey, MaterialKey>>,
pub node_to_skin_transform:
Mutex<HashMap<GltfIndex, Arc<(Vec<TransformKey>, Vec<SkinInverseBindMatrix>)>>>,
pub transform_is_joint: Mutex<HashSet<TransformKey>>,
pub transform_is_instanced: Mutex<HashSet<TransformKey>>,
pub(super) node_animation_samplers: HashMap<GltfIndex, GltfNodeAnimationSamplers>,
pub key_lookups: Arc<Mutex<GltfKeyLookups>>,
}
#[derive(Debug, Clone, Default)]
pub struct GltfKeyLookups {
pub node_transforms: HashMap<String, TransformKey>,
pub node_meshes: HashMap<String, Vec<(Option<String>, Vec<MeshKey>)>>,
pub mesh_primitives: HashMap<String, Vec<MeshKey>>,
pub node_index_to_transform: HashMap<GltfIndex, TransformKey>,
pub all_mesh_keys: HashMap<GltfIndex, Vec<MeshKey>>,
}
impl GltfKeyLookups {
pub fn insert_transform(&mut self, node: &gltf::Node, key: TransformKey) {
if let Some(name) = node.name() {
self.node_transforms.insert(name.to_string(), key);
}
self.node_index_to_transform.insert(node.index(), key);
}
pub fn insert_mesh(&mut self, node: &gltf::Node, mesh: &gltf::Mesh, mesh_key: MeshKey) {
self.all_mesh_keys
.entry(mesh.index())
.or_default()
.push(mesh_key);
if let Some(mesh_name) = mesh.name() {
self.mesh_primitives
.entry(mesh_name.to_string())
.or_default()
.push(mesh_key);
}
if let Some(node_name) = node.name() {
let entry = self.node_meshes.entry(node_name.to_string()).or_default();
match mesh.name() {
None => {
entry.push((None, vec![mesh_key]));
}
Some(name) => {
let mut found = false;
for (mesh_name_opt, mesh_keys) in entry.iter_mut() {
if let Some(mesh_name) = mesh_name_opt {
if mesh_name == name {
mesh_keys.push(mesh_key);
found = true;
}
}
}
if !found {
entry.push((Some(name.to_string()), vec![mesh_key]));
}
}
}
}
}
pub fn meshes_for_node_iter(&self, node_name: &str) -> impl Iterator<Item = &MeshKey> {
self.node_meshes
.get(node_name)
.into_iter()
.flat_map(|entries| entries.iter())
.flat_map(|(_mesh_name_opt, mesh_keys)| mesh_keys.iter())
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct GltfTextureKey {
pub index: GltfIndex,
pub color: TextureColorInfo,
}
type SkinInverseBindMatrix = Mat4;
type GltfIndex = usize;
#[derive(Clone, Copy, Debug)]
pub(super) struct GltfAnimationSamplerRef {
pub animation_index: usize,
pub channel_index: usize,
pub sampler_index: usize,
}
#[derive(Clone, Debug, Default)]
pub(super) struct GltfNodeAnimationSamplers {
pub translation: Option<GltfAnimationSamplerRef>,
pub rotation: Option<GltfAnimationSamplerRef>,
pub scale: Option<GltfAnimationSamplerRef>,
pub morph: Option<GltfAnimationSamplerRef>,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub(super) struct GltfMaterialLookupKey {
pub material_index: Option<usize>,
pub vertex_color_set_index: Option<usize>,
pub hud: bool,
}
impl AwsmRenderer {
pub async fn populate_gltf(
&mut self,
gltf_data: impl Into<Arc<GltfData>>,
scene: Option<usize>,
) -> anyhow::Result<GltfPopulateContext> {
let gltf_data = gltf_data.into();
self.gltf.raw_datas.push(gltf_data.clone());
let mut mesh_keys = Vec::new();
let node_animation_samplers = build_node_animation_sampler_lookup(&gltf_data.doc);
let ctx = GltfPopulateContext {
data: gltf_data,
textures: Mutex::new(HashMap::new()),
material_keys: Mutex::new(HashMap::new()),
node_to_skin_transform: Mutex::new(HashMap::new()),
transform_is_joint: Mutex::new(HashSet::new()),
transform_is_instanced: Mutex::new(HashSet::new()),
node_animation_samplers,
key_lookups: Arc::new(Mutex::new(GltfKeyLookups::default())),
};
let scene = match scene {
Some(index) => ctx
.data
.doc
.scenes()
.nth(index)
.ok_or(AwsmGltfError::InvalidScene(index))?,
None => match ctx.data.doc.default_scene() {
Some(scene) => scene,
None => ctx
.data
.doc
.scenes()
.next()
.ok_or(AwsmGltfError::NoDefaultScene)?,
},
};
for node in scene.nodes() {
self.populate_gltf_node_transform(&ctx, &node, None)?;
}
for node in scene.nodes() {
self.populate_gltf_node_extension_instancing(&ctx, &node)?;
}
for node in scene.nodes() {
self.populate_gltf_node_skin(&ctx, &node)?;
}
for node in scene.nodes() {
self.populate_gltf_node_animation(&ctx, &node)?;
}
for node in scene.nodes() {
mesh_keys.push(self.populate_gltf_node_mesh(&ctx, &node).await?);
}
self.finalize_gpu_textures().await?;
Ok(ctx)
}
}
impl GltfPopulateContext {
pub(super) fn resolve_animation_sampler(
&self,
sampler_ref: GltfAnimationSamplerRef,
) -> Result<gltf::animation::Sampler<'_>, AwsmGltfError> {
self.data
.doc
.animations()
.nth(sampler_ref.animation_index)
.and_then(|animation| animation.samplers().nth(sampler_ref.sampler_index))
.ok_or(AwsmGltfError::MissingAnimationSampler {
animation_index: sampler_ref.animation_index,
channel_index: sampler_ref.channel_index,
sampler_index: sampler_ref.sampler_index,
})
}
}
fn build_node_animation_sampler_lookup(
doc: &gltf::Document,
) -> HashMap<GltfIndex, GltfNodeAnimationSamplers> {
let mut out = HashMap::<GltfIndex, GltfNodeAnimationSamplers>::new();
for animation in doc.animations() {
for channel in animation.channels() {
let node_index = channel.target().node().index();
let entry = out.entry(node_index).or_default();
let sampler_ref = GltfAnimationSamplerRef {
animation_index: animation.index(),
channel_index: channel.index(),
sampler_index: channel.sampler().index(),
};
match channel.target().property() {
gltf::animation::Property::Translation => {
if entry.translation.is_none() {
entry.translation = Some(sampler_ref);
}
}
gltf::animation::Property::Rotation => {
if entry.rotation.is_none() {
entry.rotation = Some(sampler_ref);
}
}
gltf::animation::Property::Scale => {
if entry.scale.is_none() {
entry.scale = Some(sampler_ref);
}
}
gltf::animation::Property::MorphTargetWeights => {
if entry.morph.is_none() {
entry.morph = Some(sampler_ref);
}
}
}
}
}
out
}