Skip to content

Commit f50514a

Browse files
committed
Convert bevy_animation to not use entity paths
1 parent 54cba7a commit f50514a

File tree

2 files changed

+133
-126
lines changed

2 files changed

+133
-126
lines changed

crates/bevy_animation/src/lib.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ pub struct VariableCurve {
5555
#[derive(Clone, TypeUuid, Debug, Default)]
5656
#[uuid = "d81b7179-0448-4eb0-89fe-c067222725bf"]
5757
pub struct AnimationClip {
58-
curves: HashMap<EntityPath, Vec<VariableCurve>>,
58+
curves: HashMap<Entity, Vec<VariableCurve>>,
5959
duration: f32,
6060
}
6161

6262
impl AnimationClip {
6363
#[inline]
6464
/// Hashmap of the [`VariableCurve`]s per [`EntityPath`].
65-
pub fn curves(&self) -> &HashMap<EntityPath, Vec<VariableCurve>> {
65+
pub fn curves(&self) -> &HashMap<Entity, Vec<VariableCurve>> {
6666
&self.curves
6767
}
6868

@@ -73,7 +73,7 @@ impl AnimationClip {
7373
}
7474

7575
/// Add a [`VariableCurve`] to an [`EntityPath`].
76-
pub fn add_curve_to_path(&mut self, path: EntityPath, curve: VariableCurve) {
76+
pub fn add_curve_to_path(&mut self, path: Entity, curve: VariableCurve) {
7777
// Update the duration of the animation by this curve duration if it's longer
7878
self.duration = self
7979
.duration
@@ -172,7 +172,6 @@ pub fn animation_player(
172172
animations: Res<Assets<AnimationClip>>,
173173
mut animation_players: Query<(Entity, &mut AnimationPlayer)>,
174174
mut transforms: Query<&mut Transform>,
175-
lookup: NameLookup,
176175
) {
177176
for (entity, mut player) in &mut animation_players {
178177
if let Some(animation_clip) = animations.get(&player.animation_clip) {
@@ -193,13 +192,7 @@ pub fn animation_player(
193192
}
194193
for (path, curves) in &animation_clip.curves {
195194
// PERF: finding the target entity can be optimised
196-
let current_entity = match lookup.lookup(entity, path) {
197-
Ok(e) => e,
198-
Err(e) => {
199-
warn!("Entity for path {path:?} was not found");
200-
continue;
201-
}
202-
};
195+
let current_entity = *path;
203196
if let Ok(mut transform) = transforms.get_mut(current_entity) {
204197
for curve in curves {
205198
// Some curves have only one keyframe used to set a transform

crates/bevy_gltf/src/loader.rs

Lines changed: 129 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anyhow::Result;
2+
use bevy_animation::AnimationPlayer;
23
use bevy_asset::{
34
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
45
};
@@ -136,100 +137,6 @@ async fn load_gltf<'a, 'b>(
136137
}
137138
}
138139

139-
#[cfg(feature = "bevy_animation")]
140-
let paths = {
141-
let mut paths = HashMap::<usize, (usize, Vec<Name>)>::new();
142-
for scene in gltf.scenes() {
143-
for node in scene.nodes() {
144-
let root_index = node.index();
145-
paths_recur(node, &[], &mut paths, root_index);
146-
}
147-
}
148-
paths
149-
};
150-
151-
#[cfg(feature = "bevy_animation")]
152-
let (animations, named_animations, animation_roots) = {
153-
let mut animations = vec![];
154-
let mut named_animations = HashMap::default();
155-
let mut animation_roots = HashSet::default();
156-
for animation in gltf.animations() {
157-
let mut animation_clip = bevy_animation::AnimationClip::default();
158-
for channel in animation.channels() {
159-
match channel.sampler().interpolation() {
160-
gltf::animation::Interpolation::Linear => (),
161-
other => warn!(
162-
"Animation interpolation {:?} is not supported, will use linear",
163-
other
164-
),
165-
};
166-
let node = channel.target().node();
167-
let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
168-
let keyframe_timestamps: Vec<f32> = if let Some(inputs) = reader.read_inputs() {
169-
match inputs {
170-
gltf::accessor::Iter::Standard(times) => times.collect(),
171-
gltf::accessor::Iter::Sparse(_) => {
172-
warn!("Sparse accessor not supported for animation sampler input");
173-
continue;
174-
}
175-
}
176-
} else {
177-
warn!("Animations without a sampler input are not supported");
178-
return Err(GltfError::MissingAnimationSampler(animation.index()));
179-
};
180-
181-
let keyframes = if let Some(outputs) = reader.read_outputs() {
182-
match outputs {
183-
gltf::animation::util::ReadOutputs::Translations(tr) => {
184-
bevy_animation::Keyframes::Translation(tr.map(Vec3::from).collect())
185-
}
186-
gltf::animation::util::ReadOutputs::Rotations(rots) => {
187-
bevy_animation::Keyframes::Rotation(
188-
rots.into_f32().map(bevy_math::Quat::from_array).collect(),
189-
)
190-
}
191-
gltf::animation::util::ReadOutputs::Scales(scale) => {
192-
bevy_animation::Keyframes::Scale(scale.map(Vec3::from).collect())
193-
}
194-
gltf::animation::util::ReadOutputs::MorphTargetWeights(_) => {
195-
warn!("Morph animation property not yet supported");
196-
continue;
197-
}
198-
}
199-
} else {
200-
warn!("Animations without a sampler output are not supported");
201-
return Err(GltfError::MissingAnimationSampler(animation.index()));
202-
};
203-
204-
if let Some((root_index, path)) = paths.get(&node.index()) {
205-
animation_roots.insert(root_index);
206-
animation_clip.add_curve_to_path(
207-
bevy_core::EntityPath {
208-
parts: path.clone(),
209-
},
210-
bevy_animation::VariableCurve {
211-
keyframe_timestamps,
212-
keyframes,
213-
},
214-
);
215-
} else {
216-
warn!(
217-
"Animation ignored for node {}: part of its hierarchy is missing a name",
218-
node.index()
219-
);
220-
}
221-
}
222-
let handle = load_context.set_labeled_asset(
223-
&format!("Animation{}", animation.index()),
224-
LoadedAsset::new(animation_clip),
225-
);
226-
if let Some(name) = animation.name() {
227-
named_animations.insert(name.to_string(), handle.clone());
228-
}
229-
animations.push(handle);
230-
}
231-
(animations, named_animations, animation_roots)
232-
};
233140

234141
let mut meshes = vec![];
235142
let mut named_meshes = HashMap::default();
@@ -458,49 +365,39 @@ async fn load_gltf<'a, 'b>(
458365
let mut scenes = vec![];
459366
let mut named_scenes = HashMap::default();
460367
let mut active_camera_found = false;
368+
let mut node_index_to_entity_map = HashMap::new();
461369
for scene in gltf.scenes() {
462370
let mut err = None;
463371
let mut world = World::default();
464-
let mut node_index_to_entity_map = HashMap::new();
465372
let mut entity_to_skin_index_map = HashMap::new();
466373

467374
world
468375
.spawn()
469376
.insert_bundle(SpatialBundle::VISIBLE_IDENTITY)
470377
.with_children(|parent| {
471378
for node in scene.nodes() {
472-
let result = load_node(
379+
match load_node(
473380
&node,
474381
parent,
475382
load_context,
476383
&buffer_data,
477384
&mut node_index_to_entity_map,
478385
&mut entity_to_skin_index_map,
479386
&mut active_camera_found,
480-
);
481-
if result.is_err() {
482-
err = Some(result);
483-
return;
387+
Some(&gltf),
388+
) {
389+
Err(e) => {
390+
err = Some(Err(e) as Result<(), _>);
391+
return;
392+
}
393+
Ok(e) => (),
484394
}
485395
}
486396
});
487397
if let Some(Err(err)) = err {
488398
return Err(err);
489399
}
490400

491-
#[cfg(feature = "bevy_animation")]
492-
{
493-
// for each node root in a scene, check if it's the root of an animation
494-
// if it is, add the AnimationPlayer component
495-
for node in scene.nodes() {
496-
if animation_roots.contains(&node.index()) {
497-
world
498-
.entity_mut(*node_index_to_entity_map.get(&node.index()).unwrap())
499-
.insert(bevy_animation::AnimationPlayer::default());
500-
}
501-
}
502-
}
503-
504401
for (&entity, &skin_index) in &entity_to_skin_index_map {
505402
let mut entity = world.entity_mut(entity);
506403
let skin = gltf.skins().nth(skin_index).unwrap();
@@ -524,6 +421,106 @@ async fn load_gltf<'a, 'b>(
524421
scenes.push(scene_handle);
525422
}
526423

424+
// #[cfg(feature = "bevy_animation")]
425+
// let paths = {
426+
// let mut paths = HashMap::<usize, (usize, Vec<Name>)>::new();
427+
// for scene in gltf.scenes() {
428+
// for node in scene.nodes() {
429+
// let root_index = node.index();
430+
// paths_recur(node, &[], &mut paths, root_index);
431+
// }
432+
// }
433+
// paths
434+
// };
435+
436+
#[cfg(feature = "bevy_animation")]
437+
let (animations, named_animations, animation_roots) = {
438+
let mut animations = vec![];
439+
let mut named_animations = HashMap::default();
440+
for animation in gltf.animations() {
441+
let mut animation_clip = bevy_animation::AnimationClip::default();
442+
for channel in animation.channels() {
443+
match channel.sampler().interpolation() {
444+
gltf::animation::Interpolation::Linear => (),
445+
other => warn!(
446+
"Animation interpolation {:?} is not supported, will use linear",
447+
other
448+
),
449+
};
450+
let node = channel.target().node();
451+
let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
452+
let keyframe_timestamps: Vec<f32> = if let Some(inputs) = reader.read_inputs() {
453+
match inputs {
454+
gltf::accessor::Iter::Standard(times) => times.collect(),
455+
gltf::accessor::Iter::Sparse(_) => {
456+
warn!("Sparse accessor not supported for animation sampler input");
457+
continue;
458+
}
459+
}
460+
} else {
461+
warn!("Animations without a sampler input are not supported");
462+
return Err(GltfError::MissingAnimationSampler(animation.index()));
463+
};
464+
465+
let keyframes = if let Some(outputs) = reader.read_outputs() {
466+
match outputs {
467+
gltf::animation::util::ReadOutputs::Translations(tr) => {
468+
bevy_animation::Keyframes::Translation(tr.map(Vec3::from).collect())
469+
}
470+
gltf::animation::util::ReadOutputs::Rotations(rots) => {
471+
bevy_animation::Keyframes::Rotation(
472+
rots.into_f32().map(bevy_math::Quat::from_array).collect(),
473+
)
474+
}
475+
gltf::animation::util::ReadOutputs::Scales(scale) => {
476+
bevy_animation::Keyframes::Scale(scale.map(Vec3::from).collect())
477+
}
478+
gltf::animation::util::ReadOutputs::MorphTargetWeights(_) => {
479+
warn!("Morph animation property not yet supported");
480+
continue;
481+
}
482+
}
483+
} else {
484+
warn!("Animations without a sampler output are not supported");
485+
return Err(GltfError::MissingAnimationSampler(animation.index()));
486+
};
487+
488+
let node_entity = *node_index_to_entity_map.get(&node.index()).unwrap();
489+
// animation_nodes.insert(node.index());
490+
// TODO: find root & attach AnimationPlayer?
491+
animation_clip.add_curve_to_path(
492+
node_entity,
493+
bevy_animation::VariableCurve {
494+
keyframe_timestamps,
495+
keyframes,
496+
},
497+
);
498+
}
499+
let handle = load_context.set_labeled_asset(
500+
&format!("Animation{}", animation.index()),
501+
LoadedAsset::new(animation_clip),
502+
);
503+
if let Some(name) = animation.name() {
504+
named_animations.insert(name.to_string(), handle.clone());
505+
}
506+
animations.push(handle);
507+
}
508+
(animations, named_animations, ())
509+
};
510+
511+
// #[cfg(feature = "bevy_animation")]
512+
// {
513+
// // for each node root in a scene, check if it's the root of an animation
514+
// // if it is, add the AnimationPlayer component
515+
// for node in gltf.scenes().flat_map(|s| s.nodes()) {
516+
// if animation_roots.contains(&node.index()) {
517+
// world
518+
// .entity_mut(*node_index_to_entity_map.get(&node.index()).unwrap())
519+
// .insert(bevy_animation::AnimationPlayer::default());
520+
// }
521+
// }
522+
// }
523+
527524
load_context.set_default_asset(LoadedAsset::new(Gltf {
528525
default_scene: gltf
529526
.default_scene()
@@ -546,6 +543,10 @@ async fn load_gltf<'a, 'b>(
546543
Ok(())
547544
}
548545

546+
fn is_animation_root(gltf: &gltf::Gltf, node: &gltf::Node) -> bool {
547+
gltf.animations().flat_map(|a| a.channels()).any(|c| node.children().any(|n| c.target().node().index() == n.index()))
548+
}
549+
549550
fn node_name(node: &Node) -> Name {
550551
let name = node
551552
.name()
@@ -694,6 +695,7 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
694695
}
695696

696697
/// Loads a glTF node.
698+
#[allow(clippy::too_many_arguments)]
697699
fn load_node(
698700
gltf_node: &gltf::Node,
699701
world_builder: &mut WorldChildBuilder,
@@ -702,7 +704,8 @@ fn load_node(
702704
node_index_to_entity_map: &mut HashMap<usize, Entity>,
703705
entity_to_skin_index_map: &mut HashMap<Entity, usize>,
704706
active_camera_found: &mut bool,
705-
) -> Result<(), GltfError> {
707+
gltf: Option<&gltf::Gltf>,
708+
) -> Result<Entity, GltfError> {
706709
let transform = gltf_node.transform();
707710
let mut gltf_error = None;
708711
let mut node = world_builder.spawn_bundle(SpatialBundle::from(Transform::from_matrix(
@@ -763,6 +766,16 @@ fn load_node(
763766
*active_camera_found = true;
764767
}
765768

769+
#[cfg(feature = "bevy_animation")]
770+
if let Some(gltf) = gltf {
771+
if is_animation_root(gltf, gltf_node) {
772+
node.insert(bevy_animation::AnimationPlayer::default());
773+
} else {
774+
println!("Root, but not animation");
775+
println!("{:?}", gltf_node);
776+
}
777+
}
778+
766779
// Map node index to entity
767780
node_index_to_entity_map.insert(gltf_node.index(), node.id());
768781

@@ -898,6 +911,7 @@ fn load_node(
898911
node_index_to_entity_map,
899912
entity_to_skin_index_map,
900913
active_camera_found,
914+
None,
901915
) {
902916
gltf_error = Some(err);
903917
return;
@@ -907,7 +921,7 @@ fn load_node(
907921
if let Some(err) = gltf_error {
908922
Err(err)
909923
} else {
910-
Ok(())
924+
Ok(node.id())
911925
}
912926
}
913927

0 commit comments

Comments
 (0)