diff --git a/studio/src/anim/ik.rs b/studio/src/anim/ik.rs index d17c7c4..9908160 100644 --- a/studio/src/anim/ik.rs +++ b/studio/src/anim/ik.rs @@ -1,6 +1,11 @@ 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; @@ -32,6 +37,12 @@ struct IkBoneChain { len_lower: f32, } +#[derive(Component)] +pub struct IkPoleTarget; + +#[derive(Component)] +pub struct CopyXformTarget(pub Entity); + #[derive(Default)] struct IkBoneChainBuilder { root: Option, @@ -108,12 +119,18 @@ fn setup_ik_rig( let mut arm_chain_l = 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) { let Ok(name) = q_names.get(ent) else { continue; }; match name.as_str() { + // Spine entities + "DEF-spine" => spine = Some(ent), + "DEF-spine.001" => spine1 = Some(ent), // Left Leg "DEF-thigh.L" => leg_chain_l.set_root(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::>::run(on_ik_gizmo_hover), + On::>::run(on_ik_gizmo_unhover), + )); + }); + }); + } + } + for (color, bone_chain) in [ (design_system::Color::RED, leg_chain_l), (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 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| { builder.spawn(( bone_chain, + Name::new(format!("IK_{end_name}")), NotShadowCaster, + NoSelectionOutline, RenderLayers::layer(IK_RIG_RENDER_LAYER), MaterialMeshBundle { transform: end_xform.reparented_to(root_xform), @@ -166,20 +228,30 @@ fn setup_ik_rig( }), ..default() }, + PickableBundle::default(), + Selectable, + On::>::run(on_ik_gizmo_hover), + On::>::run(on_ik_gizmo_unhover), )); builder - .spawn(SpatialBundle { - transform: mid_xform.reparented_to(root_xform), - ..default() - }) + .spawn(( + Selectable, + IkPoleTarget, + 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(( + Name::new(format!("IK_{mid_name}")), NotShadowCaster, + NoSelectionOutline, RenderLayers::layer(IK_RIG_RENDER_LAYER), MaterialMeshBundle { transform, @@ -194,6 +266,9 @@ fn setup_ik_rig( }), ..default() }, + PickableBundle::default(), + On::>::run(on_ik_gizmo_hover), + On::>::run(on_ik_gizmo_unhover), )); }); }); @@ -202,6 +277,24 @@ fn setup_ik_rig( Ok(()) } +fn on_ik_gizmo_hover( + ev: Listener>, + mut ra_mats: ResMut>, + q_target: Query<&Handle>, +) { + let mat = ra_mats.get_mut(q_target.get(ev.target).unwrap()).unwrap(); + mat.color *= 1.5; +} + +fn on_ik_gizmo_unhover( + ev: Listener>, + mut ra_mats: ResMut>, + q_target: Query<&Handle>, +) { + let mat = ra_mats.get_mut(q_target.get(ev.target).unwrap()).unwrap(); + mat.color *= 2. / 3.; +} + #[cfg(feature = "wasm")] mod wasm { use bevy::prelude::*; diff --git a/studio/src/design_system.rs b/studio/src/design_system.rs index 0f95db4..f8142ff 100644 --- a/studio/src/design_system.rs +++ b/studio/src/design_system.rs @@ -12,4 +12,8 @@ impl Color { // original: rgb( 13, 110, 253) // brighter: rgb( 47, 131, 255) 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); } diff --git a/studio/src/interaction/selection.rs b/studio/src/interaction/selection.rs index 3c42ae9..3e07935 100644 --- a/studio/src/interaction/selection.rs +++ b/studio/src/interaction/selection.rs @@ -61,6 +61,9 @@ pub struct Selected; #[derive(Resource, Deref, DerefMut, Debug)] pub struct Selection(Entity); +#[derive(Component)] +pub struct NoSelectionOutline; + fn sync_editor_selected( mut cmd: Commands, q_new_selected: Query, Without)>, @@ -188,6 +191,7 @@ fn draw_selection_outline( ( With>, Without, + Without, // FIXME: It shouldn't be necessary to filter out SkinnedMesh here Without, ),