Add live hitbox viewer
This commit is contained in:
@@ -154,6 +154,7 @@ type FighterBakeProcessor = LoadTransformAndSave<
|
||||
>;
|
||||
|
||||
pub struct BakedAnimationPlugin;
|
||||
pub struct BakedAnimationVisualPlugin;
|
||||
|
||||
pub struct BakedAnimationAssetProcessingPlugin;
|
||||
|
||||
@@ -172,17 +173,27 @@ impl Plugin for BakedAnimationAssetProcessingPlugin {
|
||||
|
||||
impl Plugin for BakedAnimationPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(BakedAnimationAssetProcessingPlugin)
|
||||
.add_systems(
|
||||
bevy_ggrs::prelude::GgrsSchedule,
|
||||
super::runtime::update_fighter_bone_matrices
|
||||
.in_set(crate::schedule::GameplaySet::Animation),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
super::visual_binding::apply_baked_fighter_visual_pose,
|
||||
)
|
||||
.add_observer(super::visual_binding::bind_fighter_visual_bones);
|
||||
app.add_plugins((
|
||||
BakedAnimationAssetProcessingPlugin,
|
||||
BakedAnimationVisualPlugin,
|
||||
))
|
||||
.add_systems(
|
||||
bevy_ggrs::prelude::GgrsSchedule,
|
||||
super::runtime::update_fighter_bone_matrices
|
||||
.in_set(super::BakedAnimationSamplingSet)
|
||||
.in_set(crate::schedule::GameplaySet::Animation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for BakedAnimationVisualPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
super::visual_binding::apply_baked_fighter_visual_pose
|
||||
.after(super::BakedAnimationSamplingSet),
|
||||
)
|
||||
.add_observer(super::visual_binding::bind_fighter_visual_bones);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,17 @@ mod runtime;
|
||||
mod visual_binding;
|
||||
|
||||
pub use crate::math::fixed_transform::FixedMat4;
|
||||
pub use asset_baking::{BakedAnimationAssetProcessingPlugin, BakedAnimationPlugin};
|
||||
pub use asset_baking::{
|
||||
BakedAnimationAssetProcessingPlugin, BakedAnimationPlugin, BakedAnimationVisualPlugin,
|
||||
};
|
||||
pub use runtime::{
|
||||
BakedAnimationClip, BakedFighterAnimations, FighterBoneIndexMap, FighterBoneMatrices,
|
||||
resample_fighter_bone_matrices, update_fighter_bone_matrices,
|
||||
};
|
||||
|
||||
#[derive(bevy::prelude::SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct BakedAnimationSamplingSet;
|
||||
|
||||
pub const BAKED_ANIMATION_FPS: u32 = 60;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -55,6 +55,29 @@ impl BakedFighterAnimations {
|
||||
pub fn frame_count(&self, kind: AnimKind) -> Option<u32> {
|
||||
self.clip(kind).map(|clip| clip.frames.len() as u32)
|
||||
}
|
||||
/// Samples one bone in model space without depending on the runtime's
|
||||
/// previous-frame buffers. Tooling uses this when the user scrubs between
|
||||
/// unrelated frames.
|
||||
pub fn sample_bone_model_transform(
|
||||
&self,
|
||||
frame: &FighterAnimationFrame,
|
||||
bone: &str,
|
||||
model_scale: FGi32,
|
||||
) -> Option<FixedMat4> {
|
||||
let local = self.sample_pose(frame)?;
|
||||
let index = self.bone_names.iter().position(|name| name == bone)?;
|
||||
let mut model = vec![None; local.len()];
|
||||
resolve_model_transform(index, &self.parents, local, &mut model).ok()?;
|
||||
let scale = FixedMat4 {
|
||||
cols: [
|
||||
[model_scale, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
|
||||
[FGi32::ZERO, model_scale, FGi32::ZERO, FGi32::ZERO],
|
||||
[FGi32::ZERO, FGi32::ZERO, model_scale, FGi32::ZERO],
|
||||
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
|
||||
],
|
||||
};
|
||||
Some(scale.mul(model[index]?))
|
||||
}
|
||||
pub(super) fn clip(&self, kind: AnimKind) -> Option<&BakedAnimationClip> {
|
||||
self.clip_indices
|
||||
.get(&kind)
|
||||
@@ -78,6 +101,26 @@ pub struct FighterBoneMatrices {
|
||||
pub struct FighterBoneIndexMap(pub HashMap<String, usize>);
|
||||
|
||||
impl FighterBoneMatrices {
|
||||
/// Samples a baked pose for a preview or other non-gameplay caller.
|
||||
pub fn sample_baked_pose(
|
||||
&mut self,
|
||||
baked: &BakedFighterAnimations,
|
||||
frame: &FighterAnimationFrame,
|
||||
model_scale: FGi32,
|
||||
) -> bool {
|
||||
let Some(pose) = baked.sample_pose(frame) else {
|
||||
return false;
|
||||
};
|
||||
self.set_pose(
|
||||
frame.kind,
|
||||
&baked.bone_names,
|
||||
&baked.parents,
|
||||
pose,
|
||||
model_scale,
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn buffer_capacities(&self) -> [usize; 5] {
|
||||
[
|
||||
|
||||
@@ -92,9 +92,12 @@ pub(super) fn bind_fighter_visual_bones(
|
||||
pub(super) fn apply_baked_fighter_visual_pose(
|
||||
mut nodes: Query<(&mut Transform, &BakedFighterBone)>,
|
||||
fighters: Query<&FighterBoneMatrices>,
|
||||
timing: Res<GgrsFrameTiming>,
|
||||
timing: Option<Res<GgrsFrameTiming>>,
|
||||
) {
|
||||
let overstep_fraction = timing.overstep_fraction();
|
||||
let overstep_fraction = timing
|
||||
.as_deref()
|
||||
.map(GgrsFrameTiming::overstep_fraction)
|
||||
.unwrap_or(0.0);
|
||||
for (mut transform, bone) in &mut nodes {
|
||||
let Ok(matrices) = fighters.get(bone.fighter) else {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user