Export glTF skins as a Gltf struct (#14343)
# Objective - Make skin data of glTF meshes available for users, so it would be possible to create skinned meshes without spawning a scene. - I believe it contributes to https://github.com/bevyengine/bevy/issues/13681 ? ## Solution - Add a new `GltfSkin`, representing skin data from a glTF file, new member `skin` to `GltfNode` and both `skins` + `named_skins` to `Gltf` (a la meshes/nodes). - Rewrite glTF nodes resolution as an iterator which sorts nodes by their dependencies (nodes without dependencies first). So when we create `GltfNodes` with their associated `GltfSkin` while iterating, their dependencies already have been loaded. - Make a distinction between `GltfSkin` and `SkinnedMeshInverseBindposes` in assets: prior to this PR, `GltfAssetLabel::Skin(n)` was responsible not for a skin, but for one of skin's components. Now `GltfAssetLabel::InverseBindMatrices(n)` will map to `SkinnedMeshInverseBindposes`, and `GltfAssetLabel::Skin(n)` will map to `GltfSkin`. ## Testing - New test `skin_node` does just that; it tests whether or not `GltfSkin` was loaded properly. ## Migration Guide - Change `GltfAssetLabel::Skin(..)` to `GltfAssetLabel::InverseBindMatrices(..)`.
This commit is contained in:
parent
df61117850
commit
5f2570eb4c
@ -109,7 +109,7 @@ use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
|
|||||||
use bevy_pbr::StandardMaterial;
|
use bevy_pbr::StandardMaterial;
|
||||||
use bevy_reflect::{Reflect, TypePath};
|
use bevy_reflect::{Reflect, TypePath};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
mesh::{Mesh, MeshVertexAttribute},
|
mesh::{skinning::SkinnedMeshInverseBindposes, Mesh, MeshVertexAttribute},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
texture::CompressedImageFormats,
|
texture::CompressedImageFormats,
|
||||||
};
|
};
|
||||||
@ -153,6 +153,7 @@ impl Plugin for GltfPlugin {
|
|||||||
.init_asset::<GltfNode>()
|
.init_asset::<GltfNode>()
|
||||||
.init_asset::<GltfPrimitive>()
|
.init_asset::<GltfPrimitive>()
|
||||||
.init_asset::<GltfMesh>()
|
.init_asset::<GltfMesh>()
|
||||||
|
.init_asset::<GltfSkin>()
|
||||||
.preregister_asset_loader::<GltfLoader>(&["gltf", "glb"]);
|
.preregister_asset_loader::<GltfLoader>(&["gltf", "glb"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,6 +188,10 @@ pub struct Gltf {
|
|||||||
pub nodes: Vec<Handle<GltfNode>>,
|
pub nodes: Vec<Handle<GltfNode>>,
|
||||||
/// Named nodes loaded from the glTF file.
|
/// Named nodes loaded from the glTF file.
|
||||||
pub named_nodes: HashMap<Box<str>, Handle<GltfNode>>,
|
pub named_nodes: HashMap<Box<str>, Handle<GltfNode>>,
|
||||||
|
/// All skins loaded from the glTF file.
|
||||||
|
pub skins: Vec<Handle<GltfSkin>>,
|
||||||
|
/// Named skins loaded from the glTF file.
|
||||||
|
pub named_skins: HashMap<Box<str>, Handle<GltfSkin>>,
|
||||||
/// Default scene to be displayed.
|
/// Default scene to be displayed.
|
||||||
pub default_scene: Option<Handle<Scene>>,
|
pub default_scene: Option<Handle<Scene>>,
|
||||||
/// All animations loaded from the glTF file.
|
/// All animations loaded from the glTF file.
|
||||||
@ -200,7 +205,8 @@ pub struct Gltf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A glTF node with all of its child nodes, its [`GltfMesh`],
|
/// A glTF node with all of its child nodes, its [`GltfMesh`],
|
||||||
/// [`Transform`](bevy_transform::prelude::Transform) and an optional [`GltfExtras`].
|
/// [`Transform`](bevy_transform::prelude::Transform), its optional [`GltfSkin`]
|
||||||
|
/// and an optional [`GltfExtras`].
|
||||||
///
|
///
|
||||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-node).
|
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-node).
|
||||||
#[derive(Asset, Debug, Clone, TypePath)]
|
#[derive(Asset, Debug, Clone, TypePath)]
|
||||||
@ -213,8 +219,13 @@ pub struct GltfNode {
|
|||||||
pub children: Vec<Handle<GltfNode>>,
|
pub children: Vec<Handle<GltfNode>>,
|
||||||
/// Mesh of the node.
|
/// Mesh of the node.
|
||||||
pub mesh: Option<Handle<GltfMesh>>,
|
pub mesh: Option<Handle<GltfMesh>>,
|
||||||
|
/// Skin of the node.
|
||||||
|
pub skin: Option<Handle<GltfSkin>>,
|
||||||
/// Local transform.
|
/// Local transform.
|
||||||
pub transform: bevy_transform::prelude::Transform,
|
pub transform: bevy_transform::prelude::Transform,
|
||||||
|
/// Is this node used as an animation root
|
||||||
|
#[cfg(feature = "bevy_animation")]
|
||||||
|
pub is_animation_root: bool,
|
||||||
/// Additional data.
|
/// Additional data.
|
||||||
pub extras: Option<GltfExtras>,
|
pub extras: Option<GltfExtras>,
|
||||||
}
|
}
|
||||||
@ -226,6 +237,7 @@ impl GltfNode {
|
|||||||
children: Vec<Handle<GltfNode>>,
|
children: Vec<Handle<GltfNode>>,
|
||||||
mesh: Option<Handle<GltfMesh>>,
|
mesh: Option<Handle<GltfMesh>>,
|
||||||
transform: bevy_transform::prelude::Transform,
|
transform: bevy_transform::prelude::Transform,
|
||||||
|
skin: Option<Handle<GltfSkin>>,
|
||||||
extras: Option<GltfExtras>,
|
extras: Option<GltfExtras>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -238,16 +250,73 @@ impl GltfNode {
|
|||||||
children,
|
children,
|
||||||
mesh,
|
mesh,
|
||||||
transform,
|
transform,
|
||||||
|
skin,
|
||||||
|
#[cfg(feature = "bevy_animation")]
|
||||||
|
is_animation_root: false,
|
||||||
extras,
|
extras,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a node with animation root mark
|
||||||
|
#[cfg(feature = "bevy_animation")]
|
||||||
|
pub fn with_animation_root(self, is_animation_root: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
is_animation_root,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Subasset label for this node within the gLTF parent asset.
|
/// Subasset label for this node within the gLTF parent asset.
|
||||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||||
GltfAssetLabel::Node(self.index)
|
GltfAssetLabel::Node(self.index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A glTF skin with all of its joint nodes, [`SkinnedMeshInversiveBindposes`](bevy_render::mesh::skinning::SkinnedMeshInverseBindposes)
|
||||||
|
/// and an optional [`GltfExtras`].
|
||||||
|
///
|
||||||
|
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-skin).
|
||||||
|
#[derive(Asset, Debug, Clone, TypePath)]
|
||||||
|
pub struct GltfSkin {
|
||||||
|
/// Index of the skin inside the scene
|
||||||
|
pub index: usize,
|
||||||
|
/// Computed name for a skin - either a user defined skin name from gLTF or a generated name from index
|
||||||
|
pub name: String,
|
||||||
|
/// All the nodes that form this skin.
|
||||||
|
pub joints: Vec<Handle<GltfNode>>,
|
||||||
|
/// Inverse-bind matricy of this skin.
|
||||||
|
pub inverse_bind_matrices: Handle<SkinnedMeshInverseBindposes>,
|
||||||
|
/// Additional data.
|
||||||
|
pub extras: Option<GltfExtras>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GltfSkin {
|
||||||
|
/// Create a skin extracting name and index from glTF def
|
||||||
|
pub fn new(
|
||||||
|
skin: &gltf::Skin,
|
||||||
|
joints: Vec<Handle<GltfNode>>,
|
||||||
|
inverse_bind_matrices: Handle<SkinnedMeshInverseBindposes>,
|
||||||
|
extras: Option<GltfExtras>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
index: skin.index(),
|
||||||
|
name: if let Some(name) = skin.name() {
|
||||||
|
name.to_string()
|
||||||
|
} else {
|
||||||
|
format!("GltfSkin{}", skin.index())
|
||||||
|
},
|
||||||
|
joints,
|
||||||
|
inverse_bind_matrices,
|
||||||
|
extras,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subasset label for this skin within the gLTF parent asset.
|
||||||
|
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||||
|
GltfAssetLabel::Skin(self.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A glTF mesh, which may consist of multiple [`GltfPrimitives`](GltfPrimitive)
|
/// A glTF mesh, which may consist of multiple [`GltfPrimitives`](GltfPrimitive)
|
||||||
/// and an optional [`GltfExtras`].
|
/// and an optional [`GltfExtras`].
|
||||||
///
|
///
|
||||||
@ -449,8 +518,10 @@ pub enum GltfAssetLabel {
|
|||||||
DefaultMaterial,
|
DefaultMaterial,
|
||||||
/// `Animation{}`: glTF Animation as Bevy `AnimationClip`
|
/// `Animation{}`: glTF Animation as Bevy `AnimationClip`
|
||||||
Animation(usize),
|
Animation(usize),
|
||||||
/// `Skin{}`: glTF mesh skin as Bevy `SkinnedMeshInverseBindposes`
|
/// `Skin{}`: glTF mesh skin as `GltfSkin`
|
||||||
Skin(usize),
|
Skin(usize),
|
||||||
|
/// `Skin{}/InverseBindMatrices`: glTF mesh skin matrices as Bevy `SkinnedMeshInverseBindposes`
|
||||||
|
InverseBindMatrices(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for GltfAssetLabel {
|
impl std::fmt::Display for GltfAssetLabel {
|
||||||
@ -480,6 +551,9 @@ impl std::fmt::Display for GltfAssetLabel {
|
|||||||
GltfAssetLabel::DefaultMaterial => f.write_str("DefaultMaterial"),
|
GltfAssetLabel::DefaultMaterial => f.write_str("DefaultMaterial"),
|
||||||
GltfAssetLabel::Animation(index) => f.write_str(&format!("Animation{index}")),
|
GltfAssetLabel::Animation(index) => f.write_str(&format!("Animation{index}")),
|
||||||
GltfAssetLabel::Skin(index) => f.write_str(&format!("Skin{index}")),
|
GltfAssetLabel::Skin(index) => f.write_str(&format!("Skin{index}")),
|
||||||
|
GltfAssetLabel::InverseBindMatrices(index) => {
|
||||||
|
f.write_str(&format!("Skin{index}/InverseBindMatrices"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
vertex_attributes::convert_attribute, Gltf, GltfAssetLabel, GltfExtras, GltfMaterialExtras,
|
vertex_attributes::convert_attribute, Gltf, GltfAssetLabel, GltfExtras, GltfMaterialExtras,
|
||||||
GltfMeshExtras, GltfNode, GltfSceneExtras,
|
GltfMeshExtras, GltfNode, GltfSceneExtras, GltfSkin,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "bevy_animation")]
|
#[cfg(feature = "bevy_animation")]
|
||||||
@ -586,42 +586,6 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
meshes.push(handle);
|
meshes.push(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut nodes_intermediate = vec![];
|
|
||||||
let mut named_nodes_intermediate = HashMap::default();
|
|
||||||
for node in gltf.nodes() {
|
|
||||||
nodes_intermediate.push((
|
|
||||||
GltfNode::new(
|
|
||||||
&node,
|
|
||||||
vec![],
|
|
||||||
node.mesh()
|
|
||||||
.map(|mesh| mesh.index())
|
|
||||||
.and_then(|i: usize| meshes.get(i).cloned()),
|
|
||||||
node_transform(&node),
|
|
||||||
get_gltf_extras(node.extras()),
|
|
||||||
),
|
|
||||||
node.children()
|
|
||||||
.map(|child| {
|
|
||||||
(
|
|
||||||
child.index(),
|
|
||||||
load_context
|
|
||||||
.get_label_handle(format!("{}", GltfAssetLabel::Node(child.index()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
));
|
|
||||||
if let Some(name) = node.name() {
|
|
||||||
named_nodes_intermediate.insert(name, node.index());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let nodes = resolve_node_hierarchy(nodes_intermediate)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|node| load_context.add_labeled_asset(node.asset_label().to_string(), node))
|
|
||||||
.collect::<Vec<Handle<GltfNode>>>();
|
|
||||||
let named_nodes = named_nodes_intermediate
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(name, index)| nodes.get(index).map(|handle| (name.into(), handle.clone())))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let skinned_mesh_inverse_bindposes: Vec<_> = gltf
|
let skinned_mesh_inverse_bindposes: Vec<_> = gltf
|
||||||
.skins()
|
.skins()
|
||||||
.map(|gltf_skin| {
|
.map(|gltf_skin| {
|
||||||
@ -633,12 +597,76 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
load_context.add_labeled_asset(
|
load_context.add_labeled_asset(
|
||||||
skin_label(&gltf_skin),
|
inverse_bind_matrices_label(&gltf_skin),
|
||||||
SkinnedMeshInverseBindposes::from(local_to_bone_bind_matrices),
|
SkinnedMeshInverseBindposes::from(local_to_bone_bind_matrices),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let mut nodes = HashMap::<usize, Handle<GltfNode>>::new();
|
||||||
|
let mut named_nodes = HashMap::new();
|
||||||
|
let mut skins = vec![];
|
||||||
|
let mut named_skins = HashMap::default();
|
||||||
|
for node in GltfTreeIterator::try_new(&gltf)? {
|
||||||
|
let skin = node.skin().map(|skin| {
|
||||||
|
let joints = skin
|
||||||
|
.joints()
|
||||||
|
.map(|joint| nodes.get(&joint.index()).unwrap().clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let gltf_skin = GltfSkin::new(
|
||||||
|
&skin,
|
||||||
|
joints,
|
||||||
|
skinned_mesh_inverse_bindposes[skin.index()].clone(),
|
||||||
|
get_gltf_extras(skin.extras()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let handle = load_context.add_labeled_asset(skin_label(&skin), gltf_skin);
|
||||||
|
|
||||||
|
skins.push(handle.clone());
|
||||||
|
if let Some(name) = skin.name() {
|
||||||
|
named_skins.insert(name.into(), handle.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
handle
|
||||||
|
});
|
||||||
|
|
||||||
|
let children = node
|
||||||
|
.children()
|
||||||
|
.map(|child| nodes.get(&child.index()).unwrap().clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mesh = node
|
||||||
|
.mesh()
|
||||||
|
.map(|mesh| mesh.index())
|
||||||
|
.and_then(|i| meshes.get(i).cloned());
|
||||||
|
|
||||||
|
let gltf_node = GltfNode::new(
|
||||||
|
&node,
|
||||||
|
children,
|
||||||
|
mesh,
|
||||||
|
node_transform(&node),
|
||||||
|
skin,
|
||||||
|
get_gltf_extras(node.extras()),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_animation")]
|
||||||
|
let gltf_node = gltf_node.with_animation_root(animation_roots.contains(&node.index()));
|
||||||
|
|
||||||
|
let handle = load_context.add_labeled_asset(gltf_node.asset_label().to_string(), gltf_node);
|
||||||
|
nodes.insert(node.index(), handle.clone());
|
||||||
|
if let Some(name) = node.name() {
|
||||||
|
named_nodes.insert(name.into(), handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut nodes_to_sort = nodes.into_iter().collect::<Vec<_>>();
|
||||||
|
nodes_to_sort.sort_by_key(|(i, _)| *i);
|
||||||
|
let nodes = nodes_to_sort
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, resolved)| resolved)
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut scenes = vec![];
|
let mut scenes = vec![];
|
||||||
let mut named_scenes = HashMap::default();
|
let mut named_scenes = HashMap::default();
|
||||||
let mut active_camera_found = false;
|
let mut active_camera_found = false;
|
||||||
@ -700,7 +728,6 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut warned_about_max_joints = HashSet::new();
|
|
||||||
for (&entity, &skin_index) in &entity_to_skin_index_map {
|
for (&entity, &skin_index) in &entity_to_skin_index_map {
|
||||||
let mut entity = world.entity_mut(entity);
|
let mut entity = world.entity_mut(entity);
|
||||||
let skin = gltf.skins().nth(skin_index).unwrap();
|
let skin = gltf.skins().nth(skin_index).unwrap();
|
||||||
@ -709,16 +736,6 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
.map(|node| node_index_to_entity_map[&node.index()])
|
.map(|node| node_index_to_entity_map[&node.index()])
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if joint_entities.len() > MAX_JOINTS && warned_about_max_joints.insert(skin_index) {
|
|
||||||
warn!(
|
|
||||||
"The glTF skin {:?} has {} joints, but the maximum supported is {}",
|
|
||||||
skin.name()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.unwrap_or_else(|| skin.index().to_string()),
|
|
||||||
joint_entities.len(),
|
|
||||||
MAX_JOINTS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
entity.insert(SkinnedMesh {
|
entity.insert(SkinnedMesh {
|
||||||
inverse_bindposes: skinned_mesh_inverse_bindposes[skin_index].clone(),
|
inverse_bindposes: skinned_mesh_inverse_bindposes[skin_index].clone(),
|
||||||
joints: joint_entities,
|
joints: joint_entities,
|
||||||
@ -742,6 +759,8 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
named_scenes,
|
named_scenes,
|
||||||
meshes,
|
meshes,
|
||||||
named_meshes,
|
named_meshes,
|
||||||
|
skins,
|
||||||
|
named_skins,
|
||||||
materials,
|
materials,
|
||||||
named_materials,
|
named_materials,
|
||||||
nodes,
|
nodes,
|
||||||
@ -1547,10 +1566,16 @@ fn scene_label(scene: &gltf::Scene) -> String {
|
|||||||
GltfAssetLabel::Scene(scene.index()).to_string()
|
GltfAssetLabel::Scene(scene.index()).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the label for the `skin`.
|
||||||
fn skin_label(skin: &gltf::Skin) -> String {
|
fn skin_label(skin: &gltf::Skin) -> String {
|
||||||
GltfAssetLabel::Skin(skin.index()).to_string()
|
GltfAssetLabel::Skin(skin.index()).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the label for the `inverseBindMatrices` of the node.
|
||||||
|
fn inverse_bind_matrices_label(skin: &gltf::Skin) -> String {
|
||||||
|
GltfAssetLabel::InverseBindMatrices(skin.index()).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/// Extracts the texture sampler data from the glTF texture.
|
/// Extracts the texture sampler data from the glTF texture.
|
||||||
fn texture_sampler(texture: &gltf::Texture) -> ImageSamplerDescriptor {
|
fn texture_sampler(texture: &gltf::Texture) -> ImageSamplerDescriptor {
|
||||||
let gltf_sampler = texture.sampler();
|
let gltf_sampler = texture.sampler();
|
||||||
@ -1667,57 +1692,109 @@ async fn load_buffers(
|
|||||||
Ok(buffer_data)
|
Ok(buffer_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::result_large_err)]
|
/// Iterator for a Gltf tree.
|
||||||
fn resolve_node_hierarchy(
|
///
|
||||||
nodes_intermediate: Vec<(GltfNode, Vec<(usize, Handle<GltfNode>)>)>,
|
/// It resolves a Gltf tree and allows for a safe Gltf nodes iteration,
|
||||||
) -> Result<Vec<GltfNode>, GltfError> {
|
/// putting dependant nodes before dependencies.
|
||||||
let mut empty_children = VecDeque::new();
|
struct GltfTreeIterator<'a> {
|
||||||
let mut parents = vec![None; nodes_intermediate.len()];
|
nodes: Vec<gltf::Node<'a>>,
|
||||||
let mut unprocessed_nodes = nodes_intermediate
|
}
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, (node, children))| {
|
|
||||||
for (child_index, _child_handle) in &children {
|
|
||||||
let parent = parents.get_mut(*child_index).unwrap();
|
|
||||||
*parent = Some(i);
|
|
||||||
}
|
|
||||||
let children = children.into_iter().collect::<HashMap<_, _>>();
|
|
||||||
if children.is_empty() {
|
|
||||||
empty_children.push_back(i);
|
|
||||||
}
|
|
||||||
(i, (node, children))
|
|
||||||
})
|
|
||||||
.collect::<HashMap<_, _>>();
|
|
||||||
let mut nodes = std::collections::HashMap::<usize, GltfNode>::new();
|
|
||||||
while let Some(index) = empty_children.pop_front() {
|
|
||||||
let (node, children) = unprocessed_nodes.remove(&index).unwrap();
|
|
||||||
assert!(children.is_empty());
|
|
||||||
nodes.insert(index, node);
|
|
||||||
if let Some(parent_index) = parents[index] {
|
|
||||||
let (parent_node, parent_children) = unprocessed_nodes.get_mut(&parent_index).unwrap();
|
|
||||||
|
|
||||||
let handle = parent_children.remove(&index).unwrap();
|
impl<'a> GltfTreeIterator<'a> {
|
||||||
parent_node.children.push(handle);
|
#[allow(clippy::result_large_err)]
|
||||||
if parent_children.is_empty() {
|
fn try_new(gltf: &'a gltf::Gltf) -> Result<Self, GltfError> {
|
||||||
empty_children.push_back(parent_index);
|
let nodes = gltf.nodes().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut empty_children = VecDeque::new();
|
||||||
|
let mut parents = vec![None; nodes.len()];
|
||||||
|
let mut unprocessed_nodes = nodes
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, node)| {
|
||||||
|
let children = node
|
||||||
|
.children()
|
||||||
|
.map(|child| child.index())
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
for &child in &children {
|
||||||
|
let parent = parents.get_mut(child).unwrap();
|
||||||
|
*parent = Some(i);
|
||||||
|
}
|
||||||
|
if children.is_empty() {
|
||||||
|
empty_children.push_back(i);
|
||||||
|
}
|
||||||
|
(i, (node, children))
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
let mut nodes = Vec::new();
|
||||||
|
let mut warned_about_max_joints = HashSet::new();
|
||||||
|
while let Some(index) = empty_children.pop_front() {
|
||||||
|
if let Some(skin) = unprocessed_nodes.get(&index).unwrap().0.skin() {
|
||||||
|
if skin.joints().len() > MAX_JOINTS && warned_about_max_joints.insert(skin.index())
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"The glTF skin {:?} has {} joints, but the maximum supported is {}",
|
||||||
|
skin.name()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.unwrap_or_else(|| skin.index().to_string()),
|
||||||
|
skin.joints().len(),
|
||||||
|
MAX_JOINTS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let skin_has_dependencies = skin
|
||||||
|
.joints()
|
||||||
|
.any(|joint| unprocessed_nodes.contains_key(&joint.index()));
|
||||||
|
|
||||||
|
if skin_has_dependencies && unprocessed_nodes.len() != 1 {
|
||||||
|
empty_children.push_back(index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (node, children) = unprocessed_nodes.remove(&index).unwrap();
|
||||||
|
assert!(children.is_empty());
|
||||||
|
nodes.push(node);
|
||||||
|
|
||||||
|
if let Some(parent_index) = parents[index] {
|
||||||
|
let (_, parent_children) = unprocessed_nodes.get_mut(&parent_index).unwrap();
|
||||||
|
|
||||||
|
assert!(parent_children.remove(&index));
|
||||||
|
if parent_children.is_empty() {
|
||||||
|
empty_children.push_back(parent_index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !unprocessed_nodes.is_empty() {
|
||||||
|
return Err(GltfError::CircularChildren(format!(
|
||||||
|
"{:?}",
|
||||||
|
unprocessed_nodes
|
||||||
|
.iter()
|
||||||
|
.map(|(k, _v)| *k)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.reverse();
|
||||||
|
Ok(Self {
|
||||||
|
nodes: nodes.into_iter().collect(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if !unprocessed_nodes.is_empty() {
|
}
|
||||||
return Err(GltfError::CircularChildren(format!(
|
|
||||||
"{:?}",
|
impl<'a> Iterator for GltfTreeIterator<'a> {
|
||||||
unprocessed_nodes
|
type Item = gltf::Node<'a>;
|
||||||
.iter()
|
|
||||||
.map(|(k, _v)| *k)
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
.collect::<Vec<_>>(),
|
self.nodes.pop()
|
||||||
)));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ExactSizeIterator for GltfTreeIterator<'a> {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.nodes.len()
|
||||||
}
|
}
|
||||||
let mut nodes_to_sort = nodes.into_iter().collect::<Vec<_>>();
|
|
||||||
nodes_to_sort.sort_by_key(|(i, _)| *i);
|
|
||||||
Ok(nodes_to_sort
|
|
||||||
.into_iter()
|
|
||||||
.map(|(_, resolved)| resolved)
|
|
||||||
.collect())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ImageOrPath {
|
enum ImageOrPath {
|
||||||
@ -2003,7 +2080,7 @@ fn material_needs_tangents(material: &Material) -> bool {
|
|||||||
mod test {
|
mod test {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::{Gltf, GltfAssetLabel, GltfNode};
|
use crate::{Gltf, GltfAssetLabel, GltfNode, GltfSkin};
|
||||||
use bevy_app::App;
|
use bevy_app::App;
|
||||||
use bevy_asset::{
|
use bevy_asset::{
|
||||||
io::{
|
io::{
|
||||||
@ -2015,6 +2092,7 @@ mod test {
|
|||||||
use bevy_core::TaskPoolPlugin;
|
use bevy_core::TaskPoolPlugin;
|
||||||
use bevy_ecs::world::World;
|
use bevy_ecs::world::World;
|
||||||
use bevy_log::LogPlugin;
|
use bevy_log::LogPlugin;
|
||||||
|
use bevy_render::mesh::{skinning::SkinnedMeshInverseBindposes, MeshPlugin};
|
||||||
use bevy_scene::ScenePlugin;
|
use bevy_scene::ScenePlugin;
|
||||||
|
|
||||||
fn test_app(dir: Dir) -> App {
|
fn test_app(dir: Dir) -> App {
|
||||||
@ -2029,6 +2107,7 @@ mod test {
|
|||||||
TaskPoolPlugin::default(),
|
TaskPoolPlugin::default(),
|
||||||
AssetPlugin::default(),
|
AssetPlugin::default(),
|
||||||
ScenePlugin,
|
ScenePlugin,
|
||||||
|
MeshPlugin,
|
||||||
crate::GltfPlugin::default(),
|
crate::GltfPlugin::default(),
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -2063,10 +2142,10 @@ mod test {
|
|||||||
app.update();
|
app.update();
|
||||||
run_app_until(&mut app, |_world| {
|
run_app_until(&mut app, |_world| {
|
||||||
let load_state = asset_server.get_load_state(handle_id).unwrap();
|
let load_state = asset_server.get_load_state(handle_id).unwrap();
|
||||||
if load_state == LoadState::Loaded {
|
match load_state {
|
||||||
Some(())
|
LoadState::Loaded => Some(()),
|
||||||
} else {
|
LoadState::Failed(err) => panic!("{err}"),
|
||||||
None
|
_ => None,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
app
|
app
|
||||||
@ -2347,4 +2426,83 @@ mod test {
|
|||||||
let load_state = asset_server.get_load_state(handle_id).unwrap();
|
let load_state = asset_server.get_load_state(handle_id).unwrap();
|
||||||
assert!(matches!(load_state, LoadState::Failed(_)));
|
assert!(matches!(load_state, LoadState::Failed(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn skin_node() {
|
||||||
|
let gltf_path = "test.gltf";
|
||||||
|
let app = load_gltf_into_app(
|
||||||
|
gltf_path,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"asset": {
|
||||||
|
"version": "2.0"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "skinned",
|
||||||
|
"skin": 0,
|
||||||
|
"children": [1, 2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joint1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joint2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"skins": [
|
||||||
|
{
|
||||||
|
"inverseBindMatrices": 0,
|
||||||
|
"joints": [1, 2]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers": [
|
||||||
|
{
|
||||||
|
"uri" : "data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8=",
|
||||||
|
"byteLength" : 128
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews": [
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteLength": 128
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors": [
|
||||||
|
{
|
||||||
|
"bufferView" : 0,
|
||||||
|
"componentType" : 5126,
|
||||||
|
"count" : 2,
|
||||||
|
"type" : "MAT4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scene": 0,
|
||||||
|
"scenes": [{ "nodes": [0] }]
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let asset_server = app.world().resource::<AssetServer>();
|
||||||
|
let handle = asset_server.load(gltf_path);
|
||||||
|
let gltf_root_assets = app.world().resource::<Assets<Gltf>>();
|
||||||
|
let gltf_node_assets = app.world().resource::<Assets<GltfNode>>();
|
||||||
|
let gltf_skin_assets = app.world().resource::<Assets<GltfSkin>>();
|
||||||
|
let gltf_inverse_bind_matrices = app
|
||||||
|
.world()
|
||||||
|
.resource::<Assets<SkinnedMeshInverseBindposes>>();
|
||||||
|
let gltf_root = gltf_root_assets.get(&handle).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gltf_root.skins.len(), 1);
|
||||||
|
assert_eq!(gltf_root.nodes.len(), 3);
|
||||||
|
|
||||||
|
let skin = gltf_skin_assets.get(&gltf_root.skins[0]).unwrap();
|
||||||
|
assert_eq!(skin.joints.len(), 2);
|
||||||
|
assert_eq!(skin.joints[0], gltf_root.nodes[1]);
|
||||||
|
assert_eq!(skin.joints[1], gltf_root.nodes[2]);
|
||||||
|
assert!(gltf_inverse_bind_matrices.contains(&skin.inverse_bind_matrices));
|
||||||
|
|
||||||
|
let skinned_node = gltf_node_assets.get(&gltf_root.nodes[0]).unwrap();
|
||||||
|
assert_eq!(skinned_node.name, "skinned");
|
||||||
|
assert_eq!(skinned_node.children.len(), 2);
|
||||||
|
assert_eq!(skinned_node.skin.as_ref(), Some(&gltf_root.skins[0]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user