Attach IK rig to selected skeleton

This commit is contained in:
Danny McGee
2024-02-28 18:49:16 -05:00
parent 96f1f8ec39
commit 3f887d3f3e
5 changed files with 282 additions and 20 deletions
+6 -3
View File
@@ -90,6 +90,11 @@ export class AppElement extends LitElement {
}
}
attachIkRig(): void {
if (this.selection)
studio.attachIkRig(this.selection.id);
}
toggleHelp(): void {
this.showHelp = !this.showHelp;
}
@@ -123,9 +128,7 @@ export class AppElement extends LitElement {
<sts-button
icon="share-nodes"
?disabled=${this.selection?.type !== SceneElement.Skeleton}
@click=${() => {
console.log("Attach IK rig");
}}
@click=${this.attachIkRig}
>
Attach IK Rig
</sts-button>
+227
View File
@@ -0,0 +1,227 @@
use bevy::{pbr::NotShadowCaster, prelude::*, render::view::RenderLayers};
use crate::{design_system, interaction::gizmos::GizmoMaterial, math::geo};
pub const IK_RIG_RENDER_LAYER: u8 = 1;
pub struct IkPlugin;
impl Plugin for IkPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, setup_ik_rig.map(bevy::utils::error));
#[cfg(feature = "wasm")]
app.add_systems(Update, wasm::insert_ik_component);
}
}
#[derive(Component)]
pub struct IkRig;
#[derive(Component)]
pub struct IkRigCamera;
#[derive(Component, Clone, Copy)]
struct IkBoneChain {
root: Entity,
twist_1: Option<Entity>,
mid: Entity,
twist_2: Option<Entity>,
end: Entity,
len_upper: f32,
len_lower: f32,
}
#[derive(Default)]
struct IkBoneChainBuilder {
root: Option<Entity>,
twist_1: Option<Entity>,
mid: Option<Entity>,
twist_2: Option<Entity>,
end: Option<Entity>,
}
impl IkBoneChain {
fn build() -> IkBoneChainBuilder {
IkBoneChainBuilder::default()
}
}
impl IkBoneChainBuilder {
fn set_root(&mut self, ent: Entity) {
self.root = Some(ent);
}
fn set_twist_1(&mut self, ent: Entity) {
self.twist_1 = Some(ent);
}
fn set_mid(&mut self, ent: Entity) {
self.mid = Some(ent);
}
fn set_twist_2(&mut self, ent: Entity) {
self.twist_2 = Some(ent);
}
fn set_end(&mut self, ent: Entity) {
self.end = Some(ent);
}
fn try_finish(self, q_xforms: &Query<&GlobalTransform>) -> Result<IkBoneChain, String> {
let root = self.root.ok_or_else(|| "Root bone missing!".to_string())?;
let mid = self.mid.ok_or_else(|| "Mid bone missing!".to_string())?;
let end = self.end.ok_or_else(|| "End bone missing!".to_string())?;
let root_xform = q_xforms.get(root).map_err(|err| format!("{err}"))?;
let mid_xform = q_xforms.get(mid).map_err(|err| format!("{err}"))?;
let end_xform = q_xforms.get(end).map_err(|err| format!("{err}"))?;
let len_upper = Vec3::distance(root_xform.translation(), mid_xform.translation());
let len_lower = Vec3::distance(mid_xform.translation(), end_xform.translation());
Ok(IkBoneChain {
root,
twist_1: self.twist_1,
mid,
twist_2: self.twist_2,
end,
len_upper,
len_lower,
})
}
}
fn setup_ik_rig(
mut cmd: Commands,
mut ra_meshes: ResMut<Assets<Mesh>>,
mut ra_mats: ResMut<Assets<GizmoMaterial>>,
q_target: Query<Entity, Added<IkRig>>,
q_children: Query<&Children>,
q_names: Query<&Name>,
q_world_xforms: Query<&GlobalTransform>,
) -> Result<(), String> {
let Ok(target) = q_target.get_single() else {
return Ok(());
};
let root_xform = q_world_xforms.get(target).map_err(|err| format!("{err}"))?;
let mut leg_chain_l = IkBoneChain::build();
let mut leg_chain_r = IkBoneChain::build();
let mut arm_chain_l = IkBoneChain::build();
let mut arm_chain_r = IkBoneChain::build();
for ent in q_children.iter_descendants(target) {
let Ok(name) = q_names.get(ent) else {
continue;
};
match name.as_str() {
// Left Leg
"DEF-thigh.L" => leg_chain_l.set_root(ent),
"DEF-thigh.L.001" => leg_chain_l.set_twist_1(ent),
"DEF-shin.L" => leg_chain_l.set_mid(ent),
"DEF-shin.L.001" => leg_chain_l.set_twist_2(ent),
"DEF-foot.L" => leg_chain_l.set_end(ent),
// Right Leg
"DEF-thigh.R" => leg_chain_r.set_root(ent),
"DEF-thigh.R.001" => leg_chain_r.set_twist_1(ent),
"DEF-shin.R" => leg_chain_r.set_mid(ent),
"DEF-shin.R.001" => leg_chain_r.set_twist_2(ent),
"DEF-foot.R" => leg_chain_r.set_end(ent),
// Left Arm
"DEF-upper_arm.L" => arm_chain_l.set_root(ent),
"DEF-upper_arm.L.001" => arm_chain_l.set_twist_1(ent),
"DEF-forearm.L" => arm_chain_l.set_mid(ent),
"DEF-forearm.L.001" => arm_chain_l.set_twist_2(ent),
"DEF-hand.L" => arm_chain_l.set_end(ent),
// Right Arm
"DEF-upper_arm.R" => arm_chain_r.set_root(ent),
"DEF-upper_arm.R.001" => arm_chain_r.set_twist_1(ent),
"DEF-forearm.R" => arm_chain_r.set_mid(ent),
"DEF-forearm.R.001" => arm_chain_r.set_twist_2(ent),
"DEF-hand.R" => arm_chain_r.set_end(ent),
_ => {}
}
}
for (color, bone_chain) in [
(design_system::Color::RED, leg_chain_l),
(design_system::Color::BLUE, leg_chain_r),
(design_system::Color::RED, arm_chain_l),
(design_system::Color::BLUE, arm_chain_r),
] {
let bone_chain = bone_chain.try_finish(&q_world_xforms)?;
let end_xform = q_world_xforms.get(bone_chain.end).unwrap();
let mid_xform = q_world_xforms.get(bone_chain.mid).unwrap();
cmd.entity(target).with_children(|builder| {
builder.spawn((
bone_chain,
NotShadowCaster,
RenderLayers::layer(IK_RIG_RENDER_LAYER),
MaterialMeshBundle {
transform: end_xform.reparented_to(root_xform),
mesh: ra_meshes.add(Mesh::from(shape::Cube { size: 0.1 })),
material: ra_mats.add(GizmoMaterial {
color: color.with_a(0.4),
blend_mode: AlphaMode::Blend,
}),
..default()
},
));
builder
.spawn(SpatialBundle {
transform: mid_xform.reparented_to(root_xform),
..default()
})
.with_children(|builder| {
let transform = Transform::from_xyz(0., 0., -0.1).with_rotation(
Quat::from_axis_angle(Vec3::NEG_X, std::f32::consts::FRAC_PI_2),
);
builder.spawn((
NotShadowCaster,
RenderLayers::layer(IK_RIG_RENDER_LAYER),
MaterialMeshBundle {
transform,
mesh: ra_meshes.add(Mesh::from(geo::Cone {
radius: 0.05,
length: 0.15,
resolution: 12,
})),
material: ra_mats.add(GizmoMaterial {
color: color.with_a(0.4),
blend_mode: AlphaMode::Blend,
}),
..default()
},
));
});
});
}
Ok(())
}
#[cfg(feature = "wasm")]
mod wasm {
use bevy::prelude::*;
use wasm_bindgen::prelude::*;
use crate::wasm::{FromStr, StaticBuffer, WasmMessageBuffer};
use super::IkRig;
static ATTACH_IK_RIG: WasmMessageBuffer<Entity> = WasmMessageBuffer::new();
#[wasm_bindgen(js_name = attachIkRig)]
pub fn attach_ik_rig(entity: &str) {
let entity = Entity::from_str(entity).unwrap();
ATTACH_IK_RIG.write_message(entity);
}
pub(super) fn insert_ik_component(mut cmd: Commands) {
if let Some(entity) = ATTACH_IK_RIG.take_message() {
cmd.entity(entity).insert(IkRig);
}
}
}
+6 -1
View File
@@ -10,6 +10,7 @@ use crate::{hierarchy, scene::SceneElement};
pub use retargeting::*;
mod camera;
pub mod ik;
mod retargeting;
pub struct AnimPlugin;
@@ -31,7 +32,11 @@ impl Plugin for AnimPlugin {
app.add_systems(Update, process_skinned_meshes);
app.insert_resource(GlobalSeekTime(0.));
app.add_plugins((retargeting::RetargetingPlugin, camera::CameraAnimPlugin));
app.add_plugins((
retargeting::RetargetingPlugin,
camera::CameraAnimPlugin,
ik::IkPlugin,
));
#[cfg(feature = "wasm")]
app.add_systems(Update, wasm::update_global_seek_time);
+40 -12
View File
@@ -5,7 +5,10 @@ use bevy::{
};
use space_editor::space_editor_ui::NotShowCamera;
use crate::MainCamera;
use crate::{
anim::ik::{IkRigCamera, IK_RIG_RENDER_LAYER},
MainCamera,
};
use super::GIZMO_RENDER_LAYER;
@@ -19,9 +22,13 @@ pub(super) fn spawn_gizmo_camera(
let ent = q_main_camera.single();
cmd.entity(ent).with_children(|cmd| {
// TODO: This doesn't really belong here, but the render pipeline seems
// to break if we don't spawn these cameras in the correct order.
// We should probably add a dedicated module/plugin for setting up
// the editor cameras.
cmd.spawn((
Name::new("Gizmo Camera"),
GizmoCamera,
Name::new("IK Rig Camera"),
IkRigCamera,
Camera3dBundle {
camera_3d: Camera3d {
clear_color: ClearColorConfig::None,
@@ -34,6 +41,26 @@ pub(super) fn spawn_gizmo_camera(
},
..default()
},
RenderLayers::layer(IK_RIG_RENDER_LAYER),
Fxaa::default(),
NotShowCamera,
));
cmd.spawn((
Name::new("Gizmo Camera"),
GizmoCamera,
Camera3dBundle {
camera_3d: Camera3d {
clear_color: ClearColorConfig::None,
..default()
},
camera: Camera {
hdr: true,
order: 2,
..default()
},
..default()
},
RenderLayers::layer(GIZMO_RENDER_LAYER),
Fxaa::default(),
NotShowCamera,
@@ -41,17 +68,18 @@ pub(super) fn spawn_gizmo_camera(
});
}
pub(super) fn sync_gizmo_and_main_cameras(
q_main_camera: Query<&Camera, (With<MainCamera>, Without<GizmoCamera>)>,
mut q_gizmo_camera: Query<&mut Camera, (With<GizmoCamera>, Without<MainCamera>)>,
pub(super) fn sync_main_and_aux_cameras(
q_main_camera: Query<(Entity, &Camera), With<MainCamera>>,
mut q_other_cameras: Query<&mut Camera, Without<MainCamera>>,
q_children: Query<&Children>,
) {
let Ok(main_cam) = q_main_camera.get_single() else {
let Ok((main_cam_ent, main_cam)) = q_main_camera.get_single() else {
return;
};
let Ok(mut gizmo_cam) = q_gizmo_camera.get_single_mut() else {
return;
};
gizmo_cam.viewport = main_cam.viewport.clone();
for child in q_children.iter_descendants(main_cam_ent) {
if let Ok(mut cam) = q_other_cameras.get_mut(child) {
cam.viewport = main_cam.viewport.clone();
}
}
}
+3 -4
View File
@@ -6,12 +6,11 @@ use self::{
camera::{spawn_gizmo_camera, GizmoCamera},
combo::spawn_combo_translation_rotation_gizmo,
common::*,
material::GizmoMaterial,
resources::{init_resources, GizmoMats, GizmoMeshes},
rotation::{spawn_rotation_gizmo, update_ss_ring_rotation},
translation::spawn_translation_gizmo,
};
pub use common::TransformGizmo;
pub use self::{common::TransformGizmo, material::GizmoMaterial};
use super::{Selection, TransformMode, TransformType};
@@ -23,7 +22,7 @@ mod resources;
mod rotation;
mod translation;
const GIZMO_RENDER_LAYER: u8 = 1;
pub const GIZMO_RENDER_LAYER: u8 = 2;
/// (sin|cos)(pi / 4)
const CIRC_45: f32 = 0.70710677;
@@ -67,7 +66,7 @@ impl Plugin for GizmoPlugin {
);
//sync camera viewports
app.add_systems(Update, camera::sync_gizmo_and_main_cameras);
app.add_systems(Update, camera::sync_main_and_aux_cameras);
app.add_systems(
PostUpdate,