mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
Attach IK rig to selected skeleton
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user