//! Resolving an integrated fighter position against the stage. use crate::fighter::ecb::FighterPreviousECB; use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterTranslation}; use crate::math::segment::FGSegment2d; use crate::math::vec::FGVec2; use crate::stage::line::{StageCollision, StageLineID, StagePolyType}; /* pub fn collide_fighter_with_scene( fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>, stage_planes: Query<&StagePoly>, mut gizmos: Gizmos, ) { for (ecb, mut velocity, mut translation) in fighters { for plane in stage_planes { let ray_from = translation.0; let ray_to = translation.0 + Vec2::new(0.0, -ecb.vertical_half); let intersection_result = plane.intersect_ray(Segment2d::new(ray_from, ray_to)); if let Some(result) = intersection_result { gizmos.sphere( Isometry3d::from(Vec3::new(result.position.x, result.position.y, 0.0)), 0.1f32, HOT_PINK, ); velocity.0 = plane.project(velocity.0); translation.0 = result.position + Vec2::new(0.0, ecb.vertical_half); } } } } */ #[derive(Clone)] pub struct AirCollisionWithStageResult { pub line_id: StageLineID, pub hit_position: FGVec2, pub hit_normal: FGVec2, } pub fn air_collide_with_stage( prev_translation: &FighterPreviousTranslation, translation: &FighterTranslation, prev_ecb: &FighterPreviousECB, ecb: &FighterECB, stage_collision: &StageCollision, ignored_polys: &[usize], can_ignore_soft_platforms: bool, ) -> Option { let ray_segment = FGSegment2d::new( prev_translation.0 + prev_ecb.get_bottom_point(), translation.0 + ecb.get_bottom_point(), ); stage_collision .stage_polys .iter() .enumerate() .filter(|(poly_idx, poly)| { !ignored_polys.contains(poly_idx) && !(can_ignore_soft_platforms && matches!(poly.poly_type, StagePolyType::Platform)) }) .filter_map(|(poly_idx, poly)| { poly.intersect_ray(ray_segment).map(|intersect_result| { ( AirCollisionWithStageResult { line_id: { StageLineID { polygon: poly_idx, segment: intersect_result.segment_idx, } }, hit_position: intersect_result.position, hit_normal: intersect_result.normal, }, intersect_result .position .distance_squared(ray_segment.point2()), ) }) }) .max_by(|(_, a), (_, b)| a.cmp(b)) .map(|(result, _)| result) } #[cfg(test)] mod tests { use super::*; use crate::fighter::ecb::{FighterECB, FighterPreviousECB}; use crate::stage::line::{StagePoly, StagePolyLineSegmentType, StagePolyType}; fn horizontal_platform(y: &str) -> StagePoly { StagePoly::build( StagePolyType::Platform, &[ (FGVec2::lit("-2", y), StagePolyLineSegmentType::Floor), (FGVec2::lit("2", y), StagePolyLineSegmentType::Floor), ], ) } fn solid_floor() -> StagePoly { StagePoly::build( StagePolyType::Closed, &[ (FGVec2::lit("-2", "0"), StagePolyLineSegmentType::Floor), (FGVec2::lit("2", "0"), StagePolyLineSegmentType::Floor), (FGVec2::lit("2", "-2"), StagePolyLineSegmentType::Wall), (FGVec2::lit("-2", "-2"), StagePolyLineSegmentType::Wall), ], ) } #[test] fn airborne_collision_skips_ignored_polygons() { let stage_collision = StageCollision { stage_polys: vec![horizontal_platform("0"), horizontal_platform("1")], }; let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "2")); let translation = FighterTranslation(FGVec2::lit("0", "-1")); let previous_ecb = FighterPreviousECB { bottom: FGVec2::ZERO, top: FGVec2::ZERO, left: FGVec2::ZERO, right: FGVec2::ZERO, }; let ecb = FighterECB { bottom: FGVec2::ZERO, top: FGVec2::ZERO, left: FGVec2::ZERO, right: FGVec2::ZERO, }; let collision = air_collide_with_stage( &previous_translation, &translation, &previous_ecb, &ecb, &stage_collision, &[1], false, ) .expect("the non-ignored platform should still be collidable"); assert_eq!(collision.line_id.polygon, 0); assert_eq!(collision.hit_position, FGVec2::lit("0", "0")); } #[test] fn airborne_collision_skips_soft_platforms_while_passing_down() { let stage_collision = StageCollision { stage_polys: vec![horizontal_platform("0")], }; let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "2")); let translation = FighterTranslation(FGVec2::lit("0", "-1")); let previous_ecb = FighterPreviousECB { bottom: FGVec2::ZERO, top: FGVec2::ZERO, left: FGVec2::ZERO, right: FGVec2::ZERO, }; let ecb = FighterECB { bottom: FGVec2::ZERO, top: FGVec2::ZERO, left: FGVec2::ZERO, right: FGVec2::ZERO, }; assert!(air_collide_with_stage( &previous_translation, &translation, &previous_ecb, &ecb, &stage_collision, &[], true, ) .is_none()); assert_eq!( air_collide_with_stage( &previous_translation, &translation, &previous_ecb, &ecb, &stage_collision, &[], false, ) .expect("air dodges still collide with soft platforms") .hit_position, FGVec2::lit("0", "0") ); } #[test] fn airborne_collision_still_hits_solid_floors_while_passing_down() { let stage_collision = StageCollision { stage_polys: vec![solid_floor()], }; let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "2")); let translation = FighterTranslation(FGVec2::lit("0", "-1")); let previous_ecb = FighterPreviousECB { bottom: FGVec2::ZERO, top: FGVec2::ZERO, left: FGVec2::ZERO, right: FGVec2::ZERO, }; let ecb = FighterECB { bottom: FGVec2::ZERO, top: FGVec2::ZERO, left: FGVec2::ZERO, right: FGVec2::ZERO, }; let collision = air_collide_with_stage( &previous_translation, &translation, &previous_ecb, &ecb, &stage_collision, &[], true, ) .expect("solid floors remain collidable while passing down"); assert_eq!(collision.line_id.polygon, 0); assert_eq!(collision.hit_position, FGVec2::lit("0", "0")); } }