Implement a Debug pipeline for hitboxes and pathfinding lines.

- Implements mesh generation for cylinders and lines.
    - Implements an Id-allocator so that clients can mutate positions efficiently.
    - Is split into pipeline and scene modules.
    - Contains simple shaders that just pass through a position and color.
This commit is contained in:
Avi Weinstock 2021-05-04 13:24:45 -04:00
parent 254c2fb868
commit 364890653f
13 changed files with 480 additions and 9 deletions

View File

@ -0,0 +1,19 @@
#version 420 core
#include <globals.glsl>
layout (location = 0)
in vec4 f_color;
layout (std140, set = 1, binding = 0)
uniform u_locals {
vec4 w_pos;
vec4 w_color;
};
layout (location = 0)
out vec4 tgt_color;
void main() {
tgt_color = f_color;
}

View File

@ -0,0 +1,20 @@
#version 420 core
#include <globals.glsl>
layout (location = 0)
in vec3 v_pos;
layout (std140, set = 1, binding = 0)
uniform u_locals {
vec4 w_pos;
vec4 w_color;
};
layout (location = 0)
out vec4 f_color;
void main() {
f_color = w_color;
gl_Position = all_mat * vec4((v_pos + w_pos.xyz) - focus_off.xyz, 1);
}

View File

@ -21,6 +21,7 @@ pub use self::{
model::{DynamicModel, Model, SubModel},
pipelines::{
clouds::Locals as CloudsLocals,
debug::{DebugPipeline, Locals as DebugLocals, Vertex as DebugVertex},
figure::{
BoneData as FigureBoneData, BoneMeshes, FigureLayout, FigureModel,
Locals as FigureLocals,

View File

@ -0,0 +1,201 @@
use super::{
super::{AaMode, Bound, Consts, GlobalsLayouts, Vertex as VertexTrait},
};
use bytemuck::{Pod, Zeroable};
use std::mem;
use vek::*;
#[repr(C)]
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
pub struct Vertex {
pub pos: [f32; 3],
}
impl Vertex {
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: Self::STRIDE,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
}],
}
}
}
impl VertexTrait for Vertex {
//const QUADS_INDEX: Option<wgpu::IndexFormat> =
// Some(wgpu::IndexFormat::Uint32);
const QUADS_INDEX: Option<wgpu::IndexFormat> = None;
const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
pub struct Locals {
/// pos is [f32; 4] instead of [f32; 3] so that Locals's size is a multiple
/// of 8 bytes (which is required by gfx), the last component is ignored
/// by the shader
pub pos: [f32; 4],
pub color: [f32; 4],
}
pub type BoundLocals = Bound<Consts<Locals>>;
/*gfx_defines! {
vertex Vertex {
pos: [f32; 3] = "v_pos",
}
constant Locals {
// pos is [f32; 4] instead of [f32; 3] so that Locals's size is a multiple of 8 bytes
// (which is required by gfx), the last component is ignored by the shader
pos: [f32; 4] = "w_pos",
color: [f32; 4] = "w_color",
}
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
locals: gfx::ConstantBuffer<Locals> = "u_locals",
globals: gfx::ConstantBuffer<Globals> = "u_globals",
tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", gfx::state::ColorMask::all(), gfx::preset::blend::ALPHA),
//tgt_depth: gfx::DepthTarget<TgtDepthStencilFmt> = gfx::preset::depth::LESS_EQUAL_TEST,
tgt_depth: gfx::DepthTarget<TgtDepthStencilFmt> = gfx::preset::depth::PASS_TEST,
}
}*/
impl From<Vec3<f32>> for Vertex {
fn from(pos: Vec3<f32>) -> Vertex {
Vertex {
pos: [pos.x, pos.y, pos.z],
}
}
}
pub struct DebugPipeline {
pub pipeline: wgpu::RenderPipeline,
}
impl DebugPipeline {
pub fn new(
device: &wgpu::Device,
vs_module: &wgpu::ShaderModule,
fs_module: &wgpu::ShaderModule,
global_layouts: &GlobalsLayouts,
layout: &DebugLayout,
aa_mode: AaMode,
) -> Self {
common_base::span!(_guard, "DebugPipeline::new");
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Debug pipeline layout"),
push_constant_ranges: &[],
bind_group_layouts: &[&global_layouts.globals, &layout.locals],
});
let samples = match aa_mode {
AaMode::None | AaMode::Fxaa => 1,
// TODO: Ensure sampling in the shader is exactly between the 4 texels
AaMode::MsaaX4 => 4,
AaMode::MsaaX8 => 8,
AaMode::MsaaX16 => 16,
};
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Debug pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: vs_module,
entry_point: "main",
buffers: &[Vertex::desc()],
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
clamp_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::GreaterEqual,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState::IGNORE,
back: wgpu::StencilFaceState::IGNORE,
read_mask: !0,
write_mask: !0,
},
bias: wgpu::DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: wgpu::MultisampleState {
count: samples,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: fs_module,
entry_point: "main",
targets: &[wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba16Float,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrite::ALL,
}],
}),
});
Self {
pipeline: render_pipeline,
}
}
}
pub struct DebugLayout {
pub locals: wgpu::BindGroupLayout,
}
impl DebugLayout {
pub fn new(device: &wgpu::Device) -> Self {
Self {
locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
}),
}
}
pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts<Locals>) -> BoundLocals {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &self.locals,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: locals.buf().as_entire_binding(),
}],
});
BoundLocals {
bind_group,
with: locals,
}
}
}

View File

@ -1,5 +1,6 @@
pub mod blit;
pub mod clouds;
pub mod debug;
pub mod figure;
pub mod fluid;
pub mod lod_terrain;

View File

@ -21,7 +21,7 @@ use super::{
mesh::Mesh,
model::{DynamicModel, Model},
pipelines::{
blit, clouds, figure, postprocess, shadow, sprite, terrain, ui, GlobalsBindGroup,
blit, clouds, debug, figure, postprocess, shadow, sprite, terrain, ui, GlobalsBindGroup,
GlobalsLayouts, ShadowTexturesBindGroup,
},
texture::Texture,
@ -48,6 +48,7 @@ struct Layouts {
global: GlobalsLayouts,
clouds: clouds::CloudsLayout,
debug: debug::DebugLayout,
figure: figure::FigureLayout,
postprocess: postprocess::PostProcessLayout,
shadow: shadow::ShadowLayout,
@ -266,6 +267,7 @@ impl Renderer {
let global = GlobalsLayouts::new(&device);
let clouds = clouds::CloudsLayout::new(&device);
let debug = debug::DebugLayout::new(&device);
let figure = figure::FigureLayout::new(&device);
let postprocess = postprocess::PostProcessLayout::new(&device);
let shadow = shadow::ShadowLayout::new(&device);
@ -278,6 +280,7 @@ impl Renderer {
global,
clouds,
debug,
figure,
postprocess,
shadow,

View File

@ -2,7 +2,7 @@ use super::{
super::{
buffer::Buffer,
pipelines::{
figure, lod_terrain, shadow, sprite, terrain, ui, ColLights, GlobalModel,
debug, figure, lod_terrain, shadow, sprite, terrain, ui, ColLights, GlobalModel,
GlobalsBindGroup,
},
texture::Texture,
@ -36,6 +36,11 @@ impl Renderer {
)
}
pub fn create_debug_bound_locals(&mut self, vals: &[debug::Locals]) -> debug::BoundLocals {
let locals = self.create_consts(vals);
self.layouts.debug.bind_locals(&self.device, locals)
}
pub fn create_ui_bound_locals(&mut self, vals: &[ui::Locals]) -> ui::BoundLocals {
let locals = self.create_consts(vals);
self.layouts.ui.bind_locals(&self.device, locals)

View File

@ -4,8 +4,8 @@ use super::{
instances::Instances,
model::{DynamicModel, Model, SubModel},
pipelines::{
blit, clouds, figure, fluid, lod_terrain, particle, shadow, skybox, sprite, terrain,
ui, ColLights, GlobalsBindGroup,
blit, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox, sprite,
terrain, ui, ColLights, GlobalsBindGroup,
},
},
Renderer, ShadowMap, ShadowMapRenderer,
@ -534,6 +534,18 @@ impl<'pass> FirstPassDrawer<'pass> {
render_pass.draw(0..model.len() as u32, 0..1);
}
pub fn draw_debug(&mut self) -> DebugDrawer<'_, 'pass> {
let mut render_pass = self.render_pass.scope("debug", self.borrow.device);
render_pass.set_pipeline(&self.pipelines.debug.pipeline);
set_quad_index_buffer::<debug::Vertex>(&mut render_pass, &self.borrow);
DebugDrawer {
render_pass,
globals: self.globals,
}
}
pub fn draw_lod_terrain<'data: 'pass>(&mut self, model: &'data Model<lod_terrain::Vertex>) {
let mut render_pass = self.render_pass.scope("lod_terrain", self.borrow.device);
@ -601,6 +613,25 @@ impl<'pass> FirstPassDrawer<'pass> {
}
}
pub struct DebugDrawer<'pass_ref, 'pass: 'pass_ref> {
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
globals: &'pass GlobalsBindGroup,
}
impl<'pass_ref, 'pass: 'pass_ref> DebugDrawer<'pass_ref, 'pass> {
pub fn draw<'data: 'pass>(
&mut self,
model: &'data Model<debug::Vertex>,
locals: &'data debug::BoundLocals,
) {
self.render_pass
.set_bind_group(0, &self.globals.bind_group, &[]);
self.render_pass.set_bind_group(1, &locals.bind_group, &[]);
self.render_pass.set_vertex_buffer(0, model.buf().slice(..));
self.render_pass.draw(0..model.len() as u32, 0..1);
}
}
pub struct FigureDrawer<'pass_ref, 'pass: 'pass_ref> {
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
}

View File

@ -1,7 +1,7 @@
use super::{
super::{
pipelines::{
blit, clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox,
blit, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox,
sprite, terrain, ui,
},
AaMode, CloudMode, FluidMode, LightingMode, RenderError, RenderMode, ShadowMode,
@ -14,6 +14,7 @@ use std::sync::Arc;
/// All the pipelines
pub struct Pipelines {
pub debug: debug::DebugPipeline,
pub figure: figure::FigurePipeline,
pub fluid: fluid::FluidPipeline,
pub lod_terrain: lod_terrain::LodTerrainPipeline,
@ -32,6 +33,7 @@ pub struct Pipelines {
/// Pipelines that are needed to render 3D stuff in-game
/// Use to decouple interface pipeline creation when initializing the renderer
pub struct IngamePipelines {
debug: debug::DebugPipeline,
figure: figure::FigurePipeline,
fluid: fluid::FluidPipeline,
lod_terrain: lod_terrain::LodTerrainPipeline,
@ -66,6 +68,7 @@ pub struct InterfacePipelines {
impl Pipelines {
pub fn consolidate(interface: InterfacePipelines, ingame: IngamePipelines) -> Self {
Self {
debug: ingame.debug,
figure: ingame.figure,
fluid: ingame.fluid,
lod_terrain: ingame.lod_terrain,
@ -86,6 +89,8 @@ impl Pipelines {
struct ShaderModules {
skybox_vert: wgpu::ShaderModule,
skybox_frag: wgpu::ShaderModule,
debug_vert: wgpu::ShaderModule,
debug_frag: wgpu::ShaderModule,
figure_vert: wgpu::ShaderModule,
figure_frag: wgpu::ShaderModule,
terrain_vert: wgpu::ShaderModule,
@ -232,6 +237,8 @@ impl ShaderModules {
Ok(Self {
skybox_vert: create_shader("skybox-vert", ShaderKind::Vertex)?,
skybox_frag: create_shader("skybox-frag", ShaderKind::Fragment)?,
debug_vert: create_shader("debug-vert", ShaderKind::Vertex)?,
debug_frag: create_shader("debug-frag", ShaderKind::Fragment)?,
figure_vert: create_shader("figure-vert", ShaderKind::Vertex)?,
figure_frag: create_shader("figure-frag", ShaderKind::Fragment)?,
terrain_vert: create_shader("terrain-vert", ShaderKind::Vertex)?,
@ -352,7 +359,7 @@ fn create_interface_pipelines(
fn create_ingame_and_shadow_pipelines(
needs: PipelineNeeds,
pool: &rayon::ThreadPool,
tasks: [Task; 12],
tasks: [Task; 13],
) -> IngameAndShadowPipelines {
prof_span!(_guard, "create_ingame_and_shadow_pipelines");
@ -365,6 +372,7 @@ fn create_ingame_and_shadow_pipelines(
} = needs;
let [
debug_task,
skybox_task,
figure_task,
terrain_task,
@ -384,6 +392,22 @@ fn create_ingame_and_shadow_pipelines(
// TODO: pass in format of target color buffer
// Pipeline for rendering debug shapes
let create_debug = || {
debug_task.run(
|| {
debug::DebugPipeline::new(
device,
&shaders.debug_vert,
&shaders.debug_frag,
&layouts.global,
&layouts.debug,
mode.aa,
)
},
"debug pipeline creation",
)
};
// Pipeline for rendering skyboxes
let create_skybox = || {
skybox_task.run(
@ -596,7 +620,7 @@ fn create_ingame_and_shadow_pipelines(
)
};
let j1 = || pool.join(create_skybox, create_figure);
let j1 = || pool.join(create_debug, || pool.join(create_skybox, create_figure));
let j2 = || pool.join(create_terrain, create_fluid);
let j3 = || pool.join(create_sprite, create_particle);
let j4 = || pool.join(create_lod_terrain, create_clouds);
@ -610,7 +634,10 @@ fn create_ingame_and_shadow_pipelines(
// Ignore this
let (
(((skybox, figure), (terrain, fluid)), ((sprite, particle), (lod_terrain, clouds))),
(
((debug, (skybox, figure)), (terrain, fluid)),
((sprite, particle), (lod_terrain, clouds)),
),
((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)),
) = pool.join(
|| pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)),
@ -619,6 +646,7 @@ fn create_ingame_and_shadow_pipelines(
IngameAndShadowPipelines {
ingame: IngamePipelines {
debug,
figure,
fluid,
lod_terrain,

View File

@ -50,6 +50,8 @@ impl assets::Compound for Shaders {
"point-light-shadows-vert",
"skybox-vert",
"skybox-frag",
"debug-vert",
"debug-frag",
"figure-frag",
"terrain-vert",
"terrain-frag",

133
voxygen/src/scene/debug.rs Normal file
View File

@ -0,0 +1,133 @@
use crate::render::{
Bound, Consts, DebugLocals, DebugVertex, FirstPassDrawer, Mesh,
Model, Quad, Renderer, Tri,
};
use hashbrown::{HashMap, HashSet};
use vek::*;
#[derive(Debug)]
pub enum DebugShape {
Line([Vec3<f32>; 2]),
Cylinder { radius: f32, height: f32 },
}
impl DebugShape {
pub fn mesh(&self) -> Mesh<DebugVertex> {
use core::f32::consts::PI;
use DebugShape::*;
let mut mesh = Mesh::new();
let tri = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>| {
Tri::<DebugVertex>::new(x.into(), y.into(), z.into())
};
let quad = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, w: Vec3<f32>| {
Quad::<DebugVertex>::new(x.into(), y.into(), z.into(), w.into())
};
match self {
Line([a, b]) => {
let h = Vec3::new(0.0, 1.0, 0.0);
mesh.push_quad(quad(*a, a + h, b + h, *b));
},
Cylinder { radius, height } => {
const SUBDIVISIONS: usize = 16;
for i in 0..SUBDIVISIONS {
let angle = |j: usize| (j as f32 / SUBDIVISIONS as f32) * 2.0 * PI;
let a = Vec3::zero();
let b = Vec3::new(radius * angle(i).cos(), radius * angle(i).sin(), 0.0);
let c = Vec3::new(
radius * angle(i + 1).cos(),
radius * angle(i + 1).sin(),
0.0,
);
let h = Vec3::new(0.0, 0.0, *height);
mesh.push_tri(tri(a, b, c));
mesh.push_quad(quad(b, c, c + h, b + h));
mesh.push_tri(tri(a + h, b + h, c + h));
}
},
}
mesh
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct DebugShapeId(usize);
pub struct Debug {
next_shape_id: DebugShapeId,
pending_shapes: HashMap<DebugShapeId, DebugShape>,
pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4])>,
pending_deletes: HashSet<DebugShapeId>,
models: HashMap<DebugShapeId, Model<DebugVertex>>,
//locals: HashMap<DebugShapeId, Consts<DebugLocals>>,
locals: HashMap<DebugShapeId, Bound<Consts<DebugLocals>>>,
}
impl Debug {
pub fn new() -> Debug {
Debug {
next_shape_id: DebugShapeId(0),
pending_shapes: HashMap::new(),
pending_locals: HashMap::new(),
pending_deletes: HashSet::new(),
models: HashMap::new(),
locals: HashMap::new(),
}
}
pub fn add_shape(&mut self, shape: DebugShape) -> DebugShapeId {
let id = DebugShapeId(self.next_shape_id.0);
self.next_shape_id.0 += 1;
self.pending_shapes.insert(id, shape);
id
}
pub fn set_pos_and_color(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4]) {
self.pending_locals.insert(id, (pos, color));
}
pub fn remove_shape(&mut self, id: DebugShapeId) { self.pending_deletes.insert(id); }
pub fn maintain(&mut self, renderer: &mut Renderer) {
for (id, shape) in self.pending_shapes.drain() {
self.models
.insert(id, renderer.create_model(&shape.mesh()).unwrap());
/*self.locals.insert(
id,
renderer.create_consts(&[DebugLocals {
pos: [0.0; 4],
color: [1.0, 0.0, 0.0, 1.0],
}]),
);*/
}
for (id, (pos, color)) in self.pending_locals.drain() {
// TODO: what are the efficiency ramifications of creating the constants each
// time instead of caching them and binding them? UI seems to
// recreate them each time they change?
/*if let Some(locals) = self.locals.get_mut(&id) {
let new_locals = [DebugLocals { pos, color }];
renderer.update_consts(locals, &new_locals);
renderer.create_debug_bound_locals(new_locals);
}*/
let new_locals = [DebugLocals { pos, color }];
self.locals
.insert(id, renderer.create_debug_bound_locals(&new_locals));
}
for id in self.pending_deletes.drain() {
self.models.remove(&id);
self.locals.remove(&id);
}
}
pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) {
let mut debug_drawer = drawer.draw_debug();
for (id, model) in self.models.iter() {
if let Some(locals) = self.locals.get(id) {
debug_drawer.draw(model, locals);
}
}
}
}
impl Default for Debug {
fn default() -> Debug { Debug::new() }
}

View File

@ -1,4 +1,5 @@
pub mod camera;
pub mod debug;
pub mod figure;
pub mod lod;
pub mod math;
@ -8,6 +9,7 @@ pub mod terrain;
pub use self::{
camera::{Camera, CameraMode},
debug::{Debug, DebugShape, DebugShapeId},
figure::FigureMgr,
lod::Lod,
particle::ParticleMgr,
@ -79,6 +81,7 @@ pub struct Scene {
skybox: Skybox,
terrain: Terrain<TerrainChunk>,
pub debug: Debug,
pub lod: Lod,
loaded_distance: f32,
/// x coordinate is sea level (minimum height for any land chunk), and y
@ -289,6 +292,7 @@ impl Scene {
model: renderer.create_model(&create_skybox_mesh()).unwrap(),
},
terrain,
debug: Debug::new(),
lod,
loaded_distance: 0.0,
map_bounds: Vec2::new(
@ -653,6 +657,9 @@ impl Scene {
// Maintain LoD.
self.lod.maintain(renderer);
// Maintain debug shapes
self.debug.maintain(renderer);
// Maintain the terrain.
let (_visible_bounds, visible_light_volume, visible_psr_bounds) = self.terrain.maintain(
renderer,
@ -1109,6 +1116,9 @@ impl Scene {
// Render particle effects.
self.particle_mgr
.render(&mut first_pass.draw_particles(), scene_data);
// Render debug shapes
self.debug.render(&mut first_pass);
}
}
}

View File

@ -39,7 +39,7 @@ use crate::{
key_state::KeyState,
menu::char_selection::CharSelectionState,
render::Renderer,
scene::{camera, terrain::Interaction, CameraMode, Scene, SceneData},
scene::{camera, terrain::Interaction, CameraMode, DebugShape, DebugShapeId, Scene, SceneData},
settings::Settings,
window::{AnalogGameInput, Event, GameInput},
Direction, Error, GlobalState, PlayState, PlayStateResult,
@ -73,6 +73,7 @@ pub struct SessionState {
selected_entity: Option<(specs::Entity, std::time::Instant)>,
interactable: Option<Interactable>,
saved_zoom_dist: Option<f32>,
player_hitbox: DebugShapeId,
}
/// Represents an active game session (i.e., the one being played).
@ -100,6 +101,10 @@ impl SessionState {
let hud = Hud::new(global_state, &client.borrow());
let walk_forward_dir = scene.camera().forward_xy();
let walk_right_dir = scene.camera().right_xy();
let player_hitbox = scene.debug.add_shape(DebugShape::Cylinder {
radius: 0.4,
height: 1.75,
});
Self {
scene,
@ -120,6 +125,7 @@ impl SessionState {
selected_entity: None,
interactable: None,
saved_zoom_dist: None,
player_hitbox,
}
}
@ -139,6 +145,17 @@ impl SessionState {
span!(_guard, "tick", "Session::tick");
let mut client = self.client.borrow_mut();
if let Some(player_pos) = client
.state()
.ecs()
.read_component::<Pos>()
.get(client.entity())
{
let pos = [player_pos.0.x, player_pos.0.y, player_pos.0.z, 0.0];
self.scene
.debug
.set_pos_and_color(self.player_hitbox, pos, [1.0, 0.0, 0.0, 0.5]);
}
for event in client.tick(self.inputs.clone(), dt, crate::ecs::sys::add_local_systems)? {
match event {
client::Event::Chat(m) => {