Compare commits
4 Commits
11b31169b1
...
e6c8c259e7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6c8c259e7 | ||
|
|
a79c824540 | ||
|
|
1ad7b94386 | ||
|
|
d21a467878 |
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: explore
|
||||
description: Explore the codebase to answer architecture questions, locate files, and understand how systems interact. Use this before reading files in the main context. Returns targeted file paths and concise context. Examples: "where does the rule system read from?", "what storages does drag_system touch?", "how does level loading work?"
|
||||
model: claude-haiku-4-5-20251001
|
||||
tools:
|
||||
- mcp__plugin_qmd_qmd__query
|
||||
- mcp__plugin_qmd_qmd__get
|
||||
- mcp__opty__opty_query
|
||||
- mcp__opty__opty_ast
|
||||
- Glob
|
||||
- Grep
|
||||
- Read
|
||||
---
|
||||
|
||||
You are a codebase exploration agent for the snow trail project. Your job is to answer questions about the codebase as concisely as possible.
|
||||
|
||||
## Priority order for information sources
|
||||
|
||||
1. **QMD first** — search the `brain-project` collection with `mcp__plugin_qmd_qmd__query` using lex/vec/hyde sub-queries. Best for architecture and design patterns.
|
||||
2. **Opty** — use `mcp__opty__opty_query` for semantic code search (finding functions, types, system interactions). Use `mcp__opty__opty_ast` for exploring file structure and dependencies.
|
||||
3. **Glob/Grep** — when you need exact pattern matching or file location by name.
|
||||
4. **Read** — only read specific files when you need precise detail (e.g. function signatures, exact field names). Prefer small files or targeted line ranges.
|
||||
|
||||
## Output format
|
||||
|
||||
Return a compact summary with:
|
||||
- The direct answer to the question
|
||||
- Relevant `file:line` references for anything the caller will need to edit
|
||||
- No code blocks unless a snippet is essential to the answer
|
||||
- No re-stating of what you searched — just the findings
|
||||
|
||||
Do not read entire large files. If you need to confirm a type or function signature, use Grep to find the definition line, then Read a narrow range around it.
|
||||
@@ -1,24 +0,0 @@
|
||||
# Build Commands
|
||||
|
||||
## Desktop
|
||||
```bash
|
||||
cargo build # debug
|
||||
cargo build --release # release
|
||||
cargo run # game mode
|
||||
cargo run -- --editor # editor mode
|
||||
```
|
||||
|
||||
## iOS
|
||||
iOS builds require macOS. The project uses a custom SDL3 + wgpu iOS export pipeline. See `brain-project/ios/readme.md` in QMD for the full export guide.
|
||||
|
||||
## Android
|
||||
```bash
|
||||
cargo apk build
|
||||
```
|
||||
|
||||
## Checks
|
||||
```bash
|
||||
cargo check
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
```
|
||||
@@ -1,4 +1,8 @@
|
||||
# WGSL Uniform Buffer Alignment
|
||||
# WGSL Shader Development
|
||||
|
||||
Reference for WGSL shader development, buffer alignment, uniform struct layout, and shader asset management. Load this skill when working on shader code, graphics pipelines, or buffer alignment issues.
|
||||
|
||||
## WGSL Uniform Buffer Alignment
|
||||
|
||||
When creating uniform buffers for WGSL shaders, struct fields must be aligned:
|
||||
|
||||
@@ -12,4 +16,6 @@ When creating uniform buffers for WGSL shaders, struct fields must be aligned:
|
||||
|
||||
Use padding fields to match WGSL struct layout exactly. Prefer `vec4` over individual floats to avoid alignment issues.
|
||||
|
||||
## Shader Organization
|
||||
|
||||
Shaders live in `src/shaders/` and are embedded via `include_str!()`.
|
||||
30
.pi/settings.local.json
Normal file
30
.pi/settings.local.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cargo check:*)",
|
||||
"Bash(cargo build:*)",
|
||||
"Bash(cargo fmt:*)",
|
||||
"Bash(head:*)",
|
||||
"mcp__plugin_qmd_qmd__deep_search",
|
||||
"mcp__plugin_qmd_qmd__query",
|
||||
"mcp__opty__opty_status",
|
||||
"mcp__opty__opty_query",
|
||||
"mcp__opty__opty_ast",
|
||||
"Bash(cargo search:*)",
|
||||
"Bash(cargo info:*)"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/filter-cargo-warnings.py\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
13
CLAUDE.md
13
CLAUDE.md
@@ -10,6 +10,19 @@ Pure Rust game: SDL3 windowing, wgpu rendering, rapier3d physics, low-res retro
|
||||
- **NO inline paths** — always add `use` statements at the top of files, never inline
|
||||
- **NO `use` statements inside functions or impl blocks** — all `use` must be at the file (module) level
|
||||
|
||||
**Storage Parameters:**
|
||||
- Functions should take specific storages they need rather than `&World` or `&mut World`
|
||||
- Pass individual fields (`&world.transforms`, `&mut world.state_machines`) at the call site
|
||||
- This makes data dependencies explicit for both the borrow checker and the reader
|
||||
|
||||
## Architecture
|
||||
|
||||
Pure ECS: entities are IDs, components are plain data in `HashMap<EntityHandle, T>` storages, systems are functions receiving `&mut World`. No `Rc<RefCell<>>`.
|
||||
|
||||
## Sub-Agents & Codebase Exploration
|
||||
|
||||
**Use the `explorer` sub-agent for all codebase work.** Unless the target is trivially obvious (e.g., you already know the exact file path and line number) and unless you are the explorer agent. This includes:
|
||||
- Understanding existing code before making changes
|
||||
- Searching for related functions/types
|
||||
- Investigating bugs or architectural patterns
|
||||
- Finding usages of a function across the codebase
|
||||
|
||||
469
src/main.rs
469
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,64 +157,38 @@ 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);
|
||||
|
||||
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
|
||||
{
|
||||
let frame_start = Instant::now();
|
||||
let time = Time::get_time_elapsed();
|
||||
let delta = (frame_start - last_frame).as_secs_f32();
|
||||
last_frame = frame_start;
|
||||
|
||||
editor.begin_frame();
|
||||
fn process_events(game: &mut Game) -> bool
|
||||
{
|
||||
game.editor.begin_frame();
|
||||
|
||||
while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll()
|
||||
{
|
||||
editor.process_event(&raw_event);
|
||||
game.editor.process_event(&raw_event);
|
||||
let event = Event::from_ll(raw_event);
|
||||
|
||||
match &event
|
||||
{
|
||||
Event::Quit { .. } =>
|
||||
{
|
||||
input_state.quit_requested = true;
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
Event::KeyDown {
|
||||
@@ -176,43 +197,34 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
..
|
||||
} =>
|
||||
{
|
||||
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);
|
||||
}
|
||||
toggle_editor(game);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonDown {
|
||||
mouse_btn: MouseButton::Right,
|
||||
..
|
||||
} if editor.active =>
|
||||
} if game.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);
|
||||
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(&game.window, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonUp {
|
||||
mouse_btn: MouseButton::Right,
|
||||
..
|
||||
} if editor.active =>
|
||||
} if game.editor.active =>
|
||||
{
|
||||
editor.right_mouse_held = false;
|
||||
input_state.mouse_captured = false;
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
||||
game.editor.right_mouse_held = false;
|
||||
game.input_state.mouse_captured = false;
|
||||
game.sdl_context
|
||||
.mouse()
|
||||
.set_relative_mouse_mode(&game.window, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -221,26 +233,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
x,
|
||||
y,
|
||||
..
|
||||
} if editor.active && !editor.wants_mouse() =>
|
||||
} if game.editor.active && !game.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);
|
||||
}
|
||||
}
|
||||
handle_editor_pick(game, *x, *y);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -248,132 +243,102 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
{}
|
||||
}
|
||||
|
||||
if editor.active && (editor.wants_keyboard() || editor.wants_mouse())
|
||||
if game.editor.active && (game.editor.wants_keyboard() || game.editor.wants_mouse())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let capture_changed = input_state.handle_event(&event);
|
||||
if capture_changed && !editor.active
|
||||
let capture_changed = game.input_state.handle_event(&event);
|
||||
if capture_changed && !game.editor.active
|
||||
{
|
||||
sdl_context
|
||||
game.sdl_context
|
||||
.mouse()
|
||||
.set_relative_mouse_mode(&window, input_state.mouse_captured);
|
||||
.set_relative_mouse_mode(&game.window, game.input_state.mouse_captured);
|
||||
}
|
||||
}
|
||||
|
||||
if input_state.quit_requested
|
||||
false
|
||||
}
|
||||
|
||||
fn toggle_editor(game: &mut Game)
|
||||
{
|
||||
game.editor.active = !game.editor.active;
|
||||
if game.editor.active
|
||||
{
|
||||
break 'running;
|
||||
}
|
||||
|
||||
if input_state.debug_cycle_just_pressed
|
||||
{
|
||||
debug_mode = debug_mode.cycle();
|
||||
println!("Debug mode: {:?}", debug_mode);
|
||||
}
|
||||
|
||||
if input_state.f2_just_pressed
|
||||
{
|
||||
editor.show_player_state = !editor.show_player_state;
|
||||
}
|
||||
|
||||
camera_input_system(&mut world, &input_state);
|
||||
|
||||
if editor.active
|
||||
{
|
||||
editor_loop(&mut editor, &mut world, &input_state, &stats, delta);
|
||||
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
|
||||
{
|
||||
let dialog_active = !world.bubble_tags.all().is_empty();
|
||||
if dialog_active
|
||||
{
|
||||
dialog_camera_system(&mut world, delta);
|
||||
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);
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
fn handle_editor_pick(game: &mut Game, x: f32, y: f32)
|
||||
{
|
||||
let view = match camera_view_matrix(&game.world)
|
||||
{
|
||||
camera_follow_system(&mut world);
|
||||
}
|
||||
player_input_system(&mut world, &input_state);
|
||||
if editor.show_player_state
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
let (_, cam) = match game.world.active_camera()
|
||||
{
|
||||
editor.build_hud(&world);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
let physics_start = Instant::now();
|
||||
|
||||
physics_accumulator += delta;
|
||||
|
||||
while physics_accumulator >= FIXED_TIMESTEP
|
||||
fn submit_frame(game: &mut Game, draw_calls: &[render::DrawCall], time: f32, delta: f32)
|
||||
{
|
||||
let (camera_entity, camera_component) = match game.world.active_camera()
|
||||
{
|
||||
state_machine_physics_system(&mut 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;
|
||||
}
|
||||
|
||||
stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;
|
||||
|
||||
state_machine_system(&mut world, delta);
|
||||
|
||||
rotate_system(&mut world, delta);
|
||||
|
||||
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);
|
||||
render::update_spotlights(spotlights);
|
||||
|
||||
snow_system(&world, &mut snow_layer, editor.active);
|
||||
|
||||
let mut draw_calls = render_system(&world);
|
||||
draw_calls.extend(snow_layer.get_draw_calls());
|
||||
|
||||
if debug_mode == DebugMode::Colliders
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
let camera_transform = match game.world.transforms.get(camera_entity)
|
||||
{
|
||||
draw_calls.extend(collider_debug::render_collider_debug());
|
||||
}
|
||||
Some(t) => t,
|
||||
None => return,
|
||||
};
|
||||
let view = match camera_view_matrix(&game.world)
|
||||
{
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
if let Some(view) = camera_view_matrix(&world)
|
||||
{
|
||||
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(&world, camera_transform.position, view_proj);
|
||||
|
||||
stats.draw_call_count = draw_calls.len();
|
||||
stats.fps = 1.0 / delta;
|
||||
stats.frame_ms = delta * 1000.0;
|
||||
dialog_bubble_render_system(&game.world, camera_transform.position, view_proj);
|
||||
|
||||
let frame = render::render(
|
||||
&view,
|
||||
&projection,
|
||||
camera_transform.position,
|
||||
player_pos,
|
||||
&draw_calls,
|
||||
draw_calls,
|
||||
&billboard_calls,
|
||||
time,
|
||||
delta,
|
||||
debug_mode,
|
||||
game.world.debug_mode,
|
||||
);
|
||||
|
||||
if editor.active || editor.show_player_state
|
||||
if game.editor.active || game.editor.show_player_state
|
||||
{
|
||||
let screen_view = frame
|
||||
.texture
|
||||
@@ -383,21 +348,129 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
label: Some("ImGui Encoder"),
|
||||
})
|
||||
});
|
||||
editor.render(&mut encoder, &screen_view);
|
||||
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 game.input_state.debug_cycle_just_pressed
|
||||
{
|
||||
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 ---
|
||||
camera_input_system(&mut game.world, &game.input_state);
|
||||
|
||||
if game.editor.active
|
||||
{
|
||||
editor_loop(
|
||||
&mut game.editor,
|
||||
&mut game.world,
|
||||
&game.input_state,
|
||||
&game.stats,
|
||||
delta,
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
let dialog_active = !game.world.bubble_tags.all().is_empty();
|
||||
if dialog_active
|
||||
{
|
||||
dialog_camera_system(&mut game.world, delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_follow_system(&mut game.world);
|
||||
}
|
||||
player_input_system(&mut game.world, &game.input_state);
|
||||
if game.editor.show_player_state
|
||||
{
|
||||
game.editor.build_hud(&game.world);
|
||||
}
|
||||
}
|
||||
|
||||
input_state.clear_just_pressed();
|
||||
// --- fixed-step physics ---
|
||||
let physics_start = Instant::now();
|
||||
game.physics_accumulator += delta;
|
||||
|
||||
while game.physics_accumulator >= FIXED_TIMESTEP
|
||||
{
|
||||
state_machine_physics_system(&mut game.world, FIXED_TIMESTEP);
|
||||
PhysicsManager::physics_step();
|
||||
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;
|
||||
}
|
||||
|
||||
game.stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;
|
||||
|
||||
// --- per-frame systems ---
|
||||
state_machine_system(&mut game.world, delta);
|
||||
rotate_system(&mut game.world, delta);
|
||||
|
||||
tree_occlusion_system(&mut game.world);
|
||||
tree_dissolve_update_system(&mut game.world, delta);
|
||||
tree_instance_buffer_update_system(&mut game.world);
|
||||
|
||||
let spotlights = spotlight_sync_system(&game.world);
|
||||
render::update_spotlights(spotlights);
|
||||
|
||||
snow_system(&mut game.world, game.editor.active);
|
||||
|
||||
// --- 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 game.world.debug_mode == DebugMode::Colliders
|
||||
{
|
||||
draw_calls.extend(collider_debug::render_collider_debug());
|
||||
}
|
||||
|
||||
game.stats.draw_call_count = draw_calls.len();
|
||||
game.stats.fps = 1.0 / delta;
|
||||
game.stats.frame_ms = delta * 1000.0;
|
||||
|
||||
// --- render ---
|
||||
submit_frame(&mut game, &draw_calls, time, delta);
|
||||
|
||||
// --- 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 let Some(ref mut snow_layer) = world.snow_layer
|
||||
{
|
||||
if !noclip
|
||||
{
|
||||
snow_layer.deform_at_position(player_pos, 1.5, 10.0);
|
||||
}
|
||||
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