Skip to content

Commit 17f40c9

Browse files
committed
load animations from gltf
1 parent e30d600 commit 17f40c9

File tree

2 files changed

+138
-6
lines changed

2 files changed

+138
-6
lines changed

crates/bevy_gltf/src/lib.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
2+
use bevy_math::{Vec3, Vec4};
13
use bevy_utils::HashMap;
24

35
mod loader;
@@ -6,7 +8,7 @@ pub use loader::*;
68
use bevy_app::prelude::*;
79
use bevy_asset::{AddAsset, Handle};
810
use bevy_pbr::StandardMaterial;
9-
use bevy_reflect::TypeUuid;
11+
use bevy_reflect::{Reflect, TypeUuid};
1012
use bevy_render::mesh::Mesh;
1113
use bevy_scene::Scene;
1214

@@ -20,7 +22,9 @@ impl Plugin for GltfPlugin {
2022
.add_asset::<Gltf>()
2123
.add_asset::<GltfNode>()
2224
.add_asset::<GltfPrimitive>()
23-
.add_asset::<GltfMesh>();
25+
.add_asset::<GltfMesh>()
26+
.add_asset::<GltfAnimation>()
27+
.register_type::<GltfAnimatedNode>();
2428
}
2529
}
2630

@@ -37,6 +41,8 @@ pub struct Gltf {
3741
pub nodes: Vec<Handle<GltfNode>>,
3842
pub named_nodes: HashMap<String, Handle<GltfNode>>,
3943
pub default_scene: Option<Handle<Scene>>,
44+
pub animations: Vec<Handle<GltfAnimation>>,
45+
pub named_animations: HashMap<String, Handle<GltfAnimation>>,
4046
}
4147

4248
/// A glTF node with all of its child nodes, its [`GltfMesh`] and
@@ -63,3 +69,46 @@ pub struct GltfPrimitive {
6369
pub mesh: Handle<Mesh>,
6470
pub material: Option<Handle<StandardMaterial>>,
6571
}
72+
73+
#[derive(Clone, Debug)]
74+
pub enum GltfAnimationInterpolation {
75+
Linear,
76+
Step,
77+
CubicSpline,
78+
}
79+
80+
#[derive(Clone, Debug)]
81+
pub struct GltfNodeAnimation {
82+
pub keyframe_timestamps: Vec<f32>,
83+
pub keyframes: GltfNodeAnimationKeyframes,
84+
pub interpolation: GltfAnimationInterpolation,
85+
}
86+
87+
#[derive(Default, Clone, TypeUuid, Debug)]
88+
#[uuid = "d81b7179-0448-4eb0-89fe-c067222725bf"]
89+
pub struct GltfAnimation {
90+
pub node_animations: HashMap<usize, Vec<GltfNodeAnimation>>,
91+
}
92+
93+
#[derive(Clone, Debug)]
94+
pub enum GltfNodeAnimationKeyframes {
95+
Rotation(Vec<Vec4>),
96+
Translation(Vec<Vec3>),
97+
Scale(Vec<Vec3>),
98+
}
99+
100+
impl Default for GltfNodeAnimation {
101+
fn default() -> Self {
102+
Self {
103+
keyframe_timestamps: Default::default(),
104+
keyframes: GltfNodeAnimationKeyframes::Translation(Default::default()),
105+
interpolation: GltfAnimationInterpolation::Linear,
106+
}
107+
}
108+
}
109+
110+
#[derive(Component, Debug, Clone, Reflect, Default)]
111+
#[reflect(Component)]
112+
pub struct GltfAnimatedNode {
113+
pub index: usize,
114+
}

crates/bevy_gltf/src/loader.rs

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use bevy_asset::{
55
use bevy_core::Name;
66
use bevy_ecs::world::World;
77
use bevy_log::warn;
8-
use bevy_math::{Mat4, Vec3};
8+
use bevy_math::{Mat4, Vec3, Vec4};
99
use bevy_pbr::{
1010
AlphaMode, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight, PointLightBundle,
1111
StandardMaterial,
@@ -37,7 +37,10 @@ use gltf::{
3737
use std::{collections::VecDeque, path::Path};
3838
use thiserror::Error;
3939

40-
use crate::{Gltf, GltfNode};
40+
use crate::{
41+
Gltf, GltfAnimatedNode, GltfAnimation, GltfAnimationInterpolation, GltfNode, GltfNodeAnimation,
42+
GltfNodeAnimationKeyframes,
43+
};
4144

4245
/// An error that occurs when loading a glTF file.
4346
#[derive(Error, Debug)]
@@ -109,6 +112,76 @@ async fn load_gltf<'a, 'b>(
109112
}
110113
}
111114

115+
let mut animations = vec![];
116+
let mut named_animations = HashMap::default();
117+
let mut animated_nodes = HashSet::default();
118+
for animation in gltf.animations() {
119+
let mut gltf_animation = GltfAnimation::default();
120+
for channel in animation.channels() {
121+
let interpolation = match channel.sampler().interpolation() {
122+
gltf::animation::Interpolation::Linear => GltfAnimationInterpolation::Linear,
123+
gltf::animation::Interpolation::Step => GltfAnimationInterpolation::Step,
124+
gltf::animation::Interpolation::CubicSpline => {
125+
GltfAnimationInterpolation::CubicSpline
126+
}
127+
};
128+
let node = channel.target().node();
129+
let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
130+
let keyframe_timestamps: Vec<f32> = if let Some(inputs) = reader.read_inputs() {
131+
match inputs {
132+
gltf::accessor::Iter::Standard(times) => times.collect(),
133+
gltf::accessor::Iter::Sparse(_) => {
134+
warn!("sparse accessor not supported for animation sampler input");
135+
continue;
136+
}
137+
}
138+
} else {
139+
panic!("animations without a sampler input are not supported");
140+
};
141+
142+
let keyframes = if let Some(outputs) = reader.read_outputs() {
143+
match outputs {
144+
gltf::animation::util::ReadOutputs::Translations(tr) => {
145+
GltfNodeAnimationKeyframes::Translation(tr.map(Vec3::from).collect())
146+
}
147+
gltf::animation::util::ReadOutputs::Rotations(rots) => {
148+
GltfNodeAnimationKeyframes::Rotation(
149+
rots.into_f32().map(Vec4::from).collect(),
150+
)
151+
}
152+
gltf::animation::util::ReadOutputs::Scales(scale) => {
153+
GltfNodeAnimationKeyframes::Scale(scale.map(Vec3::from).collect())
154+
}
155+
gltf::animation::util::ReadOutputs::MorphTargetWeights(_) => {
156+
warn!("Morph animation property not yet supported");
157+
continue;
158+
}
159+
}
160+
} else {
161+
panic!("animations without a sampler output are not supported");
162+
};
163+
164+
gltf_animation
165+
.node_animations
166+
.entry(node.index())
167+
.or_default()
168+
.push(GltfNodeAnimation {
169+
keyframe_timestamps,
170+
keyframes,
171+
interpolation,
172+
});
173+
animated_nodes.insert(node.index());
174+
}
175+
let handle = load_context.set_labeled_asset(
176+
&format!("Animation{}", animation.index()),
177+
LoadedAsset::new(gltf_animation),
178+
);
179+
if let Some(name) = animation.name() {
180+
named_animations.insert(name.to_string(), handle.clone());
181+
}
182+
animations.push(handle);
183+
}
184+
112185
let mut meshes = vec![];
113186
let mut named_meshes = HashMap::default();
114187
for mesh in gltf.meshes() {
@@ -292,7 +365,8 @@ async fn load_gltf<'a, 'b>(
292365
.insert_bundle((Transform::identity(), GlobalTransform::identity()))
293366
.with_children(|parent| {
294367
for node in scene.nodes() {
295-
let result = load_node(&node, parent, load_context, &buffer_data);
368+
let result =
369+
load_node(&node, parent, load_context, &buffer_data, &animated_nodes);
296370
if result.is_err() {
297371
err = Some(result);
298372
return;
@@ -324,6 +398,8 @@ async fn load_gltf<'a, 'b>(
324398
named_materials,
325399
nodes,
326400
named_nodes,
401+
animations,
402+
named_animations,
327403
}));
328404

329405
Ok(())
@@ -459,6 +535,7 @@ fn load_node(
459535
world_builder: &mut WorldChildBuilder,
460536
load_context: &mut LoadContext,
461537
buffer_data: &[Vec<u8>],
538+
animated_nodes: &HashSet<usize>,
462539
) -> Result<(), GltfError> {
463540
let transform = gltf_node.transform();
464541
let mut gltf_error = None;
@@ -467,6 +544,12 @@ fn load_node(
467544
GlobalTransform::identity(),
468545
));
469546

547+
if animated_nodes.contains(&gltf_node.index()) {
548+
node.insert(GltfAnimatedNode {
549+
index: gltf_node.index(),
550+
});
551+
}
552+
470553
if let Some(name) = gltf_node.name() {
471554
node.insert(Name::new(name.to_string()));
472555
}
@@ -601,7 +684,7 @@ fn load_node(
601684

602685
// append other nodes
603686
for child in gltf_node.children() {
604-
if let Err(err) = load_node(&child, parent, load_context, buffer_data) {
687+
if let Err(err) = load_node(&child, parent, load_context, buffer_data, animated_nodes) {
605688
gltf_error = Some(err);
606689
return;
607690
}

0 commit comments

Comments
 (0)