Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 60 additions & 74 deletions examples/animation/morph_targets.rs
Original file line number Diff line number Diff line change
@@ -1,112 +1,98 @@
//! Controls morph targets in a loaded scene.
//! Play an animation with morph targets.
//!
//! Illustrates:
//!
//! - How to access and modify individual morph target weights.
//! See the `update_weights` system for details.
//! - How to read morph target names in `name_morphs`.
//! - How to play morph target animations in `setup_animations`.
//! Also illustrates how to read morph target names in `name_morphs`.

use bevy::prelude::*;
use bevy::{prelude::*, scene::SceneInstanceReady};
use std::f32::consts::PI;

const GLTF_PATH: &str = "models/animated/MorphStressTest.gltf";

fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "morph targets".to_string(),
..default()
}),
..default()
}))
.add_plugins(DefaultPlugins)
.insert_resource(AmbientLight {
brightness: 150.0,
..default()
})
.add_systems(Startup, setup)
.add_systems(Update, (name_morphs, setup_animations))
.add_systems(Update, name_morphs)
.run();
}

#[derive(Resource)]
struct MorphData {
the_wave: Handle<AnimationClip>,
mesh: Handle<Mesh>,
#[derive(Component)]
struct AnimationToPlay {
graph_handle: Handle<AnimationGraph>,
index: AnimationNodeIndex,
}

fn setup(asset_server: Res<AssetServer>, mut commands: Commands) {
commands.insert_resource(MorphData {
the_wave: asset_server
.load(GltfAssetLabel::Animation(2).from_asset("models/animated/MorphStressTest.gltf")),
mesh: asset_server.load(
GltfAssetLabel::Primitive {
mesh: 0,
primitive: 0,
}
.from_asset("models/animated/MorphStressTest.gltf"),
),
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/animated/MorphStressTest.gltf"),
)));
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {
let (graph, index) = AnimationGraph::from_clip(
asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)),
);

commands
.spawn((
AnimationToPlay {
graph_handle: graphs.add(graph),
index,
},
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH))),
))
.observe(play_animation_when_ready);

commands.spawn((
DirectionalLight::default(),
Transform::from_rotation(Quat::from_rotation_z(PI / 2.0)),
));

commands.spawn((
Camera3d::default(),
Transform::from_xyz(3.0, 2.1, 10.2).looking_at(Vec3::ZERO, Vec3::Y),
));
}

/// Plays an [`AnimationClip`] from the loaded [`Gltf`] on the [`AnimationPlayer`] created by the spawned scene.
fn setup_animations(
mut has_setup: Local<bool>,
fn play_animation_when_ready(
trigger: On<SceneInstanceReady>,
mut commands: Commands,
mut players: Query<(Entity, &Name, &mut AnimationPlayer)>,
morph_data: Res<MorphData>,
mut graphs: ResMut<Assets<AnimationGraph>>,
children: Query<&Children>,
animations_to_play: Query<&AnimationToPlay>,
mut players: Query<&mut AnimationPlayer>,
) {
if *has_setup {
return;
}
for (entity, name, mut player) in &mut players {
// The name of the entity in the GLTF scene containing the AnimationPlayer for our morph targets is "Main"
if name.as_str() != "Main" {
continue;
}
if let Ok(animation_to_play) = animations_to_play.get(trigger.target()) {
for child in children.iter_descendants(trigger.target()) {
if let Ok(mut player) = players.get_mut(child) {
player.play(animation_to_play.index).repeat();

let (graph, animation) = AnimationGraph::from_clip(morph_data.the_wave.clone());
commands
.entity(entity)
.insert(AnimationGraphHandle(graphs.add(graph)));

player.play(animation).repeat();
*has_setup = true;
commands
.entity(child)
.insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));
}
}
}
}

/// You can get the target names in their corresponding [`Mesh`].
/// They are in the order of the weights.
/// Whenever a mesh asset is loaded, print the name of the asset and the names
/// of its morph targets.
fn name_morphs(
mut has_printed: Local<bool>,
morph_data: Res<MorphData>,
asset_server: Res<AssetServer>,
mut events: EventReader<AssetEvent<Mesh>>,
meshes: Res<Assets<Mesh>>,
) {
if *has_printed {
return;
}

let Some(mesh) = meshes.get(&morph_data.mesh) else {
return;
};
let Some(names) = mesh.morph_target_names() else {
return;
};
for event in events.read() {
if let AssetEvent::<Mesh>::Added { id } = event
&& let Some(path) = asset_server.get_path(*id)
&& let Some(mesh) = meshes.get(*id)
&& let Some(names) = mesh.morph_target_names()
{
info!("Morph target names for {path:?}:");

info!("Target names:");
for name in names {
info!(" {name}");
for name in names {
info!(" {name}");
}
}
}
*has_printed = true;
}
36 changes: 18 additions & 18 deletions examples/stress_tests/many_foxes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
light::CascadeShadowConfigBuilder,
prelude::*,
scene::SceneInstanceReady,
window::{PresentMode, WindowResolution},
winit::{UpdateMode, WinitSettings},
};
Expand Down Expand Up @@ -68,7 +69,6 @@ fn main() {
.add_systems(
Update,
(
setup_scene_once_loaded,
keyboard_animation_control,
update_fox_rings.after(keyboard_animation_control),
),
Expand Down Expand Up @@ -173,12 +173,14 @@ fn setup(
let (x, z) = (radius * c, radius * s);

commands.entity(ring_parent).with_children(|builder| {
builder.spawn((
SceneRoot(fox_handle.clone()),
Transform::from_xyz(x, 0.0, z)
.with_scale(Vec3::splat(0.01))
.with_rotation(base_rotation * Quat::from_rotation_y(-fox_angle)),
));
builder
.spawn((
SceneRoot(fox_handle.clone()),
Transform::from_xyz(x, 0.0, z)
.with_scale(Vec3::splat(0.01))
.with_rotation(base_rotation * Quat::from_rotation_y(-fox_angle)),
))
.observe(setup_scene_once_loaded);
});
}

Expand Down Expand Up @@ -230,25 +232,23 @@ fn setup(

// Once the scene is loaded, start the animation
fn setup_scene_once_loaded(
trigger: On<SceneInstanceReady>,
animations: Res<Animations>,
foxes: Res<Foxes>,
mut commands: Commands,
mut player: Query<(Entity, &mut AnimationPlayer)>,
mut done: Local<bool>,
children: Query<&Children>,
mut players: Query<&mut AnimationPlayer>,
) {
if !*done && player.iter().len() == foxes.count {
for (entity, mut player) in &mut player {
commands
.entity(entity)
.insert(AnimationGraphHandle(animations.graph.clone()))
.insert(AnimationTransitions::new());

for child in children.iter_descendants(trigger.target()) {
if let Ok(mut player) = players.get_mut(child) {
let playing_animation = player.play(animations.node_indices[0]).repeat();
if !foxes.sync {
playing_animation.seek_to(entity.index() as f32 / 10.0);
playing_animation.seek_to(trigger.target().index() as f32 / 10.0);
}
commands
.entity(child)
.insert(AnimationGraphHandle(animations.graph.clone()));
}
*done = true;
}
}

Expand Down
Loading