Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11fc7ba704 |
@@ -109,6 +109,7 @@ impl FighterDebugRowKind {
|
|||||||
enum DebugFlag {
|
enum DebugFlag {
|
||||||
Network,
|
Network,
|
||||||
Ecb,
|
Ecb,
|
||||||
|
BlastZones,
|
||||||
StageCollision,
|
StageCollision,
|
||||||
AnimationBones,
|
AnimationBones,
|
||||||
AttackHitboxes,
|
AttackHitboxes,
|
||||||
@@ -150,6 +151,10 @@ const FIGHTER_MENU: &[DebugMenuEntry] = &[
|
|||||||
label: "ECB",
|
label: "ECB",
|
||||||
flag: DebugFlag::Ecb,
|
flag: DebugFlag::Ecb,
|
||||||
},
|
},
|
||||||
|
DebugMenuEntry::Toggle {
|
||||||
|
label: "Blast Zones",
|
||||||
|
flag: DebugFlag::BlastZones,
|
||||||
|
},
|
||||||
DebugMenuEntry::Toggle {
|
DebugMenuEntry::Toggle {
|
||||||
label: "Animation bones",
|
label: "Animation bones",
|
||||||
flag: DebugFlag::AnimationBones,
|
flag: DebugFlag::AnimationBones,
|
||||||
@@ -243,6 +248,7 @@ pub struct DebugSettings {
|
|||||||
pub frame_stepping: bool,
|
pub frame_stepping: bool,
|
||||||
pub network: bool,
|
pub network: bool,
|
||||||
pub ecb: bool,
|
pub ecb: bool,
|
||||||
|
pub blast_zones: bool,
|
||||||
pub stage_collision: bool,
|
pub stage_collision: bool,
|
||||||
pub animation_bones: bool,
|
pub animation_bones: bool,
|
||||||
pub attack_hitboxes: bool,
|
pub attack_hitboxes: bool,
|
||||||
@@ -264,6 +270,7 @@ impl Default for DebugSettings {
|
|||||||
frame_stepping: false,
|
frame_stepping: false,
|
||||||
network: false,
|
network: false,
|
||||||
ecb: false,
|
ecb: false,
|
||||||
|
blast_zones: true,
|
||||||
stage_collision: false,
|
stage_collision: false,
|
||||||
animation_bones: false,
|
animation_bones: false,
|
||||||
attack_hitboxes: false,
|
attack_hitboxes: false,
|
||||||
@@ -328,6 +335,7 @@ impl DebugSettings {
|
|||||||
match flag {
|
match flag {
|
||||||
DebugFlag::Network => self.network,
|
DebugFlag::Network => self.network,
|
||||||
DebugFlag::Ecb => self.ecb,
|
DebugFlag::Ecb => self.ecb,
|
||||||
|
DebugFlag::BlastZones => self.blast_zones,
|
||||||
DebugFlag::StageCollision => self.stage_collision,
|
DebugFlag::StageCollision => self.stage_collision,
|
||||||
DebugFlag::AnimationBones => self.animation_bones,
|
DebugFlag::AnimationBones => self.animation_bones,
|
||||||
DebugFlag::AttackHitboxes => self.attack_hitboxes,
|
DebugFlag::AttackHitboxes => self.attack_hitboxes,
|
||||||
@@ -342,6 +350,7 @@ impl DebugSettings {
|
|||||||
match flag {
|
match flag {
|
||||||
DebugFlag::Network => self.network = !self.network,
|
DebugFlag::Network => self.network = !self.network,
|
||||||
DebugFlag::Ecb => self.ecb = !self.ecb,
|
DebugFlag::Ecb => self.ecb = !self.ecb,
|
||||||
|
DebugFlag::BlastZones => self.blast_zones = !self.blast_zones,
|
||||||
DebugFlag::StageCollision => self.stage_collision = !self.stage_collision,
|
DebugFlag::StageCollision => self.stage_collision = !self.stage_collision,
|
||||||
DebugFlag::AnimationBones => self.animation_bones = !self.animation_bones,
|
DebugFlag::AnimationBones => self.animation_bones = !self.animation_bones,
|
||||||
DebugFlag::AttackHitboxes => self.attack_hitboxes = !self.attack_hitboxes,
|
DebugFlag::AttackHitboxes => self.attack_hitboxes = !self.attack_hitboxes,
|
||||||
@@ -884,6 +893,10 @@ pub fn stage_collision_enabled(settings: Res<DebugSettings>) -> bool {
|
|||||||
settings.stage_collision
|
settings.stage_collision
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn blast_zones_enabled(settings: Res<DebugSettings>) -> bool {
|
||||||
|
settings.blast_zones
|
||||||
|
}
|
||||||
|
|
||||||
pub fn animation_bones_enabled(settings: Res<DebugSettings>) -> bool {
|
pub fn animation_bones_enabled(settings: Res<DebugSettings>) -> bool {
|
||||||
settings.animation_bones
|
settings.animation_bones
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-41
@@ -1,9 +1,10 @@
|
|||||||
//! Fighter debug views: gizmos and the egui inspector window.
|
//! Fighter debug views: gizmos and the egui inspector window.
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use bevy::color::palettes::css::{ORANGE, ORANGE_RED, WHITE};
|
use bevy::color::palettes::css::ORANGE;
|
||||||
use bevy::prelude::*;
|
use bevy::{color::palettes::css::YELLOW, prelude::*};
|
||||||
use bevy_egui::{egui, EguiContexts};
|
use bevy_egui::{EguiContexts, egui};
|
||||||
|
|
||||||
use crate::debug_tools::{DebugSettings, FighterDebugRowKind};
|
use crate::debug_tools::{DebugSettings, FighterDebugRowKind};
|
||||||
use crate::fighter::animation::FighterAnimationFrame;
|
use crate::fighter::animation::FighterAnimationFrame;
|
||||||
@@ -14,14 +15,14 @@ use crate::fighter::attack::{
|
|||||||
use crate::fighter::baked_animation::{BakedFighterAnimations, FighterBoneMatrices};
|
use crate::fighter::baked_animation::{BakedFighterAnimations, FighterBoneMatrices};
|
||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
use crate::fighter::errors::FighterErrors;
|
use crate::fighter::errors::FighterErrors;
|
||||||
|
use crate::fighter::hurtbox::FixedAffineCapsule;
|
||||||
use crate::fighter::manifest::FighterManifest;
|
use crate::fighter::manifest::FighterManifest;
|
||||||
use crate::fighter::state::{air, FighterState};
|
use crate::fighter::state::{FighterState, air};
|
||||||
use crate::fighter::visual::FighterAnimations;
|
use crate::fighter::visual::FighterAnimations;
|
||||||
use crate::fighter::{
|
use crate::fighter::{
|
||||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
|
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
|
||||||
FighterTranslation, FighterVelocity,
|
FighterTranslation, FighterVelocity,
|
||||||
};
|
};
|
||||||
use crate::filled_gizmos::FilledGizmos;
|
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
use crate::math::int::FGi32;
|
use crate::math::int::FGi32;
|
||||||
@@ -37,7 +38,6 @@ pub fn debug_draw_ecb(
|
|||||||
&FighterPreviousTranslation,
|
&FighterPreviousTranslation,
|
||||||
)>,
|
)>,
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
mut filled_gizmos: FilledGizmos,
|
|
||||||
) {
|
) {
|
||||||
for (ecb, trf, ecb_prev, trf_prev) in ecbs {
|
for (ecb, trf, ecb_prev, trf_prev) in ecbs {
|
||||||
gizmos.cross(
|
gizmos.cross(
|
||||||
@@ -46,18 +46,15 @@ pub fn debug_draw_ecb(
|
|||||||
bevy::color::palettes::css::BLUE,
|
bevy::color::palettes::css::BLUE,
|
||||||
);
|
);
|
||||||
|
|
||||||
let prev_transformed = ecb_prev.to_3d_lineloop(Transform::from_xyz(
|
|
||||||
trf_prev.x.to_num::<f32>(),
|
|
||||||
trf_prev.y.to_num(),
|
|
||||||
0.0,
|
|
||||||
));
|
|
||||||
filled_gizmos.polygon(&prev_transformed, ORANGE_RED);
|
|
||||||
|
|
||||||
let transformed =
|
let transformed =
|
||||||
ecb.to_3d_lineloop(Transform::from_xyz(trf.x.to_num(), trf.y.to_num(), 0.0));
|
ecb.to_3d_lineloop(Transform::from_xyz(trf.x.to_num(), trf.y.to_num(), 0.0));
|
||||||
filled_gizmos.polygon(&transformed, ORANGE);
|
gizmos.lineloop(transformed, YELLOW);
|
||||||
gizmos.lineloop(transformed, WHITE);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn fighter_debug(
|
pub fn fighter_debug(
|
||||||
@@ -186,23 +183,15 @@ pub fn animation_debug(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hurtbox_debug(
|
pub fn hurtbox_debug(query: Query<&FighterSolvedHurtboxes>, mut gizmos: Gizmos) {
|
||||||
query: Query<&FighterSolvedHurtboxes>,
|
|
||||||
mut filled_gizmos: FilledGizmos,
|
|
||||||
) {
|
|
||||||
for hurtboxes in &query {
|
for hurtboxes in &query {
|
||||||
for hurtbox in &hurtboxes.0 {
|
for hurtbox in &hurtboxes.0 {
|
||||||
filled_gizmos.affine_capsule(
|
draw_affine_capsule(&mut gizmos, hurtbox.capsule, YELLOW);
|
||||||
hurtbox.capsule.transform.to_mat4(),
|
|
||||||
hurtbox.capsule.radius.to_num(),
|
|
||||||
hurtbox.capsule.half_length.to_num(),
|
|
||||||
Color::srgba(1.0, 1.0, 0.0, 0.75),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*fn draw_affine_capsule(
|
fn draw_affine_capsule(
|
||||||
gizmos: &mut Gizmos,
|
gizmos: &mut Gizmos,
|
||||||
capsule: FixedAffineCapsule,
|
capsule: FixedAffineCapsule,
|
||||||
color: impl Into<Color> + Copy,
|
color: impl Into<Color> + Copy,
|
||||||
@@ -224,9 +213,9 @@ pub fn hurtbox_debug(
|
|||||||
for &(start, end) in &wireframe.lines {
|
for &(start, end) in &wireframe.lines {
|
||||||
gizmos.line(vertices[start], vertices[end], color);
|
gizmos.line(vertices[start], vertices[end], color);
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
/*struct CapsuleWireVertex {
|
struct CapsuleWireVertex {
|
||||||
direction: Vec3,
|
direction: Vec3,
|
||||||
center_sign: f32,
|
center_sign: f32,
|
||||||
}
|
}
|
||||||
@@ -234,9 +223,9 @@ pub fn hurtbox_debug(
|
|||||||
struct CapsuleWireframe {
|
struct CapsuleWireframe {
|
||||||
vertices: Vec<CapsuleWireVertex>,
|
vertices: Vec<CapsuleWireVertex>,
|
||||||
lines: Vec<(usize, usize)>,
|
lines: Vec<(usize, usize)>,
|
||||||
}*/
|
}
|
||||||
|
|
||||||
/*fn capsule_wireframe() -> &'static CapsuleWireframe {
|
fn capsule_wireframe() -> &'static CapsuleWireframe {
|
||||||
const RESOLUTION: usize = 16;
|
const RESOLUTION: usize = 16;
|
||||||
const HEMISPHERE_STEPS: usize = 4;
|
const HEMISPHERE_STEPS: usize = 4;
|
||||||
static WIREFRAME: OnceLock<CapsuleWireframe> = OnceLock::new();
|
static WIREFRAME: OnceLock<CapsuleWireframe> = OnceLock::new();
|
||||||
@@ -317,7 +306,7 @@ struct CapsuleWireframe {
|
|||||||
|
|
||||||
CapsuleWireframe { vertices, lines }
|
CapsuleWireframe { vertices, lines }
|
||||||
})
|
})
|
||||||
}*/
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod capsule_debug_tests {
|
mod capsule_debug_tests {
|
||||||
@@ -337,7 +326,7 @@ mod capsule_debug_tests {
|
|||||||
assert!(marker.abs_diff_eq(Vec3::new(3.0, 4.0, 3.0), 1e-5));
|
assert!(marker.abs_diff_eq(Vec3::new(3.0, 4.0, 3.0), 1e-5));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn capsule_wireframe_caps_extend_in_opposite_directions() {
|
fn capsule_wireframe_caps_extend_in_opposite_directions() {
|
||||||
let wireframe = capsule_wireframe();
|
let wireframe = capsule_wireframe();
|
||||||
let lower: Vec<_> = wireframe
|
let lower: Vec<_> = wireframe
|
||||||
@@ -355,7 +344,7 @@ mod capsule_debug_tests {
|
|||||||
assert!(upper.iter().all(|vertex| vertex.direction.y >= 0.0));
|
assert!(upper.iter().all(|vertex| vertex.direction.y >= 0.0));
|
||||||
assert!(lower.iter().any(|vertex| vertex.direction == Vec3::NEG_Y));
|
assert!(lower.iter().any(|vertex| vertex.direction == Vec3::NEG_Y));
|
||||||
assert!(upper.iter().any(|vertex| vertex.direction == Vec3::Y));
|
assert!(upper.iter().any(|vertex| vertex.direction == Vec3::Y));
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attack_debug(
|
pub fn attack_debug(
|
||||||
@@ -367,7 +356,6 @@ pub fn attack_debug(
|
|||||||
&FighterAnimationFrame,
|
&FighterAnimationFrame,
|
||||||
)>,
|
)>,
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
mut filled_gizmos: FilledGizmos,
|
|
||||||
) {
|
) {
|
||||||
for (hitboxes, _matrices, _translation, _facing_direction, _frame) in query {
|
for (hitboxes, _matrices, _translation, _facing_direction, _frame) in query {
|
||||||
if let Some(active_attack) = &hitboxes.0 {
|
if let Some(active_attack) = &hitboxes.0 {
|
||||||
@@ -394,12 +382,6 @@ pub fn attack_debug(
|
|||||||
capsule_draw_trf.to_isometry(),
|
capsule_draw_trf.to_isometry(),
|
||||||
bevy::color::palettes::css::RED,
|
bevy::color::palettes::css::RED,
|
||||||
);
|
);
|
||||||
filled_gizmos.capsule(
|
|
||||||
capsule_draw_trf.to_isometry(),
|
|
||||||
solved_hitbox.capsule.radius.to_num(),
|
|
||||||
length * 0.5,
|
|
||||||
Color::srgba(1.0, 0.0, 0.0, 0.18),
|
|
||||||
);
|
|
||||||
|
|
||||||
gizmos.cross(start, 1.0, bevy::color::palettes::css::GREEN);
|
gizmos.cross(start, 1.0, bevy::color::palettes::css::GREEN);
|
||||||
gizmos.cross(end, 1.0, bevy::color::palettes::css::GREEN);
|
gizmos.cross(end, 1.0, bevy::color::palettes::css::GREEN);
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip}
|
|
||||||
|
|
||||||
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> material_color: vec4<f32>;
|
|
||||||
|
|
||||||
struct Vertex {
|
|
||||||
@builtin(instance_index) instance_index: u32,
|
|
||||||
@location(0) position: vec3<f32>,
|
|
||||||
@location(1) color: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
|
||||||
@location(0) color: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
|
||||||
var output: VertexOutput;
|
|
||||||
output.clip_position = mesh_position_local_to_clip(
|
|
||||||
get_world_from_local(vertex.instance_index),
|
|
||||||
vec4<f32>(vertex.position, 1.0),
|
|
||||||
);
|
|
||||||
output.color = vertex.color;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
return material_color * input.color;
|
|
||||||
}
|
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
//! Immediate-mode filled debug geometry.
|
|
||||||
//!
|
|
||||||
//! Unlike Bevy's line-only `Gizmos`, this collects triangles into one dynamic
|
|
||||||
//! mesh. It is intended for small, transient debug overlays rather than game
|
|
||||||
//! geometry.
|
|
||||||
|
|
||||||
use std::f32::consts::{FRAC_PI_2, PI, TAU};
|
|
||||||
|
|
||||||
use bevy::{
|
|
||||||
asset::{load_internal_asset, uuid_handle, RenderAssetUsages},
|
|
||||||
camera::visibility::NoFrustumCulling,
|
|
||||||
ecs::system::SystemParam,
|
|
||||||
mesh::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology},
|
|
||||||
pbr::{MaterialPipeline, MaterialPipelineKey},
|
|
||||||
prelude::*,
|
|
||||||
render::render_resource::{
|
|
||||||
AsBindGroup, RenderPipelineDescriptor, SpecializedMeshPipelineError,
|
|
||||||
},
|
|
||||||
shader::ShaderRef,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SPHERE_SECTORS: usize = 16;
|
|
||||||
const SPHERE_STACKS: usize = 8;
|
|
||||||
const CAPSULE_SECTORS: usize = 16;
|
|
||||||
const CAPSULE_HEMISPHERE_STEPS: usize = 4;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct FilledGizmoGeometry {
|
|
||||||
positions: Vec<[f32; 3]>,
|
|
||||||
normals: Vec<[f32; 3]>,
|
|
||||||
colors: Vec<[f32; 4]>,
|
|
||||||
indices: Vec<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
|
||||||
struct FilledGizmoBuffer {
|
|
||||||
/// Geometry requested by the current schedule invocation.
|
|
||||||
pending: FilledGizmoGeometry,
|
|
||||||
/// The last completed fixed-timestep geometry. This is retained between
|
|
||||||
/// render frames, just like Bevy's fixed-timestep gizmos.
|
|
||||||
retained: FilledGizmoGeometry,
|
|
||||||
mesh: Option<Handle<Mesh>>,
|
|
||||||
fixed_commit: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Immediate-mode API for drawing filled debug shapes.
|
|
||||||
#[derive(SystemParam)]
|
|
||||||
pub struct FilledGizmos<'w> {
|
|
||||||
buffer: ResMut<'w, FilledGizmoBuffer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FilledGizmos<'_> {
|
|
||||||
/// Draw a filled planar polygon. Vertices must be ordered around the
|
|
||||||
/// perimeter and the polygon must be convex.
|
|
||||||
pub fn polygon(&mut self, vertices: &[Vec3], color: impl Into<Color> + Copy) {
|
|
||||||
if vertices.len() < 3 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let color = color.into().to_linear().to_f32_array();
|
|
||||||
let normal = (vertices[1] - vertices[0])
|
|
||||||
.cross(vertices[2] - vertices[0])
|
|
||||||
.normalize_or_zero();
|
|
||||||
let base = self.buffer.pending.positions.len() as u32;
|
|
||||||
for &vertex in vertices {
|
|
||||||
self.push_vertex(vertex, normal, color);
|
|
||||||
}
|
|
||||||
for index in 2..vertices.len() as u32 {
|
|
||||||
self.buffer
|
|
||||||
.pending
|
|
||||||
.indices
|
|
||||||
.extend([base, base + index - 1, base + index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sphere(&mut self, isometry: Isometry3d, radius: f32, color: impl Into<Color> + Copy) {
|
|
||||||
if radius <= 0.0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let color = color.into().to_linear().to_f32_array();
|
|
||||||
let base = self.buffer.pending.positions.len() as u32;
|
|
||||||
for stack in 0..=SPHERE_STACKS {
|
|
||||||
let latitude = PI * 0.5 - stack as f32 * PI / SPHERE_STACKS as f32;
|
|
||||||
let (sin_lat, cos_lat) = latitude.sin_cos();
|
|
||||||
for sector in 0..=SPHERE_SECTORS {
|
|
||||||
let longitude = sector as f32 * TAU / SPHERE_SECTORS as f32;
|
|
||||||
let (sin_lon, cos_lon) = longitude.sin_cos();
|
|
||||||
let normal = Vec3::new(cos_lat * cos_lon, sin_lat, cos_lat * sin_lon);
|
|
||||||
self.push_vertex(
|
|
||||||
isometry.transform_point(normal * radius).into(),
|
|
||||||
isometry.rotation * normal,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let stride = SPHERE_SECTORS + 1;
|
|
||||||
for stack in 0..SPHERE_STACKS {
|
|
||||||
for sector in 0..SPHERE_SECTORS {
|
|
||||||
let a = base + (stack * stride + sector) as u32;
|
|
||||||
let b = a + stride as u32;
|
|
||||||
self.buffer
|
|
||||||
.pending
|
|
||||||
.indices
|
|
||||||
.extend([a, b, a + 1, a + 1, b, b + 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn capsule(
|
|
||||||
&mut self,
|
|
||||||
isometry: Isometry3d,
|
|
||||||
radius: f32,
|
|
||||||
half_length: f32,
|
|
||||||
color: impl Into<Color> + Copy,
|
|
||||||
) {
|
|
||||||
let transform = Mat4::from_scale_rotation_translation(
|
|
||||||
Vec3::ONE,
|
|
||||||
isometry.rotation,
|
|
||||||
isometry.translation.into(),
|
|
||||||
);
|
|
||||||
self.affine_capsule(transform, radius, half_length, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw a capsule transformed by an arbitrary affine transform.
|
|
||||||
pub fn affine_capsule(
|
|
||||||
&mut self,
|
|
||||||
transform: Mat4,
|
|
||||||
radius: f32,
|
|
||||||
half_length: f32,
|
|
||||||
color: impl Into<Color> + Copy,
|
|
||||||
) {
|
|
||||||
if radius <= 0.0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let color = color.into().to_linear().to_f32_array();
|
|
||||||
let base = self.buffer.pending.positions.len() as u32;
|
|
||||||
let mut rings = Vec::new();
|
|
||||||
for step in 1..=CAPSULE_HEMISPHERE_STEPS {
|
|
||||||
let angle = -FRAC_PI_2 + step as f32 * FRAC_PI_2 / CAPSULE_HEMISPHERE_STEPS as f32;
|
|
||||||
rings.push((
|
|
||||||
radius * angle.cos(),
|
|
||||||
-half_length + radius * angle.sin(),
|
|
||||||
angle.sin(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
rings.push((radius, half_length, 0.0));
|
|
||||||
for step in 1..CAPSULE_HEMISPHERE_STEPS {
|
|
||||||
let angle = step as f32 * FRAC_PI_2 / CAPSULE_HEMISPHERE_STEPS as f32;
|
|
||||||
rings.push((
|
|
||||||
radius * angle.cos(),
|
|
||||||
half_length + radius * angle.sin(),
|
|
||||||
angle.sin(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ring_indices = Vec::new();
|
|
||||||
let bottom_pole = self.push_capsule_vertex(
|
|
||||||
&mut ring_indices,
|
|
||||||
transform,
|
|
||||||
Vec3::NEG_Y,
|
|
||||||
-half_length,
|
|
||||||
radius,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
for &(ring_radius, y, normal_y) in &rings {
|
|
||||||
let mut ring = Vec::with_capacity(CAPSULE_SECTORS);
|
|
||||||
for sector in 0..CAPSULE_SECTORS {
|
|
||||||
let angle = sector as f32 * TAU / CAPSULE_SECTORS as f32;
|
|
||||||
let (sin, cos) = angle.sin_cos();
|
|
||||||
let normal = Vec3::new(
|
|
||||||
cos * (1.0 - normal_y * normal_y).sqrt(),
|
|
||||||
normal_y,
|
|
||||||
sin * (1.0 - normal_y * normal_y).sqrt(),
|
|
||||||
);
|
|
||||||
ring.push(self.push_transformed_vertex(
|
|
||||||
transform,
|
|
||||||
Vec3::new(cos * ring_radius, y, sin * ring_radius),
|
|
||||||
normal,
|
|
||||||
color,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
ring_indices.push(ring);
|
|
||||||
}
|
|
||||||
let top_pole = self.push_capsule_vertex(
|
|
||||||
&mut ring_indices,
|
|
||||||
transform,
|
|
||||||
Vec3::Y,
|
|
||||||
half_length,
|
|
||||||
radius,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
for sector in 0..CAPSULE_SECTORS {
|
|
||||||
let next = (sector + 1) % CAPSULE_SECTORS;
|
|
||||||
self.buffer.pending.indices.extend([
|
|
||||||
bottom_pole,
|
|
||||||
ring_indices[0][next],
|
|
||||||
ring_indices[0][sector],
|
|
||||||
]);
|
|
||||||
for ring in ring_indices.windows(2) {
|
|
||||||
self.buffer.pending.indices.extend([
|
|
||||||
ring[0][sector],
|
|
||||||
ring[0][next],
|
|
||||||
ring[1][sector],
|
|
||||||
ring[1][sector],
|
|
||||||
ring[0][next],
|
|
||||||
ring[1][next],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
let last = ring_indices.len() - 1;
|
|
||||||
self.buffer.pending.indices.extend([
|
|
||||||
ring_indices[last][sector],
|
|
||||||
ring_indices[last][next],
|
|
||||||
top_pole,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
// Keep the base used by this shape observable to avoid accidental
|
|
||||||
// future changes making the generated indices refer to another shape.
|
|
||||||
debug_assert!(base <= bottom_pole);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_capsule_vertex(
|
|
||||||
&mut self,
|
|
||||||
_rings: &mut Vec<Vec<u32>>,
|
|
||||||
transform: Mat4,
|
|
||||||
normal: Vec3,
|
|
||||||
y: f32,
|
|
||||||
radius: f32,
|
|
||||||
color: [f32; 4],
|
|
||||||
) -> u32 {
|
|
||||||
self.push_transformed_vertex(
|
|
||||||
transform,
|
|
||||||
Vec3::new(0.0, y + normal.y * radius, 0.0),
|
|
||||||
normal,
|
|
||||||
color,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_transformed_vertex(
|
|
||||||
&mut self,
|
|
||||||
transform: Mat4,
|
|
||||||
position: Vec3,
|
|
||||||
normal: Vec3,
|
|
||||||
color: [f32; 4],
|
|
||||||
) -> u32 {
|
|
||||||
let transformed_normal = transform
|
|
||||||
.inverse()
|
|
||||||
.transpose()
|
|
||||||
.transform_vector3(normal)
|
|
||||||
.normalize_or_zero();
|
|
||||||
let index = self.buffer.pending.positions.len() as u32;
|
|
||||||
self.push_vertex(
|
|
||||||
transform.transform_point3(position),
|
|
||||||
transformed_normal,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
index
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_vertex(&mut self, position: Vec3, normal: Vec3, color: [f32; 4]) {
|
|
||||||
self.buffer.pending.positions.push(position.to_array());
|
|
||||||
self.buffer.pending.normals.push(normal.to_array());
|
|
||||||
self.buffer.pending.colors.push(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FilledGizmosPlugin;
|
|
||||||
|
|
||||||
const FILLED_GIZMO_SHADER_HANDLE: Handle<Shader> =
|
|
||||||
uuid_handle!("0e4a979f-ea11-4e5a-91cd-19c89d4e9b53");
|
|
||||||
|
|
||||||
/// A deliberately minimal material: unlike `StandardMaterial`, its fragment
|
|
||||||
/// shader writes vertex colors directly and therefore bypasses PBR lighting
|
|
||||||
/// and camera tonemapping.
|
|
||||||
#[derive(Asset, AsBindGroup, Reflect, Clone, Debug)]
|
|
||||||
struct FilledGizmoMaterial {
|
|
||||||
#[uniform(0)]
|
|
||||||
color: LinearRgba,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Material for FilledGizmoMaterial {
|
|
||||||
fn vertex_shader() -> ShaderRef {
|
|
||||||
FILLED_GIZMO_SHADER_HANDLE.clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fragment_shader() -> ShaderRef {
|
|
||||||
FILLED_GIZMO_SHADER_HANDLE.clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alpha_mode(&self) -> AlphaMode {
|
|
||||||
AlphaMode::Blend
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_prepass() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_shadows() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn specialize(
|
|
||||||
_pipeline: &MaterialPipeline,
|
|
||||||
descriptor: &mut RenderPipelineDescriptor,
|
|
||||||
layout: &MeshVertexBufferLayoutRef,
|
|
||||||
_key: MaterialPipelineKey<Self>,
|
|
||||||
) -> Result<(), SpecializedMeshPipelineError> {
|
|
||||||
descriptor.vertex.buffers = vec![layout.0.get_layout(&[
|
|
||||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
|
||||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
|
||||||
])?];
|
|
||||||
descriptor.primitive.cull_mode = None;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for FilledGizmosPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
load_internal_asset!(
|
|
||||||
app,
|
|
||||||
FILLED_GIZMO_SHADER_HANDLE,
|
|
||||||
"filled_gizmo.wgsl",
|
|
||||||
Shader::from_wgsl
|
|
||||||
);
|
|
||||||
app.add_plugins(MaterialPlugin::<FilledGizmoMaterial>::default())
|
|
||||||
.init_resource::<FilledGizmoBuffer>()
|
|
||||||
.add_systems(Startup, setup_filled_gizmo_mesh)
|
|
||||||
.add_systems(FixedFirst, begin_fixed_filled_gizmos)
|
|
||||||
.add_systems(FixedLast, commit_fixed_filled_gizmos)
|
|
||||||
.add_systems(Last, upload_filled_gizmo_mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_filled_gizmo_mesh(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<FilledGizmoMaterial>>,
|
|
||||||
mut buffer: ResMut<FilledGizmoBuffer>,
|
|
||||||
) {
|
|
||||||
let mesh = meshes.add(
|
|
||||||
Mesh::new(
|
|
||||||
PrimitiveTopology::TriangleList,
|
|
||||||
RenderAssetUsages::default(),
|
|
||||||
)
|
|
||||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vec![[0.0; 3]; 3])
|
|
||||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 3])
|
|
||||||
.with_inserted_attribute(Mesh::ATTRIBUTE_COLOR, vec![[0.0, 0.0, 0.0, 0.0]; 3])
|
|
||||||
.with_inserted_indices(Indices::U32(vec![0, 1, 2])),
|
|
||||||
);
|
|
||||||
let material = materials.add(FilledGizmoMaterial {
|
|
||||||
color: LinearRgba::WHITE,
|
|
||||||
});
|
|
||||||
commands.spawn((
|
|
||||||
Mesh3d(mesh.clone()),
|
|
||||||
MeshMaterial3d(material),
|
|
||||||
NoFrustumCulling,
|
|
||||||
));
|
|
||||||
buffer.mesh = Some(mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_fixed_filled_gizmos(mut buffer: ResMut<FilledGizmoBuffer>) {
|
|
||||||
buffer.pending = FilledGizmoGeometry::default();
|
|
||||||
buffer.fixed_commit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commit_fixed_filled_gizmos(mut buffer: ResMut<FilledGizmoBuffer>) {
|
|
||||||
let pending = std::mem::take(&mut buffer.pending);
|
|
||||||
buffer.pending = std::mem::replace(&mut buffer.retained, pending);
|
|
||||||
buffer.fixed_commit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn upload_filled_gizmo_mesh(
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut buffer: ResMut<FilledGizmoBuffer>,
|
|
||||||
) {
|
|
||||||
// No fixed tick and no render-frame requests means the existing mesh is
|
|
||||||
// already the correct image. In particular, do not replace it with the
|
|
||||||
// empty placeholder between fixed updates.
|
|
||||||
if !buffer.fixed_commit && buffer.pending.positions.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(handle) = buffer.mesh.clone() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let (positions, normals, colors, indices) = if buffer.fixed_commit {
|
|
||||||
// Retained geometry must remain in the CPU-side buffer after upload;
|
|
||||||
// otherwise the next render frame would have nothing to draw.
|
|
||||||
(
|
|
||||||
buffer.retained.positions.clone(),
|
|
||||||
buffer.retained.normals.clone(),
|
|
||||||
buffer.retained.colors.clone(),
|
|
||||||
buffer.retained.indices.clone(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
std::mem::take(&mut buffer.pending.positions),
|
|
||||||
std::mem::take(&mut buffer.pending.normals),
|
|
||||||
std::mem::take(&mut buffer.pending.colors),
|
|
||||||
std::mem::take(&mut buffer.pending.indices),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(mut mesh) = meshes.get_mut(&handle) {
|
|
||||||
if positions.is_empty() {
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vec![[0.0; 3]; 3]);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 3]);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vec![[0.0, 0.0, 0.0, 0.0]; 3]);
|
|
||||||
mesh.insert_indices(Indices::U32(vec![0, 1, 2]));
|
|
||||||
} else {
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
|
||||||
mesh.insert_indices(Indices::U32(indices));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.pending = FilledGizmoGeometry::default();
|
|
||||||
buffer.fixed_commit = false;
|
|
||||||
}
|
|
||||||
@@ -21,8 +21,6 @@
|
|||||||
//! save, click Rebuild in jackdaw (or run `jd build`) and it
|
//! save, click Rebuild in jackdaw (or run `jd build`) and it
|
||||||
//! appears in `Add Component`. No registration code is needed.
|
//! appears in `Add Component`. No registration code is needed.
|
||||||
|
|
||||||
#![recursion_limit = "512"]
|
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
asset::processor::{AssetProcessor, ProcessorState},
|
asset::processor::{AssetProcessor, ProcessorState},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -44,7 +42,6 @@ pub mod blend_asset;
|
|||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod debug_tools;
|
pub mod debug_tools;
|
||||||
pub mod fighter;
|
pub mod fighter;
|
||||||
pub mod filled_gizmos;
|
|
||||||
pub mod game_settings;
|
pub mod game_settings;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod match_loading;
|
pub mod match_loading;
|
||||||
@@ -117,7 +114,6 @@ impl Plugin for GamePlugin {
|
|||||||
input::FighterInputPlugin,
|
input::FighterInputPlugin,
|
||||||
replay::ReplayPlugin,
|
replay::ReplayPlugin,
|
||||||
debug_tools::DebugToolsPlugin,
|
debug_tools::DebugToolsPlugin,
|
||||||
filled_gizmos::FilledGizmosPlugin,
|
|
||||||
fighter::FighterPlugin,
|
fighter::FighterPlugin,
|
||||||
fighter::baked_animation::BakedAnimationPlugin,
|
fighter::baked_animation::BakedAnimationPlugin,
|
||||||
scripting::importer::FighterScriptImportPlugin,
|
scripting::importer::FighterScriptImportPlugin,
|
||||||
|
|||||||
+29
-1
@@ -1,6 +1,12 @@
|
|||||||
use bevy::{color::palettes::css::RED, prelude::*};
|
use bevy::{color::palettes::css::RED, prelude::*};
|
||||||
|
|
||||||
use crate::stage::line::StageCollision;
|
use crate::{
|
||||||
|
match_loading::MatchConfig,
|
||||||
|
stage::{
|
||||||
|
line::StageCollision,
|
||||||
|
manifest::{StageManifest, StageManifestRegistry},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>) {
|
pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>) {
|
||||||
for poly in stage_collision.stage_polys.iter() {
|
for poly in stage_collision.stage_polys.iter() {
|
||||||
@@ -16,3 +22,25 @@ pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn debug_draw_blast_zones(
|
||||||
|
mut gizmos: Gizmos,
|
||||||
|
stage_registry: Res<StageManifestRegistry>,
|
||||||
|
stage_manifests: Res<Assets<StageManifest>>,
|
||||||
|
match_config: Res<MatchConfig>,
|
||||||
|
) {
|
||||||
|
let Some(stage_manifest_handle) = stage_registry.get(match_config.stage) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(stage_manifest) = stage_manifests.get(&stage_manifest_handle) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let top_left = stage_manifest.blast_zone_top_left.to_vec2();
|
||||||
|
let bottom_right = stage_manifest.blast_zone_bottom_right.to_vec2();
|
||||||
|
let center = (top_left + bottom_right) / 2.0;
|
||||||
|
let size = (bottom_right - top_left).abs();
|
||||||
|
|
||||||
|
gizmos.rect(Vec3::new(center.x, center.y, 0.0), size, bevy::color::palettes::css::BLUE);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,14 +7,10 @@ use strum::IntoEnumIterator;
|
|||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState, game_settings::GameSettings, math::{int::FGi32, vec::FGVec2}, stage::{
|
||||||
game_settings::GameSettings,
|
|
||||||
math::vec::FGVec2,
|
|
||||||
stage::{
|
|
||||||
StagePoly,
|
StagePoly,
|
||||||
line::{StageCollision, StagePolyLineSegmentType, StagePolyType},
|
line::{StageCollision, StagePolyLineSegmentType, StagePolyType},
|
||||||
},
|
}, texture_reference::TextureReference,
|
||||||
texture_reference::TextureReference,
|
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
@@ -87,6 +83,11 @@ pub struct StageManifest {
|
|||||||
pub icon: TextureReference,
|
pub icon: TextureReference,
|
||||||
pub camera: StageCameraProfile,
|
pub camera: StageCameraProfile,
|
||||||
pub polygons: Vec<StagePolyDefinition>,
|
pub polygons: Vec<StagePolyDefinition>,
|
||||||
|
|
||||||
|
#[reflect(ignore)]
|
||||||
|
pub blast_zone_top_left: FGVec2,
|
||||||
|
#[reflect(ignore)]
|
||||||
|
pub blast_zone_bottom_right: FGVec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
|
|||||||
+9
-4
@@ -10,7 +10,7 @@ pub mod scene;
|
|||||||
|
|
||||||
pub use line::{StagePlaneIntersectResult, StagePoly};
|
pub use line::{StagePlaneIntersectResult, StagePoly};
|
||||||
|
|
||||||
use crate::stage::{line::StageCollision, manifest::StageCameraProfile};
|
use crate::{match_loading::MatchConfig, stage::{line::StageCollision, manifest::StageCameraProfile}};
|
||||||
|
|
||||||
/// Owns the stage geometry: which scene gets spawned and how it is drawn.
|
/// Owns the stage geometry: which scene gets spawned and how it is drawn.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -20,9 +20,14 @@ impl Plugin for StagePlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
debug::debug_draw_scene
|
(
|
||||||
.run_if(resource_exists::<line::StageCollision>)
|
(debug::debug_draw_scene
|
||||||
.run_if(crate::debug_tools::stage_collision_enabled),
|
.run_if(resource_exists::<line::StageCollision>)
|
||||||
|
.run_if(crate::debug_tools::stage_collision_enabled)),
|
||||||
|
(debug::debug_draw_blast_zones
|
||||||
|
.run_if(resource_exists::<MatchConfig>)
|
||||||
|
.run_if(crate::debug_tools::blast_zones_enabled),),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user