Skip to content

Commit

Permalink
Greatly overhauled how code works
Browse files Browse the repository at this point in the history
  • Loading branch information
TheButlah committed Nov 23, 2023
1 parent 758fd91 commit b492fb1
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 76 deletions.
7 changes: 4 additions & 3 deletions skills/blendshapes/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# `IK & FK`
# Blendshapes and Morph Targets

Inverse Kinematics and Forward Kinematics
Demonstrates how to use morph targets to animate the blend shapes. For example
to make a gltf avatar blink.

To run the code:
```bash
cargo run -p ik
cargo run -p blendshapes
```
230 changes: 157 additions & 73 deletions skills/blendshapes/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use bevy::ecs::system::RunSystemOnce;
use bevy::pbr::DirectionalLightShadowMap;
use bevy::prelude::*;
use bevy::window::PrimaryWindow;
use bevy_egui::{EguiPlugin, EguiContext};
use bevy_inspector_egui::DefaultInspectorConfigPlugin;
use bevy_egui::{EguiContext, EguiPlugin};
use bevy_inspector_egui::bevy_inspector::hierarchy::SelectedEntities;
use color_eyre::eyre::bail;
use std::f32::consts::*;
use bevy_inspector_egui::DefaultInspectorConfigPlugin;
use color_eyre::eyre::{bail, ensure, eyre, WrapErr};
use color_eyre::Result;
use std::f32::consts::*;

const ASSET_FOLDER: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/");
fn main() -> Result<()> {
Expand All @@ -23,63 +22,39 @@ fn main() -> Result<()> {
.insert_resource(DirectionalLightShadowMap { size: 4096 })
.add_plugins(DefaultPlugins.set(AssetPlugin {
file_path: ASSET_FOLDER.to_string(),
..Default::default()
..default()
}))
.add_plugins(EguiPlugin)
.add_plugins(DefaultInspectorConfigPlugin)
.add_systems(Startup, setup)
.add_systems(Update, on_avatar_load)
.insert_resource(PendingAvatars::default())
.add_systems(Update, mark_morph_targets::<Blink>.map(log_on_err))
.add_event::<MarkMorphTargetEvent>()
.add_systems(Update, animate_light_direction)
.add_systems(Update, animate_morphable)
.add_systems(Update, animate_blink)
.add_systems(PostUpdate, inspector_ui)


.run();

Ok(())
}

#[derive(Component)]
struct MorphableTarget {

fn panic_on_err(result: Result<()>) {
result.unwrap()
}
impl MorphableTarget {
fn findTarget(target_name: &str, entity: Entity, world: &mut World) -> Result<Self> {
let tg_name_cp = target_name.to_owned();


world.run_system_once(move | children: Query<&Children>, names: Query<&Name> | -> Result<()> {
let mut target_entity = None;
for child in children.iter_descendants(entity) {
if let Ok(name) = names.get(child) {
if tg_name_cp == name.as_str() {
target_entity.replace(child);
}
}
}
let found_entity = match target_entity {
Some(e) => e,
None => bail!("Could not find the target entity {tg_name_cp}"),
};

// found_entity.

Ok(())
})?;




todo!()
fn log_on_err(result: Result<()>) {
if let Err(err) = result {
error!("{:?}", err);
}
}


fn setup(
mut commands: Commands,
assets: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
world: &mut World,
mut pending_avatars: ResMut<PendingAvatars>,
) {
commands
.spawn(SpatialBundle::default())
Expand Down Expand Up @@ -122,19 +97,149 @@ fn setup(
..default()
});

let avatar_entity = commands.spawn((
SceneBundle {
scene: assets.load("malek.gltf#Scene0"),
let scene = assets.load("malek.gltf#Scene0");
let avatar_entity = commands
.spawn(SceneBundle {
scene: scene.clone(),
transform: Transform::from_xyz(0.0, 0.0, 0.0).with_rotation(
Quat::from_euler(EulerRot::XYZ, 0.0, 180.0_f32.to_radians(), 0.0),
),
..default()
})
.id();
pending_avatars.0.insert(
scene.id(),
MarkMorphTargetEvent {
avatar: avatar_entity,
child_name: "Face".into(),
weight_name: "Blink",
},
)).id();
);
}

/// Because asset loading happens asynchronously over multiple update frames, we can't
/// immediately send events. Therefore, we store these events in this resource
/// and defer their sending until we detect that the avatar is loaded.
#[derive(Resource, Debug, Default)]
struct PendingAvatars(bevy::utils::HashMap<AssetId<Scene>, MarkMorphTargetEvent>);

MorphableTarget::findTarget("Face", avatar_entity, world);
/// Sends a [`MarkMorphTargetEvent`] when the avatar's `Scene` has loaded.
fn on_avatar_load(
mut asset_event: EventReader<AssetEvent<Scene>>,
mut mark_morph_targets_writer: EventWriter<MarkMorphTargetEvent>,
mut pending_avatars: ResMut<PendingAvatars>,
) {
for evt in asset_event.read() {
match evt {
AssetEvent::Removed { id } => {
pending_avatars.0.remove(&*id);
}
AssetEvent::LoadedWithDependencies { id } => {
let Some(pending) = pending_avatars.0.remove(id) else {
continue;
};
info!("Sending morph mark event");
mark_morph_targets_writer.send(pending);
}
_ => continue,
}
}
}

/// Events for the [`mark_morph_targets`] system.
#[derive(Debug, Event)]
struct MarkMorphTargetEvent {
pub avatar: Entity,
pub child_name: Name,
pub weight_name: &'static str,
}

/// Marks information about a morph target.
/// `C` allows giving the target a particular type, to make queries easy, as well
/// as adding any additional desired metadata.
#[derive(Debug, Component)]
struct MorphTarget<C> {
pub weight_idx: usize,
#[allow(unused)]
pub target_info: C,
}

/// A blink morph target. Used inside [`MorphTarget`].
struct Blink;

/// The particular entity to mark will be filtered for based on `input`.
/// Attaches component [`MorphTarget<C>`] to this entity.
fn mark_morph_targets<C>(
mut inputs: EventReader<MarkMorphTargetEvent>,
children: Query<&Children>,
morph_weights: Query<&MorphWeights>,
names: Query<&Name>,
meshes: Res<Assets<Mesh>>,
mut cmds: Commands,
) -> Result<()> {
for input in inputs.read() {
info!("Marking morphs");
mark_morph_target::<C>(
input,
&children,
&morph_weights,
&names,
&meshes,
&mut cmds,
)
.wrap_err_with(|| format!("Failed to mark morph target for {input:?}"))?;
}
Ok(())
}

/// The single item version of [`mark_morph_targets`], except we also return the
/// Entity that was marked.
fn mark_morph_target<C>(
input: &MarkMorphTargetEvent,
children: &Query<&Children>,
morph_weights: &Query<&MorphWeights>,
names: &Query<&Name>,
meshes: &Res<Assets<Mesh>>,
cmds: &mut Commands,
) -> Result<Entity> {
let MarkMorphTargetEvent {
avatar,
child_name,
weight_name,
} = input;
let mut num_children = 0;
let matching_child = children.iter_descendants(*avatar).find(|child| {
num_children += 1;
names.get(*child) == Ok(child_name)
});
ensure!(
num_children > 0,
"No children of avatar entity {avatar:?} could be found"
);
let Some(matching_child) = matching_child else {
bail!("No child named `{child_name}` found");
};
// TODO: Can we combine the morph weights query with the children query?
let morph_weights = morph_weights.get(matching_child).unwrap();
let first_mesh_handle = morph_weights.first_mesh().expect("expected a mesh");
let first_mesh = meshes
.get(first_mesh_handle)
.expect("expected asset referenced by morph weights to exist");
let (idx, _) = first_mesh
.morph_target_names()
.ok_or_else(|| {
eyre!("failed to get morph target names for mesh {first_mesh_handle:?}")
})?
.iter()
.enumerate()
.find(|(_idx, name)| *name == weight_name)
.expect("expected morph target with name {weight_name}");

cmds.entity(matching_child).insert(MorphTarget {
weight_idx: idx,
target_info: Blink,
});
Ok(matching_child)
}

fn animate_light_direction(
Expand All @@ -151,36 +256,15 @@ fn animate_light_direction(
}
}

fn animate_morphable(
morphable_entities_q: Query<(Entity, &MorphableTarget)>,
mut morph_weights_q: Query<&mut MorphWeights>,
children: Query<&Children>,
names: Query<&Name>,
fn animate_blink(
mut morphs: Query<(&mut MorphWeights, &MorphTarget<Blink>)>,
time: Res<Time>,
) {
for (entity, _thing) in morphable_entities_q.iter() {
let mut face = None;

for child in children.iter_descendants(entity) {
if let Ok(name) = names.get(child) {
if name.as_str() == "Face" {
face.replace(child);
}
}
}

let face = match face {
Some(e) => e,
// keep returning until the model fully loads in and we have found the face
// this is massively inefficient.
None => continue,
};

let mut morph_wheight = morph_weights_q.get_mut(face).unwrap();

for (mut weights, target) in morphs.iter_mut() {
// Accessing morph 13, that is the blink morph target
morph_wheight.weights_mut()[13] = (f32::sin(time.elapsed_seconds() * 10.0) + 1.) / 2.;
}
weights.weights_mut()[target.weight_idx] =
(f32::sin(time.elapsed_seconds() * 10.0) + 1.) / 2.;
}
}

fn inspector_ui(world: &mut World, mut selected_entities: Local<SelectedEntities>) {
Expand Down

0 comments on commit b492fb1

Please sign in to comment.