mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
Make IK gizmos interactable
This commit is contained in:
+98
-5
@@ -1,6 +1,11 @@
|
|||||||
use bevy::{pbr::NotShadowCaster, prelude::*, render::view::RenderLayers};
|
use bevy::{pbr::NotShadowCaster, prelude::*, render::view::RenderLayers};
|
||||||
|
use bevy_mod_picking::prelude::*;
|
||||||
|
|
||||||
use crate::{design_system, interaction::gizmos::GizmoMaterial, math::geo};
|
use crate::{
|
||||||
|
design_system,
|
||||||
|
interaction::{gizmos::GizmoMaterial, NoSelectionOutline, Selectable},
|
||||||
|
math::geo,
|
||||||
|
};
|
||||||
|
|
||||||
pub const IK_RIG_RENDER_LAYER: u8 = 1;
|
pub const IK_RIG_RENDER_LAYER: u8 = 1;
|
||||||
|
|
||||||
@@ -32,6 +37,12 @@ struct IkBoneChain {
|
|||||||
len_lower: f32,
|
len_lower: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct IkPoleTarget;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct CopyXformTarget(pub Entity);
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct IkBoneChainBuilder {
|
struct IkBoneChainBuilder {
|
||||||
root: Option<Entity>,
|
root: Option<Entity>,
|
||||||
@@ -108,12 +119,18 @@ fn setup_ik_rig(
|
|||||||
let mut arm_chain_l = IkBoneChain::build();
|
let mut arm_chain_l = IkBoneChain::build();
|
||||||
let mut arm_chain_r = IkBoneChain::build();
|
let mut arm_chain_r = IkBoneChain::build();
|
||||||
|
|
||||||
|
let mut spine = None;
|
||||||
|
let mut spine1 = None;
|
||||||
|
|
||||||
for ent in q_children.iter_descendants(target) {
|
for ent in q_children.iter_descendants(target) {
|
||||||
let Ok(name) = q_names.get(ent) else {
|
let Ok(name) = q_names.get(ent) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
|
// Spine entities
|
||||||
|
"DEF-spine" => spine = Some(ent),
|
||||||
|
"DEF-spine.001" => spine1 = Some(ent),
|
||||||
// Left Leg
|
// Left Leg
|
||||||
"DEF-thigh.L" => leg_chain_l.set_root(ent),
|
"DEF-thigh.L" => leg_chain_l.set_root(ent),
|
||||||
"DEF-thigh.L.001" => leg_chain_l.set_twist_1(ent),
|
"DEF-thigh.L.001" => leg_chain_l.set_twist_1(ent),
|
||||||
@@ -142,6 +159,46 @@ fn setup_ik_rig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(spine) = spine {
|
||||||
|
if let Some(spine1) = spine1 {
|
||||||
|
let spine_name = q_names.get(spine).unwrap();
|
||||||
|
let spine_xform = q_world_xforms.get(spine).unwrap();
|
||||||
|
let spine1_xform = q_world_xforms.get(spine1).unwrap();
|
||||||
|
|
||||||
|
cmd.entity(target).with_children(|builder| {
|
||||||
|
builder
|
||||||
|
.spawn((
|
||||||
|
Selectable,
|
||||||
|
CopyXformTarget(spine),
|
||||||
|
SpatialBundle {
|
||||||
|
transform: spine_xform.reparented_to(root_xform),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_children(|builder| {
|
||||||
|
builder.spawn((
|
||||||
|
Name::new(format!("TGT_{spine_name}")),
|
||||||
|
NotShadowCaster,
|
||||||
|
NoSelectionOutline,
|
||||||
|
RenderLayers::layer(IK_RIG_RENDER_LAYER),
|
||||||
|
MaterialMeshBundle {
|
||||||
|
transform: spine1_xform.reparented_to(spine_xform),
|
||||||
|
mesh: ra_meshes.add(Mesh::from(shape::Cube { size: 0.25 })),
|
||||||
|
material: ra_mats.add(GizmoMaterial {
|
||||||
|
color: design_system::Color::YELLOW.with_a(0.125),
|
||||||
|
blend_mode: AlphaMode::Blend,
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
PickableBundle::default(),
|
||||||
|
On::<Pointer<Over>>::run(on_ik_gizmo_hover),
|
||||||
|
On::<Pointer<Out>>::run(on_ik_gizmo_unhover),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (color, bone_chain) in [
|
for (color, bone_chain) in [
|
||||||
(design_system::Color::RED, leg_chain_l),
|
(design_system::Color::RED, leg_chain_l),
|
||||||
(design_system::Color::BLUE, leg_chain_r),
|
(design_system::Color::BLUE, leg_chain_r),
|
||||||
@@ -152,10 +209,15 @@ fn setup_ik_rig(
|
|||||||
let end_xform = q_world_xforms.get(bone_chain.end).unwrap();
|
let end_xform = q_world_xforms.get(bone_chain.end).unwrap();
|
||||||
let mid_xform = q_world_xforms.get(bone_chain.mid).unwrap();
|
let mid_xform = q_world_xforms.get(bone_chain.mid).unwrap();
|
||||||
|
|
||||||
|
let end_name = q_names.get(bone_chain.end).unwrap();
|
||||||
|
let mid_name = q_names.get(bone_chain.mid).unwrap();
|
||||||
|
|
||||||
cmd.entity(target).with_children(|builder| {
|
cmd.entity(target).with_children(|builder| {
|
||||||
builder.spawn((
|
builder.spawn((
|
||||||
bone_chain,
|
bone_chain,
|
||||||
|
Name::new(format!("IK_{end_name}")),
|
||||||
NotShadowCaster,
|
NotShadowCaster,
|
||||||
|
NoSelectionOutline,
|
||||||
RenderLayers::layer(IK_RIG_RENDER_LAYER),
|
RenderLayers::layer(IK_RIG_RENDER_LAYER),
|
||||||
MaterialMeshBundle {
|
MaterialMeshBundle {
|
||||||
transform: end_xform.reparented_to(root_xform),
|
transform: end_xform.reparented_to(root_xform),
|
||||||
@@ -166,20 +228,30 @@ fn setup_ik_rig(
|
|||||||
}),
|
}),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
PickableBundle::default(),
|
||||||
|
Selectable,
|
||||||
|
On::<Pointer<Over>>::run(on_ik_gizmo_hover),
|
||||||
|
On::<Pointer<Out>>::run(on_ik_gizmo_unhover),
|
||||||
));
|
));
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.spawn(SpatialBundle {
|
.spawn((
|
||||||
transform: mid_xform.reparented_to(root_xform),
|
Selectable,
|
||||||
..default()
|
IkPoleTarget,
|
||||||
})
|
SpatialBundle {
|
||||||
|
transform: mid_xform.reparented_to(root_xform),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
.with_children(|builder| {
|
.with_children(|builder| {
|
||||||
let transform = Transform::from_xyz(0., 0., -0.1).with_rotation(
|
let transform = Transform::from_xyz(0., 0., -0.1).with_rotation(
|
||||||
Quat::from_axis_angle(Vec3::NEG_X, std::f32::consts::FRAC_PI_2),
|
Quat::from_axis_angle(Vec3::NEG_X, std::f32::consts::FRAC_PI_2),
|
||||||
);
|
);
|
||||||
|
|
||||||
builder.spawn((
|
builder.spawn((
|
||||||
|
Name::new(format!("IK_{mid_name}")),
|
||||||
NotShadowCaster,
|
NotShadowCaster,
|
||||||
|
NoSelectionOutline,
|
||||||
RenderLayers::layer(IK_RIG_RENDER_LAYER),
|
RenderLayers::layer(IK_RIG_RENDER_LAYER),
|
||||||
MaterialMeshBundle {
|
MaterialMeshBundle {
|
||||||
transform,
|
transform,
|
||||||
@@ -194,6 +266,9 @@ fn setup_ik_rig(
|
|||||||
}),
|
}),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
PickableBundle::default(),
|
||||||
|
On::<Pointer<Over>>::run(on_ik_gizmo_hover),
|
||||||
|
On::<Pointer<Out>>::run(on_ik_gizmo_unhover),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -202,6 +277,24 @@ fn setup_ik_rig(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_ik_gizmo_hover(
|
||||||
|
ev: Listener<Pointer<Over>>,
|
||||||
|
mut ra_mats: ResMut<Assets<GizmoMaterial>>,
|
||||||
|
q_target: Query<&Handle<GizmoMaterial>>,
|
||||||
|
) {
|
||||||
|
let mat = ra_mats.get_mut(q_target.get(ev.target).unwrap()).unwrap();
|
||||||
|
mat.color *= 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_ik_gizmo_unhover(
|
||||||
|
ev: Listener<Pointer<Out>>,
|
||||||
|
mut ra_mats: ResMut<Assets<GizmoMaterial>>,
|
||||||
|
q_target: Query<&Handle<GizmoMaterial>>,
|
||||||
|
) {
|
||||||
|
let mat = ra_mats.get_mut(q_target.get(ev.target).unwrap()).unwrap();
|
||||||
|
mat.color *= 2. / 3.;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
mod wasm {
|
mod wasm {
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|||||||
@@ -12,4 +12,8 @@ impl Color {
|
|||||||
// original: rgb( 13, 110, 253)
|
// original: rgb( 13, 110, 253)
|
||||||
// brighter: rgb( 47, 131, 255)
|
// brighter: rgb( 47, 131, 255)
|
||||||
pub const BLUE: BevyColor = BevyColor::rgb(0.184314, 0.513724, 1.000000);
|
pub const BLUE: BevyColor = BevyColor::rgb(0.184314, 0.513724, 1.000000);
|
||||||
|
|
||||||
|
// "Lighten" blend of GREEN + RED
|
||||||
|
// rgb(255, 187, 100)
|
||||||
|
pub const YELLOW: BevyColor = BevyColor::rgb(1.000000, 0.733333, 0.392157);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ pub struct Selected;
|
|||||||
#[derive(Resource, Deref, DerefMut, Debug)]
|
#[derive(Resource, Deref, DerefMut, Debug)]
|
||||||
pub struct Selection(Entity);
|
pub struct Selection(Entity);
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct NoSelectionOutline;
|
||||||
|
|
||||||
fn sync_editor_selected(
|
fn sync_editor_selected(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
q_new_selected: Query<Entity, (Added<Selected>, Without<space_editor::prelude::Selected>)>,
|
q_new_selected: Query<Entity, (Added<Selected>, Without<space_editor::prelude::Selected>)>,
|
||||||
@@ -188,6 +191,7 @@ fn draw_selection_outline(
|
|||||||
(
|
(
|
||||||
With<Handle<Mesh>>,
|
With<Handle<Mesh>>,
|
||||||
Without<OutlineVolume>,
|
Without<OutlineVolume>,
|
||||||
|
Without<NoSelectionOutline>,
|
||||||
// FIXME: It shouldn't be necessary to filter out SkinnedMesh here
|
// FIXME: It shouldn't be necessary to filter out SkinnedMesh here
|
||||||
Without<SkinnedMesh>,
|
Without<SkinnedMesh>,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user