156 lines
4.7 KiB
Rust
156 lines
4.7 KiB
Rust
use bevy::prelude::*;
|
|
|
|
use crate::{
|
|
fighter::{
|
|
FighterFacingDirection, FighterSurfaceState, FighterTranslation,
|
|
baked_animation::FighterBoneMatrices,
|
|
}, math::{int::FGi32, vec::FGVec2},
|
|
};
|
|
|
|
/// Environment collision box: the diamond used to resolve a fighter against
|
|
/// the stage. Purely geometry — the collision response lives in
|
|
/// [`super::collision`].
|
|
#[derive(Component, Debug, Clone, Copy, Hash, Default)]
|
|
#[require(FighterPreviousECB)]
|
|
pub struct FighterECB {
|
|
pub top: FGVec2,
|
|
pub bottom: FGVec2,
|
|
pub left: FGVec2,
|
|
pub right: FGVec2,
|
|
}
|
|
|
|
#[derive(Component, Debug, Clone)]
|
|
pub struct FighterECBBoneIndices {
|
|
pub indices: Vec<usize>,
|
|
}
|
|
|
|
#[derive(Component, Debug, Clone, Copy, Default, Hash)]
|
|
pub struct FighterPreviousECB {
|
|
pub top: FGVec2,
|
|
pub bottom: FGVec2,
|
|
pub left: FGVec2,
|
|
pub right: FGVec2,
|
|
}
|
|
|
|
impl From<FighterECB> for FighterPreviousECB {
|
|
fn from(ecb: FighterECB) -> Self {
|
|
Self {
|
|
top: ecb.top,
|
|
bottom: ecb.bottom,
|
|
left: ecb.left,
|
|
right: ecb.right,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn snapshot_fighter_ecb(fighters: Query<(&FighterECB, &mut FighterPreviousECB)>) {
|
|
for (ecb, mut previous_ecb) in fighters {
|
|
*previous_ecb = (*ecb).into();
|
|
}
|
|
}
|
|
|
|
impl FighterECB {
|
|
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
|
[self.top, self.right, self.bottom, self.left]
|
|
.map(|point| trf.transform_point(Vec3::new(point.x.to_num(), point.y.to_num(), 0.0)))
|
|
}
|
|
|
|
pub fn get_bottom_point(&self) -> FGVec2 {
|
|
self.bottom
|
|
}
|
|
}
|
|
|
|
impl FighterPreviousECB {
|
|
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
|
[self.top, self.right, self.bottom, self.left]
|
|
.map(|point| trf.transform_point(Vec3::new(point.x.to_num(), point.y.to_num(), 0.0)))
|
|
}
|
|
|
|
pub fn get_bottom_point(&self) -> FGVec2 {
|
|
self.bottom
|
|
}
|
|
}
|
|
|
|
pub fn update_ecb(
|
|
query: Query<(
|
|
&mut FighterECB,
|
|
&FighterSurfaceState,
|
|
&FighterECBBoneIndices,
|
|
&FighterBoneMatrices,
|
|
&FighterTranslation,
|
|
&FighterFacingDirection,
|
|
)>,
|
|
) {
|
|
for (mut ecb, surface_state, bone_indices, bone_matrices, translation, facing_dir) in query {
|
|
let trf = translation.get_3d_transform(facing_dir);
|
|
|
|
let transformed_ecb_bones = bone_indices.indices.iter().filter_map(|idx| {
|
|
let Some((_, bone_matrix)) = bone_matrices.get_by_idx(*idx) else {
|
|
return None;
|
|
};
|
|
|
|
let pt = trf.transform_point(bone_matrix.get_translation());
|
|
|
|
Some(FGVec2::new(pt.x, pt.y) - translation.0)
|
|
}).collect::<Vec<_>>();
|
|
|
|
let max_x = transformed_ecb_bones.iter().max_by(|a, b| a.x.cmp(&b.x)).map(|final_val| final_val.x).unwrap_or(FGi32::lit("0.5"));
|
|
let min_x = transformed_ecb_bones.iter().min_by(|a, b| a.x.cmp(&b.x)).map(|final_val| final_val.x).unwrap_or(FGi32::lit("-0.5"));
|
|
let max_y = transformed_ecb_bones.iter().max_by(|a, b| a.y.cmp(&b.y)).map(|final_val| final_val.y).unwrap_or(FGi32::lit("0.5"));
|
|
let min_y = transformed_ecb_bones.iter().min_by(|a, b| a.y.cmp(&b.y)).map(|final_val| final_val.y).unwrap_or(FGi32::lit("-0.5"));
|
|
|
|
let midpoint_height = (max_y + min_y) / FGi32::lit("2.0");
|
|
let top = FGVec2::new(FGi32::ZERO, max_y);
|
|
let bottom = FGVec2::new(FGi32::ZERO, min_y);
|
|
let left = FGVec2::new(min_x, midpoint_height);
|
|
let right = FGVec2::new(max_x, midpoint_height);
|
|
|
|
let ecb_lock_frames_remaining = if let FighterSurfaceState::Airborne { ecb_lock_frames_remaining, .. } = surface_state {
|
|
*ecb_lock_frames_remaining
|
|
} else {
|
|
0
|
|
};
|
|
|
|
ecb.top = top;
|
|
|
|
if ecb_lock_frames_remaining == 0 {
|
|
ecb.bottom = bottom;
|
|
}
|
|
|
|
ecb.left = left;
|
|
ecb.right = right;
|
|
|
|
if surface_state.is_grounded() {
|
|
ecb.bottom = FGVec2::ZERO;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn decrement_ecb_lockout(surface_states: Query<&mut FighterSurfaceState>) {
|
|
for mut state in surface_states {
|
|
state.tick_ecb_lock();
|
|
state.tick_ignored_polygons();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn previous_ecb_copies_current_dimensions() {
|
|
let current = FighterECB {
|
|
bottom: FGVec2::lit("0.0", "-0.5"),
|
|
top: FGVec2::lit("0.0", "0.5"),
|
|
left: FGVec2::lit("-0.5", "0.0"),
|
|
right: FGVec2::lit("0.5", "0.0"),
|
|
};
|
|
let previous = FighterPreviousECB::from(current);
|
|
|
|
assert_eq!(previous.top, current.top);
|
|
assert_eq!(previous.bottom, current.bottom);
|
|
assert_eq!(previous.left, current.left);
|
|
assert_eq!(previous.right, current.right);
|
|
}
|
|
}
|