refactor
This commit is contained in:
549
src/main.rs
549
src/main.rs
@@ -9,37 +9,24 @@ mod physics;
|
||||
mod picking;
|
||||
mod postprocess;
|
||||
mod render;
|
||||
mod snow;
|
||||
mod snow_light;
|
||||
mod state;
|
||||
mod systems;
|
||||
mod texture;
|
||||
mod utility;
|
||||
mod world;
|
||||
|
||||
use crate::debug::{collider_debug, DebugMode};
|
||||
use crate::editor::{editor_loop, EditorState, FrameStats};
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use glam::Vec3;
|
||||
use render::Renderer;
|
||||
use sdl3::event::Event;
|
||||
use sdl3::keyboard::Keycode;
|
||||
use sdl3::mouse::MouseButton;
|
||||
use utility::input::InputState;
|
||||
use world::World;
|
||||
|
||||
use crate::bundles::camera::CameraBundle;
|
||||
use crate::bundles::player::PlayerBundle;
|
||||
use crate::bundles::spotlight::spawn_spotlights;
|
||||
use crate::bundles::terrain::{TerrainBundle, TerrainConfig};
|
||||
use crate::bundles::test_char::TestCharBundle;
|
||||
use crate::bundles::Bundle;
|
||||
use crate::debug::{collider_debug, DebugMode};
|
||||
use crate::editor::{editor_loop, EditorState, FrameStats};
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::scene::Space;
|
||||
use crate::physics::PhysicsManager;
|
||||
use crate::snow::{SnowConfig, SnowLayer};
|
||||
|
||||
use crate::render::snow::{SnowConfig, SnowLayer};
|
||||
use crate::systems::camera::stop_camera_following;
|
||||
use crate::systems::{
|
||||
camera_follow_system, camera_input_system, camera_view_matrix, dialog_bubble_render_system,
|
||||
@@ -49,9 +36,34 @@ use crate::systems::{
|
||||
tree_dissolve_update_system, tree_instance_buffer_update_system, tree_occlusion_system,
|
||||
trigger_system,
|
||||
};
|
||||
use crate::utility::input::InputState;
|
||||
use crate::utility::time::Time;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use glam::Vec3;
|
||||
use render::Renderer;
|
||||
use sdl3::event::Event;
|
||||
use sdl3::keyboard::Keycode;
|
||||
use sdl3::mouse::MouseButton;
|
||||
use world::World;
|
||||
|
||||
struct Game
|
||||
{
|
||||
sdl_context: sdl3::Sdl,
|
||||
window: sdl3::video::Window,
|
||||
_event_pump: sdl3::EventPump,
|
||||
world: World,
|
||||
editor: EditorState,
|
||||
input_state: InputState,
|
||||
camera_entity: EntityHandle,
|
||||
last_frame: Instant,
|
||||
frame_duration: Duration,
|
||||
physics_accumulator: f32,
|
||||
stats: FrameStats,
|
||||
}
|
||||
|
||||
fn init() -> Result<Game, Box<dyn std::error::Error>>
|
||||
{
|
||||
let sdl_context = sdl3::init()?;
|
||||
let video_subsystem = sdl_context.video()?;
|
||||
@@ -72,34 +84,69 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
});
|
||||
editor.init_platform(&window);
|
||||
|
||||
let (mut world, camera_entity) = init_world()?;
|
||||
start_camera_following(&mut world, camera_entity);
|
||||
|
||||
let _event_pump = sdl_context.event_pump()?;
|
||||
let input_state = InputState::new();
|
||||
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
||||
|
||||
Time::init();
|
||||
|
||||
Ok(Game {
|
||||
sdl_context,
|
||||
window,
|
||||
_event_pump,
|
||||
world,
|
||||
editor,
|
||||
input_state,
|
||||
camera_entity,
|
||||
last_frame: Instant::now(),
|
||||
frame_duration: Duration::from_millis(1000 / 60),
|
||||
physics_accumulator: 0.0,
|
||||
stats: FrameStats {
|
||||
fps: 0.0,
|
||||
frame_ms: 0.0,
|
||||
physics_budget_ms: 0.0,
|
||||
draw_call_count: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn init_world() -> Result<(World, EntityHandle), Box<dyn std::error::Error>>
|
||||
{
|
||||
let space = Space::load_space(&crate::paths::meshes::terrain())?;
|
||||
let terrain_config = TerrainConfig::default();
|
||||
|
||||
let player_spawn = space.player_spawn;
|
||||
let camera_spawn = space.camera_spawn_position();
|
||||
|
||||
let tree_positions: Vec<Vec3> = space
|
||||
.mesh_data
|
||||
.iter()
|
||||
.flat_map(|(_, instances)| instances.iter().map(|inst| inst.position))
|
||||
.collect();
|
||||
|
||||
let player_spawn = space.player_spawn;
|
||||
let test_char_spawn = space.test_char_spawn;
|
||||
let camera_spawn = space.camera_spawn_position();
|
||||
let spotlights = space.spotlights;
|
||||
let mesh_data = space.mesh_data;
|
||||
|
||||
let mut world = World::new();
|
||||
|
||||
let _player_entity = PlayerBundle {
|
||||
PlayerBundle {
|
||||
position: player_spawn,
|
||||
}
|
||||
.spawn(&mut world)
|
||||
.unwrap();
|
||||
|
||||
let _test_char_entity = TestCharBundle {
|
||||
position: space.test_char_spawn,
|
||||
TestCharBundle {
|
||||
position: test_char_spawn,
|
||||
}
|
||||
.spawn(&mut world)
|
||||
.unwrap();
|
||||
|
||||
let _terrain_entity = TerrainBundle::spawn(&mut world, space.mesh_data, &terrain_config)?;
|
||||
spawn_spotlights(&mut world, space.spotlights);
|
||||
TerrainBundle::spawn(&mut world, mesh_data, &terrain_config)?;
|
||||
spawn_spotlights(&mut world, spotlights);
|
||||
|
||||
render::set_terrain_data();
|
||||
|
||||
@@ -110,294 +157,320 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
);
|
||||
|
||||
let snow_config = SnowConfig::default();
|
||||
let mut snow_layer = SnowLayer::load(&snow_config)?;
|
||||
let snow_layer = SnowLayer::load(&snow_config)?;
|
||||
for pos in &tree_positions
|
||||
{
|
||||
snow_layer.deform_at_position(*pos, 5.0, 50.0);
|
||||
}
|
||||
println!("Snow layer loaded successfully");
|
||||
|
||||
render::set_snow_depth(&snow_layer.depth_texture_view);
|
||||
|
||||
let mut debug_mode = DebugMode::default();
|
||||
world.snow_layer = Some(snow_layer);
|
||||
|
||||
let camera_entity = CameraBundle {
|
||||
position: camera_spawn,
|
||||
}
|
||||
.spawn(&mut world)
|
||||
.unwrap();
|
||||
start_camera_following(&mut world, camera_entity);
|
||||
|
||||
let _event_pump = sdl_context.event_pump()?;
|
||||
let mut input_state = InputState::new();
|
||||
Ok((world, camera_entity))
|
||||
}
|
||||
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
||||
fn process_events(game: &mut Game) -> bool
|
||||
{
|
||||
game.editor.begin_frame();
|
||||
|
||||
Time::init();
|
||||
let mut last_frame = Instant::now();
|
||||
let target_fps = 60;
|
||||
let frame_duration = Duration::from_millis(1000 / target_fps);
|
||||
|
||||
const FIXED_TIMESTEP: f32 = 1.0 / 60.0;
|
||||
let mut physics_accumulator = 0.0;
|
||||
|
||||
let mut stats = FrameStats {
|
||||
fps: 0.0,
|
||||
frame_ms: 0.0,
|
||||
physics_budget_ms: 0.0,
|
||||
draw_call_count: 0,
|
||||
};
|
||||
|
||||
'running: loop
|
||||
while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll()
|
||||
{
|
||||
let frame_start = Instant::now();
|
||||
let time = Time::get_time_elapsed();
|
||||
let delta = (frame_start - last_frame).as_secs_f32();
|
||||
last_frame = frame_start;
|
||||
game.editor.process_event(&raw_event);
|
||||
let event = Event::from_ll(raw_event);
|
||||
|
||||
editor.begin_frame();
|
||||
|
||||
while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll()
|
||||
match &event
|
||||
{
|
||||
editor.process_event(&raw_event);
|
||||
let event = Event::from_ll(raw_event);
|
||||
|
||||
match &event
|
||||
Event::Quit { .. } =>
|
||||
{
|
||||
Event::Quit { .. } =>
|
||||
{
|
||||
input_state.quit_requested = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::KeyDown {
|
||||
keycode: Some(Keycode::Tab),
|
||||
repeat: false,
|
||||
..
|
||||
} =>
|
||||
{
|
||||
editor.active = !editor.active;
|
||||
if editor.active
|
||||
{
|
||||
stop_camera_following(&mut world, camera_entity);
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
||||
editor.right_mouse_held = false;
|
||||
input_state.mouse_captured = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
start_camera_following(&mut world, camera_entity);
|
||||
input_state.mouse_captured = true;
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonDown {
|
||||
mouse_btn: MouseButton::Right,
|
||||
..
|
||||
} if editor.active =>
|
||||
{
|
||||
editor.right_mouse_held = true;
|
||||
input_state.mouse_captured = true;
|
||||
stop_camera_following(&mut world, camera_entity);
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonUp {
|
||||
mouse_btn: MouseButton::Right,
|
||||
..
|
||||
} if editor.active =>
|
||||
{
|
||||
editor.right_mouse_held = false;
|
||||
input_state.mouse_captured = false;
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonDown {
|
||||
mouse_btn: MouseButton::Left,
|
||||
x,
|
||||
y,
|
||||
..
|
||||
} if editor.active && !editor.wants_mouse() =>
|
||||
{
|
||||
if let Some(view) = crate::systems::camera_view_matrix(&world)
|
||||
{
|
||||
if let Some((_, cam)) = world.active_camera()
|
||||
{
|
||||
let projection = cam.projection_matrix();
|
||||
let (win_w, win_h) = window.size();
|
||||
let ray = crate::picking::Ray::from_screen_position(
|
||||
*x,
|
||||
*y,
|
||||
win_w,
|
||||
win_h,
|
||||
&view,
|
||||
&projection,
|
||||
);
|
||||
editor.selected_entity = crate::picking::pick_entity(&ray, &world);
|
||||
render::set_selected_entity(editor.selected_entity);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
_ =>
|
||||
{}
|
||||
return true;
|
||||
}
|
||||
|
||||
if editor.active && (editor.wants_keyboard() || editor.wants_mouse())
|
||||
Event::KeyDown {
|
||||
keycode: Some(Keycode::Tab),
|
||||
repeat: false,
|
||||
..
|
||||
} =>
|
||||
{
|
||||
toggle_editor(game);
|
||||
continue;
|
||||
}
|
||||
|
||||
let capture_changed = input_state.handle_event(&event);
|
||||
if capture_changed && !editor.active
|
||||
Event::MouseButtonDown {
|
||||
mouse_btn: MouseButton::Right,
|
||||
..
|
||||
} if game.editor.active =>
|
||||
{
|
||||
sdl_context
|
||||
game.editor.right_mouse_held = true;
|
||||
game.input_state.mouse_captured = true;
|
||||
stop_camera_following(&mut game.world, game.camera_entity);
|
||||
game.sdl_context
|
||||
.mouse()
|
||||
.set_relative_mouse_mode(&window, input_state.mouse_captured);
|
||||
.set_relative_mouse_mode(&game.window, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonUp {
|
||||
mouse_btn: MouseButton::Right,
|
||||
..
|
||||
} if game.editor.active =>
|
||||
{
|
||||
game.editor.right_mouse_held = false;
|
||||
game.input_state.mouse_captured = false;
|
||||
game.sdl_context
|
||||
.mouse()
|
||||
.set_relative_mouse_mode(&game.window, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonDown {
|
||||
mouse_btn: MouseButton::Left,
|
||||
x,
|
||||
y,
|
||||
..
|
||||
} if game.editor.active && !game.editor.wants_mouse() =>
|
||||
{
|
||||
handle_editor_pick(game, *x, *y);
|
||||
continue;
|
||||
}
|
||||
|
||||
_ =>
|
||||
{}
|
||||
}
|
||||
|
||||
if input_state.quit_requested
|
||||
if game.editor.active && (game.editor.wants_keyboard() || game.editor.wants_mouse())
|
||||
{
|
||||
break 'running;
|
||||
continue;
|
||||
}
|
||||
|
||||
if input_state.debug_cycle_just_pressed
|
||||
let capture_changed = game.input_state.handle_event(&event);
|
||||
if capture_changed && !game.editor.active
|
||||
{
|
||||
debug_mode = debug_mode.cycle();
|
||||
println!("Debug mode: {:?}", debug_mode);
|
||||
game.sdl_context
|
||||
.mouse()
|
||||
.set_relative_mouse_mode(&game.window, game.input_state.mouse_captured);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn toggle_editor(game: &mut Game)
|
||||
{
|
||||
game.editor.active = !game.editor.active;
|
||||
if game.editor.active
|
||||
{
|
||||
stop_camera_following(&mut game.world, game.camera_entity);
|
||||
game.sdl_context
|
||||
.mouse()
|
||||
.set_relative_mouse_mode(&game.window, false);
|
||||
game.editor.right_mouse_held = false;
|
||||
game.input_state.mouse_captured = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
start_camera_following(&mut game.world, game.camera_entity);
|
||||
game.input_state.mouse_captured = true;
|
||||
game.sdl_context
|
||||
.mouse()
|
||||
.set_relative_mouse_mode(&game.window, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_editor_pick(game: &mut Game, x: f32, y: f32)
|
||||
{
|
||||
let view = match camera_view_matrix(&game.world)
|
||||
{
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
let (_, cam) = match game.world.active_camera()
|
||||
{
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
let projection = cam.projection_matrix();
|
||||
let (win_w, win_h) = game.window.size();
|
||||
let ray = picking::Ray::from_screen_position(x, y, win_w, win_h, &view, &projection);
|
||||
game.editor.selected_entity = picking::pick_entity(&ray, &game.world);
|
||||
render::set_selected_entity(game.editor.selected_entity);
|
||||
}
|
||||
|
||||
fn submit_frame(game: &mut Game, draw_calls: &[render::DrawCall], time: f32, delta: f32)
|
||||
{
|
||||
let (camera_entity, camera_component) = match game.world.active_camera()
|
||||
{
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
let camera_transform = match game.world.transforms.get(camera_entity)
|
||||
{
|
||||
Some(t) => t,
|
||||
None => return,
|
||||
};
|
||||
let view = match camera_view_matrix(&game.world)
|
||||
{
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let projection = camera_component.projection_matrix();
|
||||
let view_proj = projection * view;
|
||||
let player_pos = game.world.player_position();
|
||||
|
||||
let billboard_calls =
|
||||
dialog_bubble_render_system(&game.world, camera_transform.position, view_proj);
|
||||
|
||||
let frame = render::render(
|
||||
&view,
|
||||
&projection,
|
||||
camera_transform.position,
|
||||
player_pos,
|
||||
draw_calls,
|
||||
&billboard_calls,
|
||||
time,
|
||||
delta,
|
||||
game.world.debug_mode,
|
||||
);
|
||||
|
||||
if game.editor.active || game.editor.show_player_state
|
||||
{
|
||||
let screen_view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = render::with_device(|d| {
|
||||
d.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("ImGui Encoder"),
|
||||
})
|
||||
});
|
||||
game.editor.render(&mut encoder, &screen_view);
|
||||
render::with_queue(|q| q.submit(std::iter::once(encoder.finish())));
|
||||
}
|
||||
|
||||
frame.present();
|
||||
}
|
||||
|
||||
const FIXED_TIMESTEP: f32 = 1.0 / 60.0;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let mut game = init()?;
|
||||
|
||||
loop
|
||||
{
|
||||
let frame_start = Instant::now();
|
||||
let time = Time::get_time_elapsed();
|
||||
let delta = (frame_start - game.last_frame).as_secs_f32();
|
||||
game.last_frame = frame_start;
|
||||
|
||||
// --- events ---
|
||||
if process_events(&mut game)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if input_state.f2_just_pressed
|
||||
if game.input_state.debug_cycle_just_pressed
|
||||
{
|
||||
editor.show_player_state = !editor.show_player_state;
|
||||
game.world.debug_mode = game.world.debug_mode.cycle();
|
||||
println!("Debug mode: {:?}", game.world.debug_mode);
|
||||
}
|
||||
if game.input_state.f2_just_pressed
|
||||
{
|
||||
game.editor.show_player_state = !game.editor.show_player_state;
|
||||
}
|
||||
|
||||
camera_input_system(&mut world, &input_state);
|
||||
// --- camera + input ---
|
||||
camera_input_system(&mut game.world, &game.input_state);
|
||||
|
||||
if editor.active
|
||||
if game.editor.active
|
||||
{
|
||||
editor_loop(&mut editor, &mut world, &input_state, &stats, delta);
|
||||
editor_loop(
|
||||
&mut game.editor,
|
||||
&mut game.world,
|
||||
&game.input_state,
|
||||
&game.stats,
|
||||
delta,
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
let dialog_active = !world.bubble_tags.all().is_empty();
|
||||
let dialog_active = !game.world.bubble_tags.all().is_empty();
|
||||
if dialog_active
|
||||
{
|
||||
dialog_camera_system(&mut world, delta);
|
||||
dialog_camera_system(&mut game.world, delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_follow_system(&mut world);
|
||||
camera_follow_system(&mut game.world);
|
||||
}
|
||||
player_input_system(&mut world, &input_state);
|
||||
if editor.show_player_state
|
||||
player_input_system(&mut game.world, &game.input_state);
|
||||
if game.editor.show_player_state
|
||||
{
|
||||
editor.build_hud(&world);
|
||||
game.editor.build_hud(&game.world);
|
||||
}
|
||||
}
|
||||
|
||||
// --- fixed-step physics ---
|
||||
let physics_start = Instant::now();
|
||||
game.physics_accumulator += delta;
|
||||
|
||||
physics_accumulator += delta;
|
||||
|
||||
while physics_accumulator >= FIXED_TIMESTEP
|
||||
while game.physics_accumulator >= FIXED_TIMESTEP
|
||||
{
|
||||
state_machine_physics_system(&mut world, FIXED_TIMESTEP);
|
||||
|
||||
state_machine_physics_system(&mut game.world, FIXED_TIMESTEP);
|
||||
PhysicsManager::physics_step();
|
||||
|
||||
physics_sync_system(&mut world);
|
||||
trigger_system(&mut world);
|
||||
dialog_system(&mut world, FIXED_TIMESTEP);
|
||||
dialog_projectile_system(&mut world, &input_state);
|
||||
|
||||
physics_accumulator -= FIXED_TIMESTEP;
|
||||
physics_sync_system(&mut game.world);
|
||||
trigger_system(&mut game.world);
|
||||
dialog_system(&mut game.world, FIXED_TIMESTEP);
|
||||
dialog_projectile_system(&mut game.world, &game.input_state);
|
||||
game.physics_accumulator -= FIXED_TIMESTEP;
|
||||
}
|
||||
|
||||
stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;
|
||||
game.stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;
|
||||
|
||||
state_machine_system(&mut world, delta);
|
||||
// --- per-frame systems ---
|
||||
state_machine_system(&mut game.world, delta);
|
||||
rotate_system(&mut game.world, delta);
|
||||
|
||||
rotate_system(&mut world, delta);
|
||||
tree_occlusion_system(&mut game.world);
|
||||
tree_dissolve_update_system(&mut game.world, delta);
|
||||
tree_instance_buffer_update_system(&mut game.world);
|
||||
|
||||
tree_occlusion_system(&mut world);
|
||||
tree_dissolve_update_system(&mut world, delta);
|
||||
tree_instance_buffer_update_system(&mut world);
|
||||
|
||||
let spotlights = spotlight_sync_system(&world);
|
||||
let spotlights = spotlight_sync_system(&game.world);
|
||||
render::update_spotlights(spotlights);
|
||||
|
||||
snow_system(&world, &mut snow_layer, editor.active);
|
||||
snow_system(&mut game.world, game.editor.active);
|
||||
|
||||
let mut draw_calls = render_system(&world);
|
||||
draw_calls.extend(snow_layer.get_draw_calls());
|
||||
// --- draw call collection ---
|
||||
let mut draw_calls = render_system(&game.world);
|
||||
if let Some(ref snow_layer) = game.world.snow_layer
|
||||
{
|
||||
draw_calls.extend(snow_layer.get_draw_calls());
|
||||
}
|
||||
|
||||
if debug_mode == DebugMode::Colliders
|
||||
if game.world.debug_mode == DebugMode::Colliders
|
||||
{
|
||||
draw_calls.extend(collider_debug::render_collider_debug());
|
||||
}
|
||||
|
||||
if let Some((camera_entity, camera_component)) = world.active_camera()
|
||||
{
|
||||
if let Some(camera_transform) = world.transforms.get(camera_entity)
|
||||
{
|
||||
let player_pos = world.player_position();
|
||||
game.stats.draw_call_count = draw_calls.len();
|
||||
game.stats.fps = 1.0 / delta;
|
||||
game.stats.frame_ms = delta * 1000.0;
|
||||
|
||||
if let Some(view) = camera_view_matrix(&world)
|
||||
{
|
||||
let projection = camera_component.projection_matrix();
|
||||
let view_proj = projection * view;
|
||||
// --- render ---
|
||||
submit_frame(&mut game, &draw_calls, time, delta);
|
||||
|
||||
let billboard_calls =
|
||||
dialog_bubble_render_system(&world, camera_transform.position, view_proj);
|
||||
|
||||
stats.draw_call_count = draw_calls.len();
|
||||
stats.fps = 1.0 / delta;
|
||||
stats.frame_ms = delta * 1000.0;
|
||||
|
||||
let frame = render::render(
|
||||
&view,
|
||||
&projection,
|
||||
camera_transform.position,
|
||||
player_pos,
|
||||
&draw_calls,
|
||||
&billboard_calls,
|
||||
time,
|
||||
delta,
|
||||
debug_mode,
|
||||
);
|
||||
|
||||
if editor.active || editor.show_player_state
|
||||
{
|
||||
let screen_view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = render::with_device(|d| {
|
||||
d.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("ImGui Encoder"),
|
||||
})
|
||||
});
|
||||
editor.render(&mut encoder, &screen_view);
|
||||
render::with_queue(|q| q.submit(std::iter::once(encoder.finish())));
|
||||
}
|
||||
|
||||
frame.present();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input_state.clear_just_pressed();
|
||||
// --- end frame ---
|
||||
game.input_state.clear_just_pressed();
|
||||
|
||||
let frame_time = frame_start.elapsed();
|
||||
if frame_time < frame_duration
|
||||
if frame_time < game.frame_duration
|
||||
{
|
||||
std::thread::sleep(frame_duration - frame_time);
|
||||
std::thread::sleep(game.frame_duration - frame_time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
127
src/render/global.rs
Normal file
127
src/render/global.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
/// Global renderer access via thread-local storage.
|
||||
///
|
||||
/// This module isolates the global singleton pattern used to give systems
|
||||
/// and loaders access to the wgpu `Device` and `Queue` without threading
|
||||
/// them through every call site. The long-term goal is to replace these
|
||||
/// with explicit `RenderContext` parameters on systems that need GPU access.
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::debug::DebugMode;
|
||||
use crate::entity::EntityHandle;
|
||||
|
||||
use super::{BillboardDrawCall, DrawCall, Renderer, Spotlight};
|
||||
|
||||
thread_local! {
|
||||
static GLOBAL_RENDERER: RefCell<Option<Renderer>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
fn with_ref<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&Renderer) -> R,
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not initialized");
|
||||
f(renderer)
|
||||
})
|
||||
}
|
||||
|
||||
fn with_mut<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Renderer) -> R,
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not initialized");
|
||||
f(renderer)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init(renderer: Renderer)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer));
|
||||
}
|
||||
|
||||
pub fn with_device<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&wgpu::Device) -> R,
|
||||
{
|
||||
with_ref(|r| f(&r.device))
|
||||
}
|
||||
|
||||
pub fn with_queue<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&wgpu::Queue) -> R,
|
||||
{
|
||||
with_ref(|r| f(&r.queue))
|
||||
}
|
||||
|
||||
pub fn with_surface_format<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(wgpu::TextureFormat) -> R,
|
||||
{
|
||||
with_ref(|r| f(r.config.format))
|
||||
}
|
||||
|
||||
pub fn aspect_ratio() -> f32
|
||||
{
|
||||
with_ref(|r| r.aspect_ratio())
|
||||
}
|
||||
|
||||
pub fn set_terrain_data()
|
||||
{
|
||||
with_mut(|r| r.set_terrain_data());
|
||||
}
|
||||
|
||||
pub fn init_snow_light_accumulation(terrain_min: glam::Vec2, terrain_max: glam::Vec2)
|
||||
{
|
||||
with_mut(|r| r.init_snow_light_accumulation(terrain_min, terrain_max));
|
||||
}
|
||||
|
||||
pub fn set_snow_depth(snow_depth_view: &wgpu::TextureView)
|
||||
{
|
||||
with_mut(|r| r.set_snow_depth(snow_depth_view));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_shadow_bias(bias: f32)
|
||||
{
|
||||
with_mut(|r| r.shadow_bias = bias);
|
||||
}
|
||||
|
||||
pub fn update_spotlights(spotlights: Vec<Spotlight>)
|
||||
{
|
||||
with_mut(|r| r.spotlights = spotlights);
|
||||
}
|
||||
|
||||
pub fn set_selected_entity(entity: Option<EntityHandle>)
|
||||
{
|
||||
with_mut(|r| r.selected_entity = entity);
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
view: &glam::Mat4,
|
||||
projection: &glam::Mat4,
|
||||
camera_position: glam::Vec3,
|
||||
player_position: glam::Vec3,
|
||||
draw_calls: &[DrawCall],
|
||||
billboard_calls: &[BillboardDrawCall],
|
||||
time: f32,
|
||||
delta_time: f32,
|
||||
debug_mode: DebugMode,
|
||||
) -> wgpu::SurfaceTexture
|
||||
{
|
||||
with_mut(|r| {
|
||||
r.render(
|
||||
view,
|
||||
projection,
|
||||
camera_position,
|
||||
player_position,
|
||||
draw_calls,
|
||||
billboard_calls,
|
||||
time,
|
||||
delta_time,
|
||||
debug_mode,
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
pub mod billboard;
|
||||
mod bind_group;
|
||||
mod debug_overlay;
|
||||
mod global;
|
||||
mod pipeline;
|
||||
mod shadow;
|
||||
mod types;
|
||||
|
||||
pub mod billboard;
|
||||
pub mod snow;
|
||||
pub mod snow_light;
|
||||
|
||||
pub use billboard::{BillboardDrawCall, BillboardPipeline};
|
||||
pub use global::{
|
||||
aspect_ratio, init, init_snow_light_accumulation, render, set_selected_entity, set_snow_depth,
|
||||
set_terrain_data, update_spotlights, with_device, with_queue, with_surface_format,
|
||||
};
|
||||
pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS};
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
@@ -18,7 +26,6 @@ use pipeline::{
|
||||
create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline,
|
||||
create_wireframe_pipeline,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
const MAX_DRAW_CALLS: usize = 64;
|
||||
@@ -71,7 +78,7 @@ pub struct Renderer
|
||||
dummy_snow_light_view: wgpu::TextureView,
|
||||
dummy_snow_light_sampler: wgpu::Sampler,
|
||||
|
||||
snow_light_accumulation: Option<crate::snow_light::SnowLightAccumulation>,
|
||||
snow_light_accumulation: Option<snow_light::SnowLightAccumulation>,
|
||||
snow_light_bound: bool,
|
||||
|
||||
pub selected_entity: Option<EntityHandle>,
|
||||
@@ -1037,12 +1044,8 @@ impl Renderer
|
||||
|
||||
pub fn init_snow_light_accumulation(&mut self, terrain_min: glam::Vec2, terrain_max: glam::Vec2)
|
||||
{
|
||||
let snow_light_accumulation = crate::snow_light::SnowLightAccumulation::new(
|
||||
&self.device,
|
||||
terrain_min,
|
||||
terrain_max,
|
||||
512,
|
||||
);
|
||||
let snow_light_accumulation =
|
||||
snow_light::SnowLightAccumulation::new(&self.device, terrain_min, terrain_max, 512);
|
||||
|
||||
self.snow_light_accumulation = Some(snow_light_accumulation);
|
||||
}
|
||||
@@ -1093,137 +1096,3 @@ impl Renderer
|
||||
self.config.width as f32 / self.config.height as f32
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static GLOBAL_RENDERER: RefCell<Option<Renderer>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub fn init(renderer: Renderer)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer));
|
||||
}
|
||||
|
||||
pub fn with_device<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&wgpu::Device) -> R,
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not set");
|
||||
f(&renderer.device)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_queue<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&wgpu::Queue) -> R,
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not set");
|
||||
f(&renderer.queue)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_terrain_data()
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.set_terrain_data();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init_snow_light_accumulation(terrain_min: glam::Vec2, terrain_max: glam::Vec2)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.init_snow_light_accumulation(terrain_min, terrain_max);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_snow_depth(snow_depth_view: &wgpu::TextureView)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.set_snow_depth(snow_depth_view);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn aspect_ratio() -> f32
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not set");
|
||||
renderer.aspect_ratio()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
view: &glam::Mat4,
|
||||
projection: &glam::Mat4,
|
||||
camera_position: glam::Vec3,
|
||||
player_position: glam::Vec3,
|
||||
draw_calls: &[DrawCall],
|
||||
billboard_calls: &[BillboardDrawCall],
|
||||
time: f32,
|
||||
delta_time: f32,
|
||||
debug_mode: DebugMode,
|
||||
) -> wgpu::SurfaceTexture
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.render(
|
||||
view,
|
||||
projection,
|
||||
camera_position,
|
||||
player_position,
|
||||
draw_calls,
|
||||
billboard_calls,
|
||||
time,
|
||||
delta_time,
|
||||
debug_mode,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_surface_format<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(wgpu::TextureFormat) -> R,
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not set");
|
||||
f(renderer.config.format)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_shadow_bias(bias: f32)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.shadow_bias = bias;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_spotlights(spotlights: Vec<Spotlight>)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.spotlights = spotlights;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_selected_entity(entity: Option<EntityHandle>)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.selected_entity = entity;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ use exr::prelude::{ReadChannels, ReadLayers};
|
||||
use glam::{Vec2, Vec3};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use super::{with_device, with_queue, DrawCall, Pipeline};
|
||||
use crate::{
|
||||
loaders::mesh::{InstanceRaw, Mesh, Vertex},
|
||||
paths,
|
||||
render::{self, DrawCall, Pipeline},
|
||||
texture::HeightmapTexture,
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ pub struct SnowLayer
|
||||
|
||||
fn create_instance_buffer() -> wgpu::Buffer
|
||||
{
|
||||
render::with_device(|device| {
|
||||
with_device(|device| {
|
||||
device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Snow Clipmap Instance Buffer"),
|
||||
size: std::mem::size_of::<InstanceRaw>() as u64,
|
||||
@@ -107,13 +107,11 @@ impl SnowLayer
|
||||
let (deform_pipeline, deform_bind_group, deform_params_buffer) =
|
||||
Self::create_deform_pipeline(&depth_texture_view);
|
||||
|
||||
let heightmap_texture = render::with_device(|device| {
|
||||
render::with_queue(|queue| {
|
||||
HeightmapTexture::load(device, queue, &config.heightmap_path)
|
||||
})
|
||||
let heightmap_texture = with_device(|device| {
|
||||
with_queue(|queue| HeightmapTexture::load(device, queue, &config.heightmap_path))
|
||||
})?;
|
||||
|
||||
let depth_sampler = render::with_device(|device| {
|
||||
let depth_sampler = with_device(|device| {
|
||||
device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Snow Depth Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
@@ -226,7 +224,7 @@ impl SnowLayer
|
||||
height: u32,
|
||||
) -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup)
|
||||
{
|
||||
render::with_device(|device| {
|
||||
with_device(|device| {
|
||||
let size = wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
@@ -248,7 +246,7 @@ impl SnowLayer
|
||||
|
||||
let data_bytes: &[u8] = bytemuck::cast_slice(depth_data);
|
||||
|
||||
render::with_queue(|queue| {
|
||||
with_queue(|queue| {
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &texture,
|
||||
@@ -300,7 +298,7 @@ impl SnowLayer
|
||||
depth_texture_view: &wgpu::TextureView,
|
||||
) -> (wgpu::ComputePipeline, wgpu::BindGroup, wgpu::Buffer)
|
||||
{
|
||||
render::with_device(|device| {
|
||||
with_device(|device| {
|
||||
let shader_source = std::fs::read_to_string(&paths::shaders::snow_deform())
|
||||
.expect("Failed to load snow deform shader");
|
||||
|
||||
@@ -383,7 +381,7 @@ impl SnowLayer
|
||||
depth_sampler: &wgpu::Sampler,
|
||||
) -> wgpu::BindGroup
|
||||
{
|
||||
render::with_device(|device| {
|
||||
with_device(|device| {
|
||||
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Snow Displacement Bind Group Layout"),
|
||||
entries: &[
|
||||
@@ -490,7 +488,7 @@ impl SnowLayer
|
||||
}
|
||||
}
|
||||
|
||||
let vertex_buffer = render::with_device(|device| {
|
||||
let vertex_buffer = with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("Snow Clipmap Level {} Vertex Buffer", level)),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
@@ -498,7 +496,7 @@ impl SnowLayer
|
||||
})
|
||||
});
|
||||
|
||||
let index_buffer = render::with_device(|device| {
|
||||
let index_buffer = with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("Snow Clipmap Level {} Index Buffer", level)),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
@@ -542,7 +540,7 @@ impl SnowLayer
|
||||
}
|
||||
}
|
||||
|
||||
render::with_queue(|queue| {
|
||||
with_queue(|queue| {
|
||||
for (level, clipmap_level) in self.levels.iter().enumerate()
|
||||
{
|
||||
let cell_size = self.clipmap_config.base_cell_size * (1u32 << level) as f32;
|
||||
@@ -589,7 +587,7 @@ impl SnowLayer
|
||||
|
||||
pub fn deform_at_position(&self, position: Vec3, radius: f32, depth: f32)
|
||||
{
|
||||
render::with_queue(|queue| {
|
||||
with_queue(|queue| {
|
||||
let params_data = [
|
||||
position.x,
|
||||
position.z,
|
||||
@@ -605,7 +603,7 @@ impl SnowLayer
|
||||
queue.write_buffer(&self.deform_params_buffer, 0, params_bytes);
|
||||
});
|
||||
|
||||
render::with_device(|device| {
|
||||
with_device(|device| {
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Snow Deform Encoder"),
|
||||
});
|
||||
@@ -625,7 +623,7 @@ impl SnowLayer
|
||||
compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, 1);
|
||||
}
|
||||
|
||||
render::with_queue(|queue| {
|
||||
with_queue(|queue| {
|
||||
queue.submit(Some(encoder.finish()));
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::{Spotlight, SpotlightRaw, MAX_SPOTLIGHTS};
|
||||
use crate::paths;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::Vec2;
|
||||
@@ -13,12 +14,12 @@ struct AccumulationUniforms
|
||||
delta_time: f32,
|
||||
spotlight_count: u32,
|
||||
_padding: u32,
|
||||
light_view_projections: [[[f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS],
|
||||
light_view_projections: [[[f32; 4]; 4]; MAX_SPOTLIGHTS],
|
||||
shadow_bias: f32,
|
||||
terrain_height_scale: f32,
|
||||
_padding3: f32,
|
||||
_padding4: f32,
|
||||
spotlights: [crate::render::SpotlightRaw; crate::render::MAX_SPOTLIGHTS],
|
||||
spotlights: [SpotlightRaw; MAX_SPOTLIGHTS],
|
||||
}
|
||||
|
||||
pub struct SnowLightAccumulation
|
||||
@@ -417,7 +418,7 @@ impl SnowLightAccumulation
|
||||
&mut self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
queue: &wgpu::Queue,
|
||||
spotlights: &[crate::render::Spotlight],
|
||||
spotlights: &[Spotlight],
|
||||
delta_time: f32,
|
||||
light_view_projections: &[glam::Mat4],
|
||||
shadow_bias: f32,
|
||||
@@ -430,12 +431,8 @@ impl SnowLightAccumulation
|
||||
self.needs_clear = false;
|
||||
}
|
||||
|
||||
let mut spotlight_array =
|
||||
[crate::render::SpotlightRaw::default(); crate::render::MAX_SPOTLIGHTS];
|
||||
for (i, spotlight) in spotlights
|
||||
.iter()
|
||||
.take(crate::render::MAX_SPOTLIGHTS)
|
||||
.enumerate()
|
||||
let mut spotlight_array = [SpotlightRaw::default(); MAX_SPOTLIGHTS];
|
||||
for (i, spotlight) in spotlights.iter().take(MAX_SPOTLIGHTS).enumerate()
|
||||
{
|
||||
spotlight_array[i] = spotlight.to_raw();
|
||||
}
|
||||
@@ -445,13 +442,13 @@ impl SnowLightAccumulation
|
||||
terrain_max_xz: self.terrain_max.to_array(),
|
||||
decay_rate: self.decay_rate,
|
||||
delta_time,
|
||||
spotlight_count: spotlights.len().min(crate::render::MAX_SPOTLIGHTS) as u32,
|
||||
spotlight_count: spotlights.len().min(MAX_SPOTLIGHTS) as u32,
|
||||
_padding: 0,
|
||||
light_view_projections: {
|
||||
let mut arr = [[[0.0f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS];
|
||||
let mut arr = [[[0.0f32; 4]; 4]; MAX_SPOTLIGHTS];
|
||||
for (i, mat) in light_view_projections
|
||||
.iter()
|
||||
.take(crate::render::MAX_SPOTLIGHTS)
|
||||
.take(MAX_SPOTLIGHTS)
|
||||
.enumerate()
|
||||
{
|
||||
arr[i] = mat.to_cols_array_2d();
|
||||
@@ -1,13 +1,15 @@
|
||||
use crate::snow::SnowLayer;
|
||||
use crate::world::World;
|
||||
|
||||
pub fn snow_system(world: &World, snow_layer: &mut SnowLayer, noclip: bool)
|
||||
pub fn snow_system(world: &mut World, noclip: bool)
|
||||
{
|
||||
let camera_pos = world.active_camera_position();
|
||||
let player_pos = world.player_position();
|
||||
if !noclip
|
||||
if let Some(ref mut snow_layer) = world.snow_layer
|
||||
{
|
||||
snow_layer.deform_at_position(player_pos, 1.5, 10.0);
|
||||
if !noclip
|
||||
{
|
||||
snow_layer.deform_at_position(player_pos, 1.5, 10.0);
|
||||
}
|
||||
snow_layer.update(camera_pos);
|
||||
}
|
||||
snow_layer.update(camera_pos);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ use crate::components::{
|
||||
CameraComponent, InputComponent, JumpComponent, MeshComponent, MovementComponent,
|
||||
PhysicsComponent, RotateComponent,
|
||||
};
|
||||
use crate::debug::DebugMode;
|
||||
use crate::entity::{EntityHandle, EntityManager};
|
||||
use crate::render::snow::SnowLayer;
|
||||
use crate::state::StateMachine;
|
||||
|
||||
pub use crate::utility::transform::Transform;
|
||||
@@ -107,6 +109,10 @@ pub struct World
|
||||
pub bubble_tags: Storage<()>,
|
||||
pub projectile_tags: Storage<()>,
|
||||
pub dialog_outcomes: Vec<DialogOutcomeEvent>,
|
||||
|
||||
// --- singleton state (not per-entity) ---
|
||||
pub snow_layer: Option<SnowLayer>,
|
||||
pub debug_mode: DebugMode,
|
||||
}
|
||||
|
||||
impl World
|
||||
@@ -145,6 +151,8 @@ impl World
|
||||
bubble_tags: Storage::new(),
|
||||
projectile_tags: Storage::new(),
|
||||
dialog_outcomes: Vec::new(),
|
||||
snow_layer: None,
|
||||
debug_mode: DebugMode::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user