Port to fixed point math
This commit is contained in:
@@ -13,7 +13,7 @@ pub struct FighterAnimationPlayerLink(Entity);
|
||||
pub fn apply_fighter_translation_to_visuals(query: Query<(&FighterTranslation, &FighterECB, &mut Transform)>) {
|
||||
for (translation, ecb, mut transform) in query {
|
||||
let translation = translation.0 + ecb.get_bottom_point();
|
||||
transform.translation = Vec3::new(translation.x, translation.y, 0.0);
|
||||
transform.translation = Vec3::new(translation.x.to_num(), translation.y.to_num(), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-14
@@ -1,31 +1,32 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::input::FighterInputFrame;
|
||||
use crate::{input::FighterInputFrame, math::int::FGi32};
|
||||
|
||||
/// Per-fighter tuning values. These are character data, not global rules —
|
||||
/// anything that applies to every fighter belongs in
|
||||
/// [`crate::game_settings::FighterSettingsCommon`] instead.
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct FighterAttributes {
|
||||
pub base_walk_accel: f32,
|
||||
pub stick_walk_accel: f32,
|
||||
pub max_walk_vel: f32,
|
||||
pub base_walk_accel: FGi32,
|
||||
pub stick_walk_accel: FGi32,
|
||||
pub max_walk_vel: FGi32,
|
||||
|
||||
pub dash_duration: u32,
|
||||
pub dash_initial_velocity: f32,
|
||||
pub base_dash_accel: f32,
|
||||
pub stick_dash_accel: f32,
|
||||
pub max_dash_vel: f32,
|
||||
pub dash_initial_velocity: FGi32,
|
||||
pub base_dash_accel: FGi32,
|
||||
pub stick_dash_accel: FGi32,
|
||||
pub max_dash_vel: FGi32,
|
||||
|
||||
pub jump_vertical_velocity: f32,
|
||||
pub jumpsquat_duration: u32,
|
||||
pub jump_vertical_velocity: FGi32,
|
||||
|
||||
pub ground_friction: f32,
|
||||
pub terminal_velocity: f32,
|
||||
pub gravity: f32,
|
||||
pub ground_friction: FGi32,
|
||||
pub terminal_velocity: FGi32,
|
||||
pub gravity: FGi32,
|
||||
}
|
||||
|
||||
impl FighterAttributes {
|
||||
pub fn get_accel_and_target_dashrun(&self, input: &FighterInputFrame) -> (f32, f32) {
|
||||
pub fn get_accel_and_target_dashrun(&self, input: &FighterInputFrame) -> (FGi32, FGi32) {
|
||||
let accel = input.movement.x * self.stick_dash_accel;
|
||||
let accel = accel + input.movement.x.signum() * self.base_dash_accel;
|
||||
let target_vel = input.movement.x * self.max_dash_vel;
|
||||
@@ -33,7 +34,7 @@ impl FighterAttributes {
|
||||
(accel, target_vel)
|
||||
}
|
||||
|
||||
pub fn get_accel_and_target_walk(&self, input: &FighterInputFrame) -> (f32, f32) {
|
||||
pub fn get_accel_and_target_walk(&self, input: &FighterInputFrame) -> (FGi32, FGi32) {
|
||||
let accel = input.movement.x * self.stick_walk_accel;
|
||||
let accel = accel + input.movement.x.signum() * self.base_walk_accel;
|
||||
let target_vel = input.movement.x * self.max_walk_vel;
|
||||
|
||||
@@ -5,6 +5,8 @@ use bevy::{color::palettes::css::HOT_PINK, prelude::*};
|
||||
use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::state::FighterStateContext;
|
||||
use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity};
|
||||
use crate::math::segment::FGSegment2d;
|
||||
use crate::math::vec::FGVec2;
|
||||
use crate::stage::StagePoly;
|
||||
use crate::stage::line::{StageCollision, StageLineID};
|
||||
/*
|
||||
@@ -36,14 +38,14 @@ pub fn collide_fighter_with_scene(
|
||||
|
||||
pub struct AirCollisionWithStageResult {
|
||||
pub line_id: StageLineID,
|
||||
pub hit_position: Vec2,
|
||||
pub hit_normal: Vec2,
|
||||
pub hit_position: FGVec2,
|
||||
pub hit_normal: FGVec2,
|
||||
}
|
||||
|
||||
pub fn air_collide_with_stage(
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<AirCollisionWithStageResult> {
|
||||
let ray_segment = Segment2d::new(
|
||||
let ray_segment = FGSegment2d::new(
|
||||
state_context.prev_translation.0 + state_context.prev_ecb.get_bottom_point(),
|
||||
state_context.translation.0 + state_context.ecb.get_bottom_point(),
|
||||
);
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
//! Fighter debug views: gizmos and the egui inspector window.
|
||||
|
||||
use bevy::color::palettes::css::ORANGE;
|
||||
use bevy::{color::palettes::css::YELLOW, prelude::*};
|
||||
use bevy_egui::{EguiContexts, egui};
|
||||
|
||||
use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::state::StateNameDebug;
|
||||
use crate::fighter::{FighterECB, FighterTranslation, FighterVelocity};
|
||||
use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity};
|
||||
use crate::input::FighterInput;
|
||||
|
||||
pub fn debug_draw_ecb(ecbs: Query<(&FighterECB, &FighterTranslation)>, mut gizmos: Gizmos) {
|
||||
for (ecb, trf) in ecbs {
|
||||
let transformed = ecb.to_3d_lineloop(Transform::from_xyz(trf.x, trf.y, 0.0));
|
||||
pub fn debug_draw_ecb(ecbs: Query<(&FighterECB, &FighterTranslation, &FighterPreviousECB, &FighterPreviousTranslation)>, mut gizmos: Gizmos) {
|
||||
for (ecb, trf, ecb_prev, trf_prev) in ecbs {
|
||||
let transformed = ecb.to_3d_lineloop(Transform::from_xyz(trf.x.to_num(), trf.y.to_num(), 0.0));
|
||||
gizmos.lineloop(transformed, YELLOW);
|
||||
let prev_transformed = ecb_prev.to_3d_lineloop(Transform::from_xyz(trf_prev.x.to_num(), trf_prev.y.to_num(), -0.1));
|
||||
gizmos.lineloop(prev_transformed, ORANGE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+53
-16
@@ -1,4 +1,7 @@
|
||||
use bevy::prelude::*;
|
||||
use fixed::traits::Fixed;
|
||||
|
||||
use crate::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
|
||||
@@ -6,40 +9,74 @@ use bevy::prelude::*;
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
#[require(FighterPreviousECB)]
|
||||
pub struct FighterECB {
|
||||
pub vertical_half: f32,
|
||||
pub horizontal_half: f32,
|
||||
pub vertical_half: FGi32,
|
||||
pub horizontal_half: FGi32,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy, Default)]
|
||||
pub struct FighterPreviousECB {
|
||||
pub vertical_half: f32,
|
||||
pub horizontal_half: f32,
|
||||
pub vertical_half: FGi32,
|
||||
pub horizontal_half: FGi32,
|
||||
}
|
||||
|
||||
impl From<FighterECB> for FighterPreviousECB {
|
||||
fn from(ecb: FighterECB) -> Self {
|
||||
Self {
|
||||
vertical_half: ecb.vertical_half,
|
||||
horizontal_half: ecb.horizontal_half,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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] {
|
||||
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
|
||||
let rel_down = trf.transform_point(Vec3::new(0.0, -self.vertical_half, 0.0));
|
||||
let rel_left = trf.transform_point(Vec3::new(-self.horizontal_half, 0.0, 0.0));
|
||||
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
|
||||
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half.to_num(), 0.0));
|
||||
let rel_down = trf.transform_point(Vec3::new(0.0, (-self.vertical_half).to_num(), 0.0));
|
||||
let rel_left = trf.transform_point(Vec3::new((-self.horizontal_half).to_num(), 0.0, 0.0));
|
||||
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half.to_num(), 0.0, 0.0));
|
||||
[rel_up, rel_right, rel_down, rel_left]
|
||||
}
|
||||
|
||||
pub fn get_bottom_point(&self) -> Vec2 {
|
||||
Vec2::new(0.0, -self.vertical_half)
|
||||
pub fn get_bottom_point(&self) -> FGVec2 {
|
||||
FGVec2::new(FGi32::ZERO, -self.vertical_half)
|
||||
}
|
||||
}
|
||||
|
||||
impl FighterPreviousECB {
|
||||
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
||||
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
|
||||
let rel_down = trf.transform_point(Vec3::new(0.0, -self.vertical_half, 0.0));
|
||||
let rel_left = trf.transform_point(Vec3::new(-self.horizontal_half, 0.0, 0.0));
|
||||
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
|
||||
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half.to_num(), 0.0));
|
||||
let rel_down = trf.transform_point(Vec3::new(0.0, (-self.vertical_half).to_num(), 0.0));
|
||||
let rel_left = trf.transform_point(Vec3::new((-self.horizontal_half).to_num(), 0.0, 0.0));
|
||||
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half.to_num(), 0.0, 0.0));
|
||||
[rel_up, rel_right, rel_down, rel_left]
|
||||
}
|
||||
|
||||
pub fn get_bottom_point(&self) -> Vec2 {
|
||||
Vec2::new(0.0, -self.vertical_half)
|
||||
pub fn get_bottom_point(&self) -> FGVec2 {
|
||||
FGVec2::new(FGi32::ZERO, -self.vertical_half)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn previous_ecb_copies_current_dimensions() {
|
||||
let current = FighterECB {
|
||||
vertical_half: FGi32::lit("0.5"),
|
||||
horizontal_half: FGi32::lit("0.25"),
|
||||
};
|
||||
let previous = FighterPreviousECB::from(current);
|
||||
|
||||
assert_eq!(previous.vertical_half, current.vertical_half);
|
||||
assert_eq!(previous.horizontal_half, current.horizontal_half);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -45,7 +45,12 @@ impl Plugin for FighterPlugin {
|
||||
// Every state gets a chance to interrupt itself, in priority order.
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
(animation::setup_fighter_animation_player, state::state_interrupt_system,).in_set(GameplaySet::Interrupt),
|
||||
(
|
||||
ecb::snapshot_fighter_ecb.before(state_interrupt_system),
|
||||
animation::setup_fighter_animation_player,
|
||||
state::state_interrupt_system,
|
||||
)
|
||||
.in_set(GameplaySet::Interrupt),
|
||||
)
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
@@ -74,6 +79,7 @@ impl Plugin for FighterPlugin {
|
||||
// components; a component simulated here but missing from this list
|
||||
// is a desync waiting to happen.
|
||||
.rollback_component_with_copy::<FighterECB>()
|
||||
.rollback_component_with_copy::<ecb::FighterPreviousECB>()
|
||||
.rollback_component_with_copy::<FighterVelocity>()
|
||||
.rollback_component_with_copy::<FighterTranslation>()
|
||||
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::fighter::FighterAttributes;
|
||||
use crate::{fighter::FighterAttributes, math::vec::FGVec2};
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct Grounded;
|
||||
|
||||
#[derive(Component, Deref, DerefMut, Default, Debug, Clone, Copy)]
|
||||
pub struct FighterVelocity(pub Vec2);
|
||||
pub struct FighterVelocity(pub FGVec2);
|
||||
|
||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy)]
|
||||
pub struct FighterPreviousTranslation(pub Vec2);
|
||||
pub struct FighterPreviousTranslation(pub FGVec2);
|
||||
|
||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy)]
|
||||
#[require(FighterPreviousTranslation)]
|
||||
pub struct FighterTranslation(pub Vec2);
|
||||
pub struct FighterTranslation(pub FGVec2);
|
||||
|
||||
pub fn apply_air_motion(
|
||||
fighters: Query<(
|
||||
@@ -37,7 +37,7 @@ pub fn copy_fighter_transform_to_visuals(
|
||||
fighters: Query<(&FighterTranslation, &mut Transform)>,
|
||||
) {
|
||||
for (translation, mut transform) in fighters {
|
||||
transform.translation = Vec3::new(translation.x, translation.y, 0.0);
|
||||
transform.translation = Vec3::new(translation.x.to_num(), translation.y.to_num(), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::hash::{Hash, Hasher};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, run, wait, walk};
|
||||
use super::{FighterStateImpl, FighterStateContext, ground, run, wait, walk};
|
||||
use crate::fighter::state::ground::GroundedStateCommon;
|
||||
use crate::fighter::state::wait::WaitState;
|
||||
use crate::fighter::state::{FighterState};
|
||||
@@ -12,11 +12,12 @@ use crate::input::{
|
||||
FighterCommands::{SmashMoveLeft, SmashMoveRight},
|
||||
FighterInput,
|
||||
};
|
||||
use crate::math::int::FGi32;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DashState {
|
||||
frames_in_dash: u32,
|
||||
direction: f32,
|
||||
direction: FGi32,
|
||||
ground_common: GroundedStateCommon
|
||||
}
|
||||
|
||||
@@ -28,7 +29,7 @@ impl std::hash::Hash for DashState {
|
||||
}
|
||||
|
||||
impl DashState {
|
||||
pub fn new(direction: f32, ground_common: GroundedStateCommon) -> Self {
|
||||
pub fn new(direction: FGi32, ground_common: GroundedStateCommon) -> Self {
|
||||
Self {
|
||||
frames_in_dash: 0,
|
||||
direction,
|
||||
@@ -98,11 +99,11 @@ impl FighterStateImpl for DashState {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
|
||||
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<FGi32> {
|
||||
if state_context.input.has_command(SmashMoveLeft) {
|
||||
return Some(-1.0);
|
||||
return Some(FGi32::NEG_ONE);
|
||||
} else if state_context.input.has_command(SmashMoveRight) {
|
||||
return Some(1.0);
|
||||
return Some(FGi32::ONE);
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, walk};
|
||||
use super::{FighterStateImpl, FighterStateContext, ground, walk};
|
||||
use crate::fighter::state::{FighterState, air};
|
||||
use crate::fighter::state::ground::GroundedStateCommon;
|
||||
use crate::fighter::state::wait::WaitState;
|
||||
|
||||
+21
-23
@@ -7,17 +7,20 @@ use crate::fighter::{
|
||||
FighterAttributes, FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||
};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::math::int::FGi32;
|
||||
use crate::math::vec::FGVec2;
|
||||
use crate::stage::StagePoly;
|
||||
use crate::stage::line::{StageCollision, StageLineID, StagePolyLineSegment, StagePolyLineSegmentType};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use fixed::traits::Fixed;
|
||||
|
||||
#[derive(Reflect, Hash, Clone, Debug)]
|
||||
pub struct GroundedStateCommon {
|
||||
pub current_line_id: StageLineID,
|
||||
}
|
||||
|
||||
pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
||||
pub fn apply_grounded_friction(friction: FGi32, ground_vel: FGi32) -> FGi32 {
|
||||
if friction.abs() > ground_vel.abs() {
|
||||
-ground_vel
|
||||
} else {
|
||||
@@ -28,21 +31,21 @@ pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
||||
pub fn move_accelerate() {}
|
||||
|
||||
pub fn compute_ground_accel(
|
||||
accel: f32,
|
||||
target_vel: f32,
|
||||
gr_vel: f32,
|
||||
accel: FGi32,
|
||||
target_vel: FGi32,
|
||||
gr_vel: FGi32,
|
||||
attribs: &FighterAttributes,
|
||||
game_settings: &GameSettings,
|
||||
) -> f32 {
|
||||
) -> FGi32 {
|
||||
if target_vel == 0.0 {
|
||||
apply_grounded_friction(attribs.ground_friction, gr_vel)
|
||||
} else {
|
||||
let mut accel = accel;
|
||||
let ground_max_horizontal_velocity =
|
||||
game_settings.figher_common.ground_max_horizontal_velocity;
|
||||
if !(gr_vel * accel < 0.0) {
|
||||
if !(gr_vel * accel < FGi32::ZERO) {
|
||||
// accelerating, not reversing
|
||||
if accel > 0.0 {
|
||||
if accel > FGi32::ZERO {
|
||||
if gr_vel + accel > target_vel {
|
||||
accel = -attribs.ground_friction;
|
||||
if gr_vel + accel < target_vel {
|
||||
@@ -78,7 +81,7 @@ pub fn apply_grounded_motion(
|
||||
) {
|
||||
// Vertical velocity should be 0 on the ground
|
||||
prev_translation.0 = translation.0;
|
||||
translation.0 += Vec2::new(velocity.0.x ,0.0);
|
||||
translation.0 += FGVec2::new(velocity.0.x ,FGi32::ZERO);
|
||||
}
|
||||
|
||||
pub fn collide_with_stage_grounded(
|
||||
@@ -96,34 +99,29 @@ pub fn collide_with_stage_grounded(
|
||||
|
||||
if let Some(stage_poly) = stage_poly {
|
||||
let ecb_bottom_pos = translation.0 + ecb.get_bottom_point();
|
||||
let left_segment_idx = ground_common
|
||||
.current_line_id
|
||||
.segment
|
||||
.checked_sub(1)
|
||||
.unwrap_or(0);
|
||||
let right_segment_idx =
|
||||
(ground_common.current_line_id.segment + 1) % stage_poly.segments.len();
|
||||
let current_segment_idx = ground_common.current_line_id.segment;
|
||||
|
||||
// First, we check the current segment, if that fails, we check the surrounding ones
|
||||
let grounded_ecb_bottom_pos = [
|
||||
ground_common.current_line_id.segment.clone(),
|
||||
left_segment_idx,
|
||||
right_segment_idx,
|
||||
Some(current_segment_idx),
|
||||
stage_poly.get_prev_segment_index(current_segment_idx),
|
||||
stage_poly.get_next_segment_index(current_segment_idx),
|
||||
]
|
||||
.iter()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find_map(|segment_idx| {
|
||||
let segment = stage_poly.get_segment(*segment_idx)?;
|
||||
let segment = stage_poly.get_segment(segment_idx)?;
|
||||
if segment.segment_type != StagePolyLineSegmentType::Floor {
|
||||
return None;
|
||||
}
|
||||
|
||||
let line_dir = segment.segment.direction();
|
||||
let projected_point = segment.segment.point1() + (ecb_bottom_pos - segment.segment.point1()).project_onto(line_dir.as_vec2());
|
||||
let projected_point = segment.segment.point1() + (ecb_bottom_pos - segment.segment.point1()).project_onto(line_dir);
|
||||
let closest_point = segment.segment.closest_point(ecb_bottom_pos);
|
||||
let distance = closest_point.distance(projected_point);
|
||||
|
||||
|
||||
if distance < 0.01 {
|
||||
return Some((projected_point, *segment_idx, segment.normal));
|
||||
return Some((projected_point, segment_idx, segment.normal));
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
+85
-20
@@ -1,41 +1,106 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::fighter::state::{FighterState, FighterStateImpl};
|
||||
use crate::{
|
||||
fighter::state::{
|
||||
FighterState, FighterStateContext, FighterStateImpl,
|
||||
fall::FallState,
|
||||
ground::{self, GroundedMotionResult, GroundedStateCommon},
|
||||
},
|
||||
input::FighterCommands,
|
||||
};
|
||||
|
||||
pub struct JumpState {
|
||||
pub counter: u32
|
||||
pub enum JumpType {
|
||||
ShortHop,
|
||||
LongJump,
|
||||
DoubleJump,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct JumpState {}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct JumpSquatState {
|
||||
pub counter: u32
|
||||
pub grounded_common: GroundedStateCommon,
|
||||
pub duration_counter: u32,
|
||||
}
|
||||
|
||||
impl JumpSquatState {
|
||||
pub fn create(grounded_common: GroundedStateCommon) -> Self {
|
||||
Self {
|
||||
grounded_common,
|
||||
duration_counter: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FighterStateImpl for JumpSquatState {
|
||||
const NAME: &'static str = "JumpSquat";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &super::FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
None
|
||||
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
if self.duration_counter >= state_context.fighter_attribs.jumpsquat_duration {
|
||||
Some(FighterState::Jump(JumpState {}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _state_context: &mut super::FighterStateContext) {
|
||||
todo!()
|
||||
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||
self.duration_counter += 1;
|
||||
|
||||
let friction =
|
||||
if state_context.velocity.x.abs() > state_context.fighter_attribs.max_walk_vel {
|
||||
state_context.fighter_attribs.ground_friction
|
||||
* state_context
|
||||
.game_settings
|
||||
.figher_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_attribs.ground_friction
|
||||
};
|
||||
|
||||
let ground_velocity = state_context.velocity.x;
|
||||
|
||||
state_context.velocity.x += ground::apply_grounded_friction(friction, ground_velocity);
|
||||
|
||||
ground::apply_grounded_motion(
|
||||
state_context.velocity,
|
||||
state_context.translation,
|
||||
state_context.prev_translation,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
state_context: &mut FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
if let GroundedMotionResult::InAir =
|
||||
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
||||
{
|
||||
Some(FighterState::Fall(FallState))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl FighterStateImpl for JumpState {
|
||||
const NAME: &'static str = "Jump";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &super::FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
None
|
||||
}
|
||||
|
||||
fn update(&mut self, _state_context: &mut super::FighterStateContext) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
fn update(&mut self, _state_context: &mut FighterStateContext) {}
|
||||
}
|
||||
|
||||
pub fn check_input(
|
||||
state_context: &FighterStateContext,
|
||||
ground_common: &GroundedStateCommon,
|
||||
) -> Option<FighterState> {
|
||||
state_context
|
||||
.input
|
||||
.has_command(FighterCommands::Jump)
|
||||
.then_some(FighterState::JumpSquat(JumpSquatState::create(
|
||||
ground_common.clone(),
|
||||
)));
|
||||
None
|
||||
}
|
||||
|
||||
+13
-13
@@ -11,6 +11,7 @@ use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::{FighterAttributes, FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
use crate::math::int::FGi32;
|
||||
use crate::schedule::GameplaySet;
|
||||
use crate::stage::StagePoly;
|
||||
use crate::stage::line::StageCollision;
|
||||
@@ -24,14 +25,6 @@ pub mod fall;
|
||||
pub mod air;
|
||||
pub mod jump;
|
||||
|
||||
pub enum FighterStateTransition {
|
||||
Wait,
|
||||
Walk,
|
||||
Dash(f32),
|
||||
Run,
|
||||
Fall,
|
||||
}
|
||||
|
||||
macro_rules! fighter_states {
|
||||
($($variant:ident => $ty:ty),* $(,)?) => {
|
||||
#[derive(Component, Clone, Debug, Hash)]
|
||||
@@ -80,7 +73,7 @@ pub trait FighterStateImpl: Sized {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FighterStateContext<'a> {
|
||||
pub struct FighterStateContext<'a, 'w, 's> {
|
||||
pub translation: &'a mut FighterTranslation,
|
||||
pub prev_translation: &'a mut FighterPreviousTranslation,
|
||||
pub input: &'a mut FighterInput,
|
||||
@@ -91,6 +84,7 @@ pub struct FighterStateContext<'a> {
|
||||
pub game_settings: &'a GameSettings,
|
||||
pub entity: Entity,
|
||||
pub stage_collision: &'a StageCollision,
|
||||
pub gizmos: Option<&'a mut Gizmos<'w, 's>>,
|
||||
}
|
||||
|
||||
fighter_states! {
|
||||
@@ -98,7 +92,9 @@ fighter_states! {
|
||||
Walk => walk::WalkState,
|
||||
Dash => dash::DashState,
|
||||
Run => run::RunState,
|
||||
Fall => fall::FallState
|
||||
Fall => fall::FallState,
|
||||
JumpSquat => jump::JumpSquatState,
|
||||
Jump => jump::JumpState
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
@@ -128,6 +124,7 @@ pub fn state_update_system(
|
||||
game_settings: &game_settings,
|
||||
entity: ent,
|
||||
stage_collision: &stage_collision,
|
||||
gizmos: Some(&mut gizmos),
|
||||
};
|
||||
state.update(&mut update_ctx);
|
||||
}
|
||||
@@ -150,7 +147,8 @@ pub fn state_interrupt_system (
|
||||
fighter_attribs: &attribs,
|
||||
game_settings: &game_settings,
|
||||
entity: ent,
|
||||
stage_collision: &stage_collision
|
||||
stage_collision: &stage_collision,
|
||||
gizmos: None,
|
||||
};
|
||||
if let Some(new_state) = state.check_interrupt(&state_context) {
|
||||
let mut new_state = new_state;
|
||||
@@ -171,6 +169,7 @@ pub fn state_collision_interrupt_system(
|
||||
stage_collision: Res<StageCollision>,
|
||||
game_settings: Res<GameSettings>,
|
||||
mut commands: Commands,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
for (ent, mut state, mut translation, mut prev_translation, mut input, mut velocity, mut ecb, mut prev_ecb, attribs) in query {
|
||||
let mut state_context = FighterStateContext {
|
||||
@@ -183,7 +182,8 @@ pub fn state_collision_interrupt_system(
|
||||
fighter_attribs: &attribs,
|
||||
game_settings: &game_settings,
|
||||
entity: ent,
|
||||
stage_collision: &stage_collision
|
||||
stage_collision: &stage_collision,
|
||||
gizmos: Some(&mut gizmos),
|
||||
};
|
||||
if let Some(new_state) = state.check_collision_interrupt(&mut state_context) {
|
||||
let mut new_state = new_state;
|
||||
@@ -197,4 +197,4 @@ pub fn state_collision_interrupt_system(
|
||||
info!("State {} -> {}", old_state_name, new_state_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, wait, walk};
|
||||
use super::{FighterStateImpl, FighterStateContext, ground, wait, walk};
|
||||
use crate::fighter::state::FighterState;
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, walk};
|
||||
use super::{FighterStateImpl, FighterStateContext, ground, walk};
|
||||
use crate::fighter::state::FighterState;
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||
@@ -33,7 +33,10 @@ impl FighterStateImpl for WaitState {
|
||||
} else {
|
||||
state_context.fighter_attribs.ground_friction
|
||||
};
|
||||
state_context.velocity.x += ground::apply_grounded_friction(friction, state_context.velocity.x);
|
||||
|
||||
let ground_velocity = state_context.velocity.x;
|
||||
|
||||
state_context.velocity.x += ground::apply_grounded_friction(friction, ground_velocity);
|
||||
|
||||
ground::apply_grounded_motion(
|
||||
state_context.velocity,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, dash, ground, wait};
|
||||
use super::{FighterStateImpl, FighterStateContext, dash, ground, wait};
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||
use crate::fighter::state::{self, FighterState};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::math::int::FGi32;
|
||||
|
||||
pub struct FighterSettingsCommon {
|
||||
pub walk_speed_ease: f32,
|
||||
pub ground_max_horizontal_velocity: f32,
|
||||
pub ground_friction_over_walk_speed_multiplier: f32
|
||||
pub walk_speed_ease: FGi32,
|
||||
pub ground_max_horizontal_velocity: FGi32,
|
||||
pub ground_friction_over_walk_speed_multiplier: FGi32
|
||||
}
|
||||
pub struct InputSettingsCommon {
|
||||
pub stick_deadzone: f32,
|
||||
|
||||
@@ -17,6 +17,7 @@ struct FighterInputBufferedActions {}
|
||||
pub enum FighterCommands {
|
||||
SmashMoveLeft,
|
||||
SmashMoveRight,
|
||||
Jump,
|
||||
}
|
||||
|
||||
/// Frames of life remaining for each buffered command. Indexed by
|
||||
@@ -25,6 +26,7 @@ pub enum FighterCommands {
|
||||
pub struct FighterCommandLifetimes {
|
||||
smash_move_left: u8,
|
||||
smash_move_right: u8,
|
||||
jump: u8,
|
||||
}
|
||||
|
||||
impl Index<FighterCommands> for FighterCommandLifetimes {
|
||||
@@ -34,6 +36,7 @@ impl Index<FighterCommands> for FighterCommandLifetimes {
|
||||
match command {
|
||||
FighterCommands::SmashMoveLeft => &self.smash_move_left,
|
||||
FighterCommands::SmashMoveRight => &self.smash_move_right,
|
||||
FighterCommands::Jump => &self.jump,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +46,7 @@ impl IndexMut<FighterCommands> for FighterCommandLifetimes {
|
||||
match command {
|
||||
FighterCommands::SmashMoveLeft => &mut self.smash_move_left,
|
||||
FighterCommands::SmashMoveRight => &mut self.smash_move_right,
|
||||
FighterCommands::Jump => &mut self.jump,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,10 +1,12 @@
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::math::vec::FGVec2;
|
||||
|
||||
/// One frame of a player's raw input. This is the type GGRS serializes and
|
||||
/// sends over the wire, so keep it small and keep it `PartialEq`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct FighterInputFrame {
|
||||
pub movement: Vec2,
|
||||
pub directional_attack: Vec2,
|
||||
pub movement: FGVec2,
|
||||
pub directional_attack: FGVec2,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use bevy::prelude::*;
|
||||
use bevy_ggrs::{LocalInputs, LocalPlayers};
|
||||
|
||||
use crate::input::{BaseInputMap, FighterInputFrame, InputActionState, InputMapAction};
|
||||
use crate::math::int::FGi32;
|
||||
use crate::netcode::GGRSCfg;
|
||||
|
||||
pub fn preprocess_keyboard_input(
|
||||
@@ -32,16 +33,16 @@ pub fn preprocess_keyboard_input(
|
||||
};
|
||||
|
||||
let action_strength = match action_state {
|
||||
InputActionState::JustPressed | InputActionState::Pressed => 1.0f32,
|
||||
InputActionState::JustReleased | InputActionState::Released => 0.0f32,
|
||||
InputActionState::JustPressed | InputActionState::Pressed => FGi32::ONE,
|
||||
InputActionState::JustReleased | InputActionState::Released => FGi32::ZERO,
|
||||
};
|
||||
|
||||
match keyboard_elment.action {
|
||||
InputMapAction::MovementXDir(sign) => {
|
||||
input_frame.movement.x += (sign as f32) * action_strength
|
||||
input_frame.movement.x += (sign) * action_strength
|
||||
}
|
||||
InputMapAction::MovementYDir(sign) => {
|
||||
input_frame.movement.y += (sign as f32) * action_strength
|
||||
input_frame.movement.y += (sign) * action_strength
|
||||
}
|
||||
InputMapAction::Jump => todo!(),
|
||||
}
|
||||
|
||||
+18
-18
@@ -29,7 +29,7 @@ use jackdaw_runtime::prelude::*;
|
||||
use crate::{
|
||||
fighter::{
|
||||
FighterAttributes, FighterECB, FighterTranslation, FighterVelocity, FighterVisual, animation::FighterAnimations, state::{FighterState, fall::FallState, wait::WaitState},
|
||||
}, game_settings::{FighterSettingsCommon, GameSettings, InputSettingsCommon}, input::FighterInput, player::Player, schedule::GameplaySchedulePlugin,
|
||||
}, game_settings::{FighterSettingsCommon, GameSettings, InputSettingsCommon}, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySchedulePlugin,
|
||||
};
|
||||
|
||||
mod args;
|
||||
@@ -62,24 +62,24 @@ pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
Name("Fighter".into()),
|
||||
Player { handle: 0 },
|
||||
FighterECB {
|
||||
vertical_half: 0.5,
|
||||
horizontal_half: 0.25,
|
||||
vertical_half: FGi32::lit("0.5"),
|
||||
horizontal_half: FGi32::lit("0.25"),
|
||||
},
|
||||
FighterVelocity::default(),
|
||||
FighterTranslation(Vec2::new(0.5, 1.25)),
|
||||
FighterTranslation(FGVec2::lit("0.5", "1.25")),
|
||||
FighterAttributes {
|
||||
base_walk_accel: 0.01,
|
||||
stick_walk_accel: 0.02,
|
||||
max_walk_vel: 0.16,
|
||||
ground_friction: 0.008,
|
||||
base_walk_accel: FGi32::lit("0.01"),
|
||||
stick_walk_accel: FGi32::lit("0.02"),
|
||||
max_walk_vel: FGi32::lit("0.16"),
|
||||
ground_friction: FGi32::lit("0.008"),
|
||||
dash_duration: 15,
|
||||
base_dash_accel: 0.002,
|
||||
stick_dash_accel: 0.01,
|
||||
max_dash_vel: 0.22,
|
||||
jump_vertical_velocity: 0.25,
|
||||
dash_initial_velocity: 0.19,
|
||||
terminal_velocity: 0.28,
|
||||
gravity: 0.023,
|
||||
base_dash_accel: FGi32::lit("0.002"),
|
||||
stick_dash_accel: FGi32::lit("0.01"),
|
||||
max_dash_vel: FGi32::lit("0.22"),
|
||||
jump_vertical_velocity: FGi32::lit("0.25"),
|
||||
dash_initial_velocity: FGi32::lit("0.19"),
|
||||
terminal_velocity: FGi32::lit("0.28"),
|
||||
gravity: FGi32::lit("0.023"),
|
||||
},
|
||||
FighterState::Fall(FallState),
|
||||
FighterInput::default(),
|
||||
@@ -115,9 +115,9 @@ impl Plugin for GamePlugin {
|
||||
.insert_resource(args::Args::parse())
|
||||
.insert_resource(GameSettings {
|
||||
figher_common: FighterSettingsCommon {
|
||||
walk_speed_ease: 0.5,
|
||||
ground_max_horizontal_velocity: 0.3,
|
||||
ground_friction_over_walk_speed_multiplier: 2.0,
|
||||
walk_speed_ease: FGi32::lit("0.5"),
|
||||
ground_max_horizontal_velocity: FGi32::lit("0.3"),
|
||||
ground_friction_over_walk_speed_multiplier: FGi32::lit("2.0"),
|
||||
},
|
||||
input_common: InputSettingsCommon::default(),
|
||||
});
|
||||
|
||||
+79
-34
@@ -1,12 +1,13 @@
|
||||
use bevy::math::{Vec2, primitives::Segment2d};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use fixed::prelude::*;
|
||||
|
||||
use crate::math::vec::FGVec2;
|
||||
|
||||
type FGi32 = fixed::FixedI32<fixed::types::extra::U16>;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Reflect)]
|
||||
pub struct FGSegment2d {
|
||||
point1: FGVec2,
|
||||
point2: FGVec2
|
||||
@@ -29,14 +30,72 @@ impl FGSegment2d {
|
||||
pub fn point2(&self) -> FGVec2 {
|
||||
self.point2
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SegmentIntersection {
|
||||
fn segment_intersection(&self, rhs: &Segment2d) -> Option<FGVec2>;
|
||||
}
|
||||
/// Compute the normalized direction pointing from the first endpoint to the second endpoint.
|
||||
///
|
||||
/// For the non-panicking version, see [`FGVec2::try_direction`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a valid direction could not be computed, for example when the endpoints are coincident, NaN, or infinite.
|
||||
#[inline]
|
||||
pub fn direction(&self) -> FGVec2 {
|
||||
self.try_direction().expect("Failed to compute the direction of a line segment")
|
||||
}
|
||||
|
||||
impl SegmentIntersection for Segment2d {
|
||||
fn segment_intersection(&self, rhs: &Segment2d) -> Option<Vec2> {
|
||||
/// Try to compute the normalized direction pointing from the first endpoint to the second endpoint.
|
||||
///
|
||||
/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid direction could not be computed,
|
||||
/// for example when the endpoints are coincident, NaN, or infinite.
|
||||
#[inline]
|
||||
pub fn try_direction(&self) -> Option<FGVec2> {
|
||||
self.point1.direction_to(self.point2)
|
||||
}
|
||||
|
||||
/// Returns the point on the [`Segment2d`] that is closest to the specified `point`.
|
||||
#[inline]
|
||||
pub fn closest_point(&self, point: FGVec2) -> FGVec2 {
|
||||
// `point`
|
||||
// x
|
||||
// ^|
|
||||
// / |
|
||||
//`offset`/ |
|
||||
// / | `segment_vector`
|
||||
// x----.-------------->x
|
||||
// 0 t 1
|
||||
let segment_vector = self.point2 - self.point1;
|
||||
let offset = point - self.point1;
|
||||
// The signed projection of `offset` onto `segment_vector`, scaled by the length of the segment.
|
||||
let projection_scaled = segment_vector.dot(offset);
|
||||
|
||||
// `point` is too far "left" in the picture
|
||||
if projection_scaled <= 0.0 {
|
||||
return self.point1;
|
||||
}
|
||||
|
||||
let length_squared = segment_vector.length_squared();
|
||||
// `point` is too far "right" in the picture
|
||||
if projection_scaled >= length_squared {
|
||||
return self.point2;
|
||||
}
|
||||
|
||||
// Point lies somewhere in the middle, we compute the closest point by finding the parameter along the line.
|
||||
let t = projection_scaled / length_squared;
|
||||
self.point1 + segment_vector * t
|
||||
}
|
||||
|
||||
/// Compute the normalized counterclockwise normal on the left-hand side of the line segment.
|
||||
///
|
||||
/// For the non-panicking version, see [`Segment2d::try_left_normal`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a valid normal could not be computed, for example when the endpoints are coincident
|
||||
pub fn left_normal(&self) -> FGVec2 {
|
||||
self.point1.direction_to(self.point2).map(|o| { FGVec2::new(-o.y, o.x)}).expect("a valid normal could not be computed")
|
||||
}
|
||||
|
||||
pub fn segment_intersection(&self, rhs: &FGSegment2d) -> Option<FGVec2> {
|
||||
let p0 = self.point1();
|
||||
let p1 = self.point2();
|
||||
let p2 = rhs.point1();
|
||||
@@ -48,45 +107,31 @@ impl SegmentIntersection for Segment2d {
|
||||
let s2_y = p3.y - p2.y;
|
||||
|
||||
let denom = -s2_x * s1_y + s1_x * s2_y;
|
||||
if denom.abs() < f32::EPSILON {
|
||||
if denom == FGi32::ZERO {
|
||||
return None; // Parallel or collinear
|
||||
}
|
||||
|
||||
let s = (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) / denom;
|
||||
let t = ( s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) / denom;
|
||||
|
||||
if s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0 {
|
||||
Some(Vec2::new(p0.x + (t * s1_x), p0.y + (t * s1_y)))
|
||||
if s >= FGi32::ZERO && s <= FGi32::ONE && t >= FGi32::ZERO && t <= FGi32::ONE {
|
||||
Some(FGVec2::new(p0.x + (t * s1_x), p0.y + (t * s1_y)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SegmentIntersection for Segment2d {
|
||||
fn segment_intersection(&self, rhs: &Segment2d) -> Option<Vec2> {
|
||||
let p0 = self.point1();
|
||||
let p1 = self.point2();
|
||||
let p2 = rhs.point1();
|
||||
let p3 = rhs.point2();
|
||||
|
||||
let s1_x = p1.x - p0.x;
|
||||
let s1_y = p1.y - p0.y;
|
||||
let s2_x = p3.x - p2.x;
|
||||
let s2_y = p3.y - p2.y;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
let denom = -s2_x * s1_y + s1_x * s2_y;
|
||||
if denom.abs() < f32::EPSILON {
|
||||
return None; // Parallel or collinear
|
||||
}
|
||||
#[test]
|
||||
fn closest_point_projects_and_clamps_to_segment() {
|
||||
let segment = FGSegment2d::new(FGVec2::lit("-4", "0"), FGVec2::lit("4", "0"));
|
||||
|
||||
let s = (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) / denom;
|
||||
let t = ( s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) / denom;
|
||||
|
||||
if s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0 {
|
||||
Some(Vec2::new(p0.x + (t * s1_x), p0.y + (t * s1_y)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
assert_eq!(segment.closest_point(FGVec2::lit("2", "1")), FGVec2::lit("2", "0"));
|
||||
assert_eq!(segment.closest_point(FGVec2::lit("-6", "1")), segment.point1());
|
||||
assert_eq!(segment.closest_point(FGVec2::lit("6", "1")), segment.point2());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+206
-8
@@ -1,14 +1,24 @@
|
||||
use bevy::math::Vec2;
|
||||
use std::ops::{Add, Div, Mul, Sub};
|
||||
use bevy::prelude::*;
|
||||
use fixed::types::I32F32;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::{AddAssign, Add, Div, Mul, Sub};
|
||||
|
||||
use super::int::FGi32;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Default, Debug, Reflect, Serialize, Deserialize)]
|
||||
#[reflect(opaque)]
|
||||
pub struct FGVec2 {
|
||||
pub x: FGi32,
|
||||
pub y: FGi32,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FGVec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[{}, {}]", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl FGVec2 {
|
||||
pub const ZERO: Self = Self::new(FGi32::ZERO, FGi32::ZERO);
|
||||
|
||||
@@ -17,6 +27,11 @@ impl FGVec2 {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn lit(x: &str, y: &str) -> Self {
|
||||
Self { x: FGi32::lit(x), y: FGi32::lit(y) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_i32(x: i32, y: i32) -> Self {
|
||||
Self {
|
||||
@@ -31,17 +46,78 @@ impl FGVec2 {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(&self) -> FGi32 {
|
||||
pub fn length(self) -> FGi32 {
|
||||
(self.x * self.x + self.y * self.y).sqrt()
|
||||
}
|
||||
|
||||
/// Returns the vector projection of `self` onto `rhs`.
|
||||
///
|
||||
/// `rhs` must be normalized.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if `rhs` is not normalized when `glam_assert` is enabled.
|
||||
#[inline]
|
||||
pub fn normalized_or_zero(&self) -> Self {
|
||||
let len = self.length();
|
||||
if len.is_zero() {
|
||||
Self::ZERO
|
||||
#[must_use]
|
||||
pub fn project_onto_normalized(self, rhs: Self) -> Self {
|
||||
assert!(rhs.is_normalized());
|
||||
|
||||
rhs * self.dot(rhs)
|
||||
}
|
||||
|
||||
const EPS: FGi32 = FGi32::from_bits(6);
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_normalized(self) -> bool {
|
||||
let diff = self.length() - FGi32::ONE;
|
||||
diff.abs() <= Self::EPS
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn dot(self, rhs: Self) -> FGi32 {
|
||||
(self.x * rhs.x) + (self.y * rhs.y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn project_onto(self, rhs: Self) -> Self {
|
||||
let other_len_sq = rhs.dot(rhs);
|
||||
assert!(other_len_sq != FGi32::ZERO);
|
||||
rhs * (self.dot(rhs) / other_len_sq)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn normalize_or_zero(&self) -> Self {
|
||||
self.normalize().unwrap_or(FGVec2::ZERO)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn direction_to(self, to: Self) -> Option<Self> {
|
||||
(to - self).normalize()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn distance(self, to: Self) -> FGi32 {
|
||||
(to - self).length()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn normalize(&self) -> Option<Self> {
|
||||
let x = I32F32::from_num(self.x);
|
||||
let y = I32F32::from_num(self.y);
|
||||
let length_squared = x * x + y * y;
|
||||
|
||||
if length_squared.is_zero() {
|
||||
None
|
||||
} else {
|
||||
self / len
|
||||
let length = length_squared.sqrt();
|
||||
Some(Self::new(
|
||||
FGi32::from_num(x / length),
|
||||
FGi32::from_num(y / length),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +154,14 @@ impl Sub for FGVec2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&FGVec2> for &FGVec2 {
|
||||
type Output = FGVec2;
|
||||
#[inline]
|
||||
fn sub(self, rhs: &FGVec2) -> FGVec2 {
|
||||
(*self).sub(*rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for FGVec2 {
|
||||
type Output = Self;
|
||||
|
||||
@@ -113,3 +197,117 @@ impl Div<FGi32> for &FGVec2 {
|
||||
(*self).div(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for FGVec2 {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = Self {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod is_normalized_tests {
|
||||
use super::*;
|
||||
use fixed::types::I16F16 as FGi32;
|
||||
|
||||
// Normalization is computed in Q32.32, so the Q16.16 error comes from the
|
||||
// two final component conversions and length_squared()'s multiplications.
|
||||
const EPS_FIXED_BITS: i32 = 4;
|
||||
|
||||
#[test]
|
||||
fn normalizes_small_axis_vector_without_losing_squared_precision() {
|
||||
let normalized = FGVec2::lit("0.01", "0").normalize().unwrap();
|
||||
|
||||
assert_eq!(normalized, FGVec2::lit("1", "0"));
|
||||
assert!(normalized.is_normalized());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn measure_normalize_error_bound() {
|
||||
let mut max_abs_diff_bits: i32 = 0;
|
||||
let mut worst_case: Option<(f64, f64, i32)> = None;
|
||||
|
||||
let angle_steps = 3600; // 0.1 degree resolution
|
||||
let magnitudes: &[f64] = &[
|
||||
0.001, 0.01, 0.1, 0.5, 1.0, 2.0, 10.0, 100.0, 1000.0, 30000.0,
|
||||
];
|
||||
|
||||
for &mag in magnitudes {
|
||||
for i in 0..angle_steps {
|
||||
let theta = (i as f64) * std::f64::consts::TAU / (angle_steps as f64);
|
||||
let x = mag * theta.cos();
|
||||
let y = mag * theta.sin();
|
||||
|
||||
let Some(fx) = FGi32::checked_from_num(x) else { continue };
|
||||
let Some(fy) = FGi32::checked_from_num(y) else { continue };
|
||||
|
||||
let v = FGVec2 { x: fx, y: fy };
|
||||
|
||||
if let Some(normalized) = v.normalize() {
|
||||
let ls = normalized.length_squared();
|
||||
let diff_bits = ls.to_bits() - FGi32::ONE.to_bits();
|
||||
let abs_diff_bits = diff_bits.abs();
|
||||
|
||||
if abs_diff_bits > max_abs_diff_bits {
|
||||
max_abs_diff_bits = abs_diff_bits;
|
||||
worst_case = Some((x, y, abs_diff_bits));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ulp_value = FGi32::DELTA.to_num::<f64>();
|
||||
println!(
|
||||
"Max |length_squared - 1.0| = {} raw bits ({:e} real), worst case: {:?}",
|
||||
max_abs_diff_bits,
|
||||
max_abs_diff_bits as f64 * ulp_value,
|
||||
worst_case
|
||||
);
|
||||
|
||||
assert!(
|
||||
max_abs_diff_bits <= EPS_FIXED_BITS,
|
||||
"measured error ({} bits) exceeds analytical bound ({} bits) — \
|
||||
revisit the epsilon derivation",
|
||||
max_abs_diff_bits,
|
||||
EPS_FIXED_BITS
|
||||
);
|
||||
|
||||
println!(
|
||||
"Bound: {} bits, measured: {} bits (headroom: {} bits)",
|
||||
EPS_FIXED_BITS,
|
||||
max_abs_diff_bits,
|
||||
EPS_FIXED_BITS - max_abs_diff_bits
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_normalized_behaves_correctly() {
|
||||
let angle_steps = 360;
|
||||
for i in 0..angle_steps {
|
||||
let theta = (i as f64) * std::f64::consts::TAU / (angle_steps as f64);
|
||||
for &mag in &[0.01, 1.0, 100.0, 10000.0] {
|
||||
let x = mag * theta.cos();
|
||||
let y = mag * theta.sin();
|
||||
let Some(fx) = FGi32::checked_from_num(x) else { continue };
|
||||
let Some(fy) = FGi32::checked_from_num(y) else { continue };
|
||||
let v = FGVec2 { x: fx, y: fy };
|
||||
|
||||
if let Some(normalized) = v.normalize() {
|
||||
assert!(
|
||||
normalized.is_normalized(),
|
||||
"normalized({}, {}) failed is_normalized(): length_squared = {:?}",
|
||||
x, y, normalized.length_squared()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let non_unit = FGVec2 { x: FGi32::from_num(2.0), y: FGi32::from_num(0.0) };
|
||||
assert!(!non_unit.is_normalized());
|
||||
|
||||
let non_unit_small = FGVec2 { x: FGi32::from_num(0.5), y: FGi32::from_num(0.0) };
|
||||
assert!(!non_unit_small.is_normalized());
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,11 +5,11 @@ use crate::stage::{StagePoly, line::StageCollision};
|
||||
pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>) {
|
||||
for poly in stage_collision.stage_polys.iter() {
|
||||
for segment in poly.segments.iter() {
|
||||
let from = segment.segment.point1();
|
||||
let to = segment.segment.point2();
|
||||
let from = segment.segment.point1().to_vec2();
|
||||
let to = segment.segment.point2().to_vec2();
|
||||
let from_3d = Vec3::new(from.x, from.y, 0.0);
|
||||
let end_3d = Vec3::new(to.x, to.y, 0.0);
|
||||
let normal_3d = Vec3::new(segment.normal.x, segment.normal.y, 0.0);
|
||||
let normal_3d = Vec3::new(segment.normal.x.to_num(), segment.normal.y.to_num(), 0.0);
|
||||
gizmos.line(from_3d, end_3d, RED);
|
||||
let midpoint = (from_3d + end_3d) * 0.5;
|
||||
gizmos.arrow(midpoint, midpoint + normal_3d, RED);
|
||||
|
||||
+141
-37
@@ -4,7 +4,7 @@ use std::default;
|
||||
|
||||
use bevy::{math::InvalidDirectionError, prelude::*};
|
||||
|
||||
use crate::math::segment::SegmentIntersection;
|
||||
use crate::math::{segment::{FGSegment2d}, vec::FGVec2};
|
||||
|
||||
#[derive(Reflect, Clone, Copy, PartialEq)]
|
||||
pub enum StagePolyLineSegmentType {
|
||||
@@ -14,8 +14,8 @@ pub enum StagePolyLineSegmentType {
|
||||
|
||||
#[derive(Reflect, Clone)]
|
||||
pub struct StagePolyLineSegment {
|
||||
pub segment: Segment2d,
|
||||
pub normal: Vec2,
|
||||
pub segment: FGSegment2d,
|
||||
pub normal: FGVec2,
|
||||
pub segment_type: StagePolyLineSegmentType
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ pub struct StagePoly {
|
||||
}
|
||||
|
||||
pub struct StagePlaneIntersectResult {
|
||||
pub position: Vec2,
|
||||
pub position: FGVec2,
|
||||
pub segment_idx: usize,
|
||||
pub normal: Vec2
|
||||
pub normal: FGVec2
|
||||
}
|
||||
|
||||
#[derive(Resource, Reflect, Clone, Default)]
|
||||
@@ -52,18 +52,30 @@ pub struct StageCollision {
|
||||
}
|
||||
|
||||
impl StagePoly {
|
||||
pub fn intersect_ray(&self, ray_segment: Segment2d) -> Option<StagePlaneIntersectResult> {
|
||||
for (i, segment) in self.segments.iter().enumerate() {
|
||||
if let Some(intersection) = segment.segment.segment_intersection(&ray_segment) {
|
||||
println!("DIST3 {} {}", intersection, segment.segment.closest_point(intersection));
|
||||
return Some(StagePlaneIntersectResult {
|
||||
position: intersection,
|
||||
segment_idx: i,
|
||||
normal: segment.normal
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
pub fn intersect_ray(&self, ray_segment: FGSegment2d) -> Option<StagePlaneIntersectResult> {
|
||||
let ray_start = ray_segment.point1();
|
||||
let (segment_idx, segment, position) = self
|
||||
.segments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(segment_idx, segment)| {
|
||||
segment
|
||||
.segment
|
||||
.segment_intersection(&ray_segment)
|
||||
.map(|position| (segment_idx, segment, position))
|
||||
})
|
||||
.min_by_key(|(_, _, position)| {
|
||||
let offset = *position - ray_start;
|
||||
let x = i128::from(offset.x.to_bits());
|
||||
let y = i128::from(offset.y.to_bits());
|
||||
x * x + y * y
|
||||
})?;
|
||||
|
||||
Some(StagePlaneIntersectResult {
|
||||
position,
|
||||
segment_idx,
|
||||
normal: segment.normal,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_segment(&self, segment_idx: usize) -> Option<&StagePolyLineSegment> {
|
||||
@@ -71,24 +83,65 @@ impl StagePoly {
|
||||
}
|
||||
|
||||
pub fn get_next_segment(&self, segment_idx: usize) -> Option<&StagePolyLineSegment> {
|
||||
self.segments.get(segment_idx % segment_idx + 1)
|
||||
self.get_next_segment_index(segment_idx)
|
||||
.and_then(|index| self.segments.get(index))
|
||||
}
|
||||
|
||||
pub fn get_prev_segment(&self, segment_idx: usize) -> Option<&StagePolyLineSegment> {
|
||||
self.segments.get(segment_idx % segment_idx + 1)
|
||||
self.get_prev_segment_index(segment_idx)
|
||||
.and_then(|index| self.segments.get(index))
|
||||
}
|
||||
|
||||
pub fn build(poly_type: StagePolyType, points: &[(Vec2, StagePolyLineSegmentType)]) -> Self {
|
||||
let windows = points.iter().zip(points.iter().cycle().skip(1));
|
||||
pub fn get_next_segment_index(&self, segment_idx: usize) -> Option<usize> {
|
||||
if segment_idx >= self.segments.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let next = segment_idx + 1;
|
||||
if next < self.segments.len() {
|
||||
Some(next)
|
||||
} else if matches!(&self.poly_type, StagePolyType::Closed) {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_prev_segment_index(&self, segment_idx: usize) -> Option<usize> {
|
||||
if segment_idx >= self.segments.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if segment_idx > 0 {
|
||||
Some(segment_idx - 1)
|
||||
} else if matches!(&self.poly_type, StagePolyType::Closed) {
|
||||
self.segments.len().checked_sub(1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(poly_type: StagePolyType, points: &[(FGVec2, StagePolyLineSegmentType)]) -> Self {
|
||||
let segment_count = if points.len() < 2 {
|
||||
0
|
||||
} else if matches!(&poly_type, StagePolyType::Closed) {
|
||||
points.len()
|
||||
} else {
|
||||
points.len() - 1
|
||||
};
|
||||
let windows = points
|
||||
.iter()
|
||||
.zip(points.iter().cycle().skip(1))
|
||||
.take(segment_count);
|
||||
|
||||
let segments = windows.map(|segment_window| {
|
||||
let (first_point, segment_type) = segment_window.0;
|
||||
let (second_point, _) = segment_window.1;
|
||||
let segment = Segment2d::new(*first_point, *second_point);
|
||||
let segment = FGSegment2d::new(*first_point, *second_point);
|
||||
StagePolyLineSegment {
|
||||
segment_type: *segment_type,
|
||||
segment,
|
||||
normal: segment.left_normal().as_vec2()
|
||||
normal: segment.left_normal()
|
||||
}
|
||||
}).collect();
|
||||
|
||||
@@ -100,46 +153,97 @@ impl StagePoly {
|
||||
}
|
||||
|
||||
impl StagePolyLineSegment {
|
||||
pub fn project(&self, vec: Vec2) -> Vec2 {
|
||||
pub fn project(&self, vec: FGVec2) -> FGVec2 {
|
||||
vec - vec.project_onto_normalized(self.normal)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::math::int::FGi32;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn stage_segment(point1: FGVec2, point2: FGVec2) -> StagePolyLineSegment {
|
||||
let segment = FGSegment2d::new(point1, point2);
|
||||
StagePolyLineSegment {
|
||||
segment,
|
||||
normal: segment.left_normal(),
|
||||
segment_type: StagePolyLineSegmentType::Floor,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_is_open_and_closed_poly_wraps() {
|
||||
let points = [
|
||||
(FGVec2::lit("0", "0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("1", "0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("2", "0"), StagePolyLineSegmentType::Floor),
|
||||
];
|
||||
let platform = StagePoly::build(StagePolyType::Platform, &points);
|
||||
let closed = StagePoly::build(StagePolyType::Closed, &points);
|
||||
|
||||
assert_eq!(platform.segments.len(), 2);
|
||||
assert_eq!(platform.get_next_segment_index(0), Some(1));
|
||||
assert_eq!(platform.get_next_segment_index(1), None);
|
||||
assert_eq!(platform.get_prev_segment_index(0), None);
|
||||
assert_eq!(platform.get_prev_segment_index(1), Some(0));
|
||||
|
||||
assert_eq!(closed.segments.len(), 3);
|
||||
assert_eq!(closed.get_next_segment_index(2), Some(0));
|
||||
assert_eq!(closed.get_prev_segment_index(0), Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_intersection_returns_nearest_hit() {
|
||||
let stage_poly = StagePoly {
|
||||
poly_type: StagePolyType::Platform,
|
||||
segments: vec![
|
||||
stage_segment(FGVec2::lit("-1", "0"), FGVec2::lit("1", "0")),
|
||||
stage_segment(FGVec2::lit("-1", "1"), FGVec2::lit("1", "1")),
|
||||
],
|
||||
};
|
||||
|
||||
let hit = stage_poly
|
||||
.intersect_ray(FGSegment2d::new(FGVec2::lit("0", "2"), FGVec2::lit("0", "-1")))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hit.segment_idx, 1);
|
||||
assert_eq!(hit.position, FGVec2::lit("0", "1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_intersection_test() {
|
||||
let stage_poly = StagePoly::build(crate::stage::line::StagePolyType::Platform, &[
|
||||
(Vec2::new(-4.5, 0.0), StagePolyLineSegmentType::Floor),
|
||||
(Vec2::new(4.5, 0.0), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("-4.5", "0.0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("4.5", "0.0"), StagePolyLineSegmentType::Floor),
|
||||
]);
|
||||
|
||||
let res = stage_poly.intersect_ray(Segment2d::new(Vec2::new(0.0, 1.0), Vec2::new(0.0, -1.0)));
|
||||
let res = stage_poly.intersect_ray(FGSegment2d::new(FGVec2::lit("0.0", "1.0"), FGVec2::lit("0.0", "-1.0")));
|
||||
assert!(res.is_some());
|
||||
assert_eq!(res.unwrap().position, Vec2::ZERO);
|
||||
assert_eq!(res.unwrap().position, FGVec2::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_intersection_test_side() {
|
||||
let stage_poly = StagePoly::build(crate::stage::line::StagePolyType::Platform, &[
|
||||
(Vec2::new(-4.5, 0.0), StagePolyLineSegmentType::Floor),
|
||||
(Vec2::new(4.5, 0.0), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("-4.5", "0.0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("4.5", "0.0"), StagePolyLineSegmentType::Floor),
|
||||
]);
|
||||
|
||||
let res = stage_poly.intersect_ray(Segment2d::new(Vec2::new(2.0, 1.0), Vec2::new(2.0, -1.0)));
|
||||
let res = stage_poly.intersect_ray(FGSegment2d::new(FGVec2::lit("2.0", "1.0"), FGVec2::lit("2.0", "-1.0")));
|
||||
assert!(res.is_some());
|
||||
assert_eq!(res.unwrap().position, Vec2::new(2.0, 0.0));
|
||||
assert!(res.unwrap().position.distance(FGVec2::lit("2.0", "0.0")) < FGi32::from_bits(4));
|
||||
}
|
||||
#[test]
|
||||
fn ray_intersection_test_diagonal() {
|
||||
let stage_poly = StagePoly::build(crate::stage::line::StagePolyType::Platform, &[
|
||||
(Vec2::new(0.0, 0.0), StagePolyLineSegmentType::Floor),
|
||||
(Vec2::new(2.0, 2.0), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("0.0", "0.0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("2.0", "2.0"), StagePolyLineSegmentType::Floor),
|
||||
]);
|
||||
|
||||
let res = stage_poly.intersect_ray(Segment2d::new(Vec2::new(1.0, 2.0), Vec2::new(1.0, -2.0)));
|
||||
let res = stage_poly.intersect_ray(FGSegment2d::new(FGVec2::lit("1.0", "2.0"), FGVec2::lit("1.0", "-2.0")));
|
||||
assert!(res.is_some());
|
||||
assert_eq!(res.unwrap().position, Vec2::new(1.0, 1.0));
|
||||
assert_eq!(res.unwrap().position, FGVec2::lit("1.0", "1.0"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -4,19 +4,19 @@ use std::f32::consts::PI;
|
||||
|
||||
use bevy::{camera_controller::free_camera::FreeCamera, prelude::*};
|
||||
|
||||
use crate::stage::{StagePoly, line::StagePolyLineSegmentType, line::StageCollision};
|
||||
use crate::{math::vec::FGVec2, stage::{StagePoly, line::{StageCollision, StagePolyLineSegmentType}}};
|
||||
|
||||
/// set up a simple 3D scene
|
||||
pub fn test_scene() -> impl SceneList {
|
||||
|
||||
let stage_poly = StagePoly::build(crate::stage::line::StagePolyType::Closed, &[
|
||||
(Vec2::new(-5.6, -0.35), StagePolyLineSegmentType::Floor),
|
||||
(Vec2::new(-3.92, 0.0), StagePolyLineSegmentType::Floor),
|
||||
(Vec2::new(0.0, 0.0), StagePolyLineSegmentType::Floor),
|
||||
(Vec2::new(3.92, 0.0), StagePolyLineSegmentType::Floor),
|
||||
(Vec2::new(5.6, -0.35), StagePolyLineSegmentType::Wall),
|
||||
(Vec2::new(5.6, -20.0), StagePolyLineSegmentType::Ceiling),
|
||||
(Vec2::new(-5.6, -20.0), StagePolyLineSegmentType::Wall)
|
||||
(FGVec2::lit("-5.6", "-0.35"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("-3.92", "0.0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("0.0", "0.0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("3.92", "0.0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("5.6", "-0.35"), StagePolyLineSegmentType::Wall),
|
||||
(FGVec2::lit("5.6", "-20.0"), StagePolyLineSegmentType::Ceiling),
|
||||
(FGVec2::lit("-5.6", "-20.0"), StagePolyLineSegmentType::Wall)
|
||||
]);
|
||||
|
||||
bsn_list! [
|
||||
|
||||
Reference in New Issue
Block a user