Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7271d02e29 | ||
|
|
866a9c7500 |
@@ -109,7 +109,6 @@ impl FighterDebugRowKind {
|
||||
enum DebugFlag {
|
||||
Network,
|
||||
Ecb,
|
||||
BlastZones,
|
||||
StageCollision,
|
||||
AnimationBones,
|
||||
AttackHitboxes,
|
||||
@@ -151,10 +150,6 @@ const FIGHTER_MENU: &[DebugMenuEntry] = &[
|
||||
label: "ECB",
|
||||
flag: DebugFlag::Ecb,
|
||||
},
|
||||
DebugMenuEntry::Toggle {
|
||||
label: "Blast Zones",
|
||||
flag: DebugFlag::BlastZones,
|
||||
},
|
||||
DebugMenuEntry::Toggle {
|
||||
label: "Animation bones",
|
||||
flag: DebugFlag::AnimationBones,
|
||||
@@ -248,7 +243,6 @@ pub struct DebugSettings {
|
||||
pub frame_stepping: bool,
|
||||
pub network: bool,
|
||||
pub ecb: bool,
|
||||
pub blast_zones: bool,
|
||||
pub stage_collision: bool,
|
||||
pub animation_bones: bool,
|
||||
pub attack_hitboxes: bool,
|
||||
@@ -270,7 +264,6 @@ impl Default for DebugSettings {
|
||||
frame_stepping: false,
|
||||
network: false,
|
||||
ecb: false,
|
||||
blast_zones: true,
|
||||
stage_collision: false,
|
||||
animation_bones: false,
|
||||
attack_hitboxes: false,
|
||||
@@ -335,7 +328,6 @@ impl DebugSettings {
|
||||
match flag {
|
||||
DebugFlag::Network => self.network,
|
||||
DebugFlag::Ecb => self.ecb,
|
||||
DebugFlag::BlastZones => self.blast_zones,
|
||||
DebugFlag::StageCollision => self.stage_collision,
|
||||
DebugFlag::AnimationBones => self.animation_bones,
|
||||
DebugFlag::AttackHitboxes => self.attack_hitboxes,
|
||||
@@ -350,7 +342,6 @@ impl DebugSettings {
|
||||
match flag {
|
||||
DebugFlag::Network => self.network = !self.network,
|
||||
DebugFlag::Ecb => self.ecb = !self.ecb,
|
||||
DebugFlag::BlastZones => self.blast_zones = !self.blast_zones,
|
||||
DebugFlag::StageCollision => self.stage_collision = !self.stage_collision,
|
||||
DebugFlag::AnimationBones => self.animation_bones = !self.animation_bones,
|
||||
DebugFlag::AttackHitboxes => self.attack_hitboxes = !self.attack_hitboxes,
|
||||
@@ -893,10 +884,6 @@ pub fn stage_collision_enabled(settings: Res<DebugSettings>) -> bool {
|
||||
settings.stage_collision
|
||||
}
|
||||
|
||||
pub fn blast_zones_enabled(settings: Res<DebugSettings>) -> bool {
|
||||
settings.blast_zones
|
||||
}
|
||||
|
||||
pub fn animation_bones_enabled(settings: Res<DebugSettings>) -> bool {
|
||||
settings.animation_bones
|
||||
}
|
||||
|
||||
+41
-23
@@ -1,10 +1,9 @@
|
||||
//! Fighter debug views: gizmos and the egui inspector window.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use bevy::color::palettes::css::ORANGE;
|
||||
use bevy::{color::palettes::css::YELLOW, prelude::*};
|
||||
use bevy_egui::{EguiContexts, egui};
|
||||
use bevy::color::palettes::css::{ORANGE, ORANGE_RED, WHITE};
|
||||
use bevy::prelude::*;
|
||||
use bevy_egui::{egui, EguiContexts};
|
||||
|
||||
use crate::debug_tools::{DebugSettings, FighterDebugRowKind};
|
||||
use crate::fighter::animation::FighterAnimationFrame;
|
||||
@@ -15,14 +14,14 @@ use crate::fighter::attack::{
|
||||
use crate::fighter::baked_animation::{BakedFighterAnimations, FighterBoneMatrices};
|
||||
use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::errors::FighterErrors;
|
||||
use crate::fighter::hurtbox::FixedAffineCapsule;
|
||||
use crate::fighter::manifest::FighterManifest;
|
||||
use crate::fighter::state::{FighterState, air};
|
||||
use crate::fighter::state::{air, FighterState};
|
||||
use crate::fighter::visual::FighterAnimations;
|
||||
use crate::fighter::{
|
||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
|
||||
FighterTranslation, FighterVelocity,
|
||||
};
|
||||
use crate::filled_gizmos::FilledGizmos;
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
use crate::math::int::FGi32;
|
||||
@@ -38,6 +37,7 @@ pub fn debug_draw_ecb(
|
||||
&FighterPreviousTranslation,
|
||||
)>,
|
||||
mut gizmos: Gizmos,
|
||||
mut filled_gizmos: FilledGizmos,
|
||||
) {
|
||||
for (ecb, trf, ecb_prev, trf_prev) in ecbs {
|
||||
gizmos.cross(
|
||||
@@ -46,15 +46,18 @@ pub fn debug_draw_ecb(
|
||||
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 =
|
||||
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);
|
||||
filled_gizmos.polygon(&transformed, ORANGE);
|
||||
gizmos.lineloop(transformed, WHITE);
|
||||
|
||||
}
|
||||
}
|
||||
pub fn fighter_debug(
|
||||
@@ -183,15 +186,23 @@ pub fn animation_debug(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hurtbox_debug(query: Query<&FighterSolvedHurtboxes>, mut gizmos: Gizmos) {
|
||||
pub fn hurtbox_debug(
|
||||
query: Query<&FighterSolvedHurtboxes>,
|
||||
mut filled_gizmos: FilledGizmos,
|
||||
) {
|
||||
for hurtboxes in &query {
|
||||
for hurtbox in &hurtboxes.0 {
|
||||
draw_affine_capsule(&mut gizmos, hurtbox.capsule, YELLOW);
|
||||
filled_gizmos.affine_capsule(
|
||||
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,
|
||||
capsule: FixedAffineCapsule,
|
||||
color: impl Into<Color> + Copy,
|
||||
@@ -213,9 +224,9 @@ fn draw_affine_capsule(
|
||||
for &(start, end) in &wireframe.lines {
|
||||
gizmos.line(vertices[start], vertices[end], color);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
struct CapsuleWireVertex {
|
||||
/*struct CapsuleWireVertex {
|
||||
direction: Vec3,
|
||||
center_sign: f32,
|
||||
}
|
||||
@@ -223,9 +234,9 @@ struct CapsuleWireVertex {
|
||||
struct CapsuleWireframe {
|
||||
vertices: Vec<CapsuleWireVertex>,
|
||||
lines: Vec<(usize, usize)>,
|
||||
}
|
||||
}*/
|
||||
|
||||
fn capsule_wireframe() -> &'static CapsuleWireframe {
|
||||
/*fn capsule_wireframe() -> &'static CapsuleWireframe {
|
||||
const RESOLUTION: usize = 16;
|
||||
const HEMISPHERE_STEPS: usize = 4;
|
||||
static WIREFRAME: OnceLock<CapsuleWireframe> = OnceLock::new();
|
||||
@@ -306,7 +317,7 @@ fn capsule_wireframe() -> &'static CapsuleWireframe {
|
||||
|
||||
CapsuleWireframe { vertices, lines }
|
||||
})
|
||||
}
|
||||
}*/
|
||||
|
||||
#[cfg(test)]
|
||||
mod capsule_debug_tests {
|
||||
@@ -326,7 +337,7 @@ mod capsule_debug_tests {
|
||||
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() {
|
||||
let wireframe = capsule_wireframe();
|
||||
let lower: Vec<_> = wireframe
|
||||
@@ -344,7 +355,7 @@ mod capsule_debug_tests {
|
||||
assert!(upper.iter().all(|vertex| vertex.direction.y >= 0.0));
|
||||
assert!(lower.iter().any(|vertex| vertex.direction == Vec3::NEG_Y));
|
||||
assert!(upper.iter().any(|vertex| vertex.direction == Vec3::Y));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
pub fn attack_debug(
|
||||
@@ -356,6 +367,7 @@ pub fn attack_debug(
|
||||
&FighterAnimationFrame,
|
||||
)>,
|
||||
mut gizmos: Gizmos,
|
||||
mut filled_gizmos: FilledGizmos,
|
||||
) {
|
||||
for (hitboxes, _matrices, _translation, _facing_direction, _frame) in query {
|
||||
if let Some(active_attack) = &hitboxes.0 {
|
||||
@@ -382,6 +394,12 @@ pub fn attack_debug(
|
||||
capsule_draw_trf.to_isometry(),
|
||||
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(end, 1.0, bevy::color::palettes::css::GREEN);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
//! 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,6 +21,8 @@
|
||||
//! save, click Rebuild in jackdaw (or run `jd build`) and it
|
||||
//! appears in `Add Component`. No registration code is needed.
|
||||
|
||||
#![recursion_limit = "512"]
|
||||
|
||||
use bevy::{
|
||||
asset::processor::{AssetProcessor, ProcessorState},
|
||||
prelude::*,
|
||||
@@ -42,6 +44,7 @@ pub mod blend_asset;
|
||||
pub mod camera;
|
||||
pub mod debug_tools;
|
||||
pub mod fighter;
|
||||
pub mod filled_gizmos;
|
||||
pub mod game_settings;
|
||||
pub mod input;
|
||||
pub mod match_loading;
|
||||
@@ -114,6 +117,7 @@ impl Plugin for GamePlugin {
|
||||
input::FighterInputPlugin,
|
||||
replay::ReplayPlugin,
|
||||
debug_tools::DebugToolsPlugin,
|
||||
filled_gizmos::FilledGizmosPlugin,
|
||||
fighter::FighterPlugin,
|
||||
fighter::baked_animation::BakedAnimationPlugin,
|
||||
scripting::importer::FighterScriptImportPlugin,
|
||||
|
||||
+1
-29
@@ -1,12 +1,6 @@
|
||||
use bevy::{color::palettes::css::RED, prelude::*};
|
||||
|
||||
use crate::{
|
||||
match_loading::MatchConfig,
|
||||
stage::{
|
||||
line::StageCollision,
|
||||
manifest::{StageManifest, StageManifestRegistry},
|
||||
},
|
||||
};
|
||||
use crate::stage::line::StageCollision;
|
||||
|
||||
pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>) {
|
||||
for poly in stage_collision.stage_polys.iter() {
|
||||
@@ -22,25 +16,3 @@ 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,10 +7,14 @@ use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::{
|
||||
AppState, game_settings::GameSettings, math::{vec::FGVec2}, stage::{
|
||||
AppState,
|
||||
game_settings::GameSettings,
|
||||
math::vec::FGVec2,
|
||||
stage::{
|
||||
StagePoly,
|
||||
line::{StageCollision, StagePolyLineSegmentType, StagePolyType},
|
||||
}, texture_reference::TextureReference,
|
||||
},
|
||||
texture_reference::TextureReference,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -83,11 +87,6 @@ pub struct StageManifest {
|
||||
pub icon: TextureReference,
|
||||
pub camera: StageCameraProfile,
|
||||
pub polygons: Vec<StagePolyDefinition>,
|
||||
|
||||
#[reflect(ignore)]
|
||||
pub blast_zone_top_left: FGVec2,
|
||||
#[reflect(ignore)]
|
||||
pub blast_zone_bottom_right: FGVec2,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
|
||||
+4
-9
@@ -10,7 +10,7 @@ pub mod scene;
|
||||
|
||||
pub use line::{StagePlaneIntersectResult, StagePoly};
|
||||
|
||||
use crate::{match_loading::MatchConfig, stage::{line::StageCollision, manifest::StageCameraProfile}};
|
||||
use crate::stage::{line::StageCollision, manifest::StageCameraProfile};
|
||||
|
||||
/// Owns the stage geometry: which scene gets spawned and how it is drawn.
|
||||
#[derive(Default)]
|
||||
@@ -20,14 +20,9 @@ impl Plugin for StagePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
(debug::debug_draw_scene
|
||||
.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),),
|
||||
),
|
||||
debug::debug_draw_scene
|
||||
.run_if(resource_exists::<line::StageCollision>)
|
||||
.run_if(crate::debug_tools::stage_collision_enabled),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user