render iteration
This commit is contained in:
351
QWEN.md
Normal file
351
QWEN.md
Normal file
@@ -0,0 +1,351 @@
|
||||
# Snow Trail SDL Project
|
||||
|
||||
## Model Usage Guide
|
||||
|
||||
**When interacting with this codebase, I follow a step-by-step, concise approach:**
|
||||
|
||||
1. **Start with exploration**: Read files to understand context before making changes
|
||||
2. **Build incrementally**: Make small, targeted changes and verify them
|
||||
3. **Test after changes**: Run `cargo check` and relevant tests
|
||||
4. **Keep explanations brief**: Code should speak for itself; comments only for complex logic
|
||||
5. **Follow existing patterns**: Mimic the style, structure, and conventions in the codebase
|
||||
|
||||
**For task management**, I use the todo list to track multi-step work:
|
||||
- Mark tasks as `in_progress` when starting
|
||||
- Mark tasks as `completed` immediately after finishing
|
||||
- Add new tasks when scope expands
|
||||
- Never batch multiple completions
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a pure Rust game engine implementation (not a game yet) that serves as a migration from a Godot-based project. It's a 3D game using SDL3 for windowing/input, wgpu for rendering, rapier3d for physics, and features a low-res retro aesthetic with dithering.
|
||||
|
||||
The project implements an ECS (Entity Component System) architecture without engine dependencies, providing core systems for rendering, physics, input handling, and entity management.
|
||||
|
||||
### Key Technologies
|
||||
- **SDL3**: Windowing and input handling (latest stable bindings)
|
||||
- **wgpu**: Modern GPU API abstraction for rendering (Vulkan/Metal/DX12/OpenGL backends)
|
||||
- **rapier3d**: Fast 3D physics engine
|
||||
- **glam**: Fast vector/matrix math library
|
||||
- **gltf**: Loading 3D models in glTF format
|
||||
- **exr**: Loading EXR heightmap files for physics colliders
|
||||
- **kurbo**: Bezier curve evaluation for movement acceleration curves
|
||||
|
||||
### Architecture: Pure ECS
|
||||
- **Entities**: Just IDs (`EntityHandle = u64`), managed by `EntityManager`
|
||||
- **Components**: Pure data structures stored in component storages (HashMap)
|
||||
- **Systems**: Functions that query entities with specific component combinations
|
||||
- **No Rc<RefCell<>>** - Clean ownership model with components as data in hashmaps
|
||||
- **Component Storages** owned by single `World` struct
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Build Commands
|
||||
```bash
|
||||
cargo build
|
||||
cargo build --release
|
||||
cargo check
|
||||
cargo test
|
||||
cargo run
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
### Shader Compilation
|
||||
The project uses a custom shader compilation system via the `wesl` crate:
|
||||
- WGSL/WESL shaders are compiled at build time via `build.rs`
|
||||
- Shaders are loaded at runtime via `std::fs::read_to_string()`, allowing hot-reloading by restarting the application
|
||||
- Build artifact: `standard` shader package
|
||||
|
||||
### Runtime Behavior
|
||||
- Window resolution: 800×600 (resizable)
|
||||
- Rendering resolution: Low-res framebuffer (160×120) upscaled to window
|
||||
- Target FPS: 60 FPS with fixed timestep physics (1/60s)
|
||||
- Default mode: Noclip camera active
|
||||
- Toggle modes: Press 'N' to toggle noclip/follow modes
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Code Style (from CLAUDE.md)
|
||||
- **NO inline comments unless ABSOLUTELY necessary** - Code must be self-documenting
|
||||
- **Doc comments (`///`)** only for public APIs and complex algorithms
|
||||
- **All `use` statements must be at the file level** (module top), not inside function bodies
|
||||
- **NO inline paths** - always add `use` statements at the top of files
|
||||
- **Formatting**: `brace_style = "AlwaysNextLine"`, `control_brace_style = "AlwaysNextLine"`
|
||||
|
||||
### File Structure
|
||||
```
|
||||
src/
|
||||
├── main.rs - SDL3 event loop, game loop orchestration, system execution order
|
||||
├── entity.rs - EntityManager for entity lifecycle (spawn/despawn/query)
|
||||
├── world.rs - World struct owning all component storages
|
||||
├── camera.rs - 3D camera with rotation and follow behavior
|
||||
├── physics.rs - PhysicsManager singleton (rapier3d world)
|
||||
├── player.rs - Player entity spawning function
|
||||
├── terrain.rs - Terrain entity spawning, glTF loading, EXR heightmap loading
|
||||
├── render.rs - wgpu renderer, pipelines, bind groups, DrawCall execution
|
||||
├── postprocess.rs - Low-res framebuffer and blit shader for upscaling
|
||||
├── mesh.rs - Vertex/Mesh structs, plane/cube mesh generation, glTF loading
|
||||
├── shader.rs - Standard mesh shader (WGSL) with diffuse+ambient lighting
|
||||
├── state.rs - Generic StateMachine implementation
|
||||
├── event.rs - Type-safe event bus (complementary to ECS)
|
||||
├── picking.rs - Ray casting for mouse picking (unused currently)
|
||||
├── heightmap.rs - EXR heightmap loading utilities
|
||||
├── draw.rs - DrawManager (legacy, kept for compatibility)
|
||||
├── texture_loader.rs - Texture loading utilities
|
||||
└── systems/ - ECS systems (input, state_machine, physics_sync, render, camera)
|
||||
├── input.rs
|
||||
├── state_machine.rs
|
||||
├── physics_sync.rs
|
||||
├── render.rs
|
||||
├── camera_follow.rs
|
||||
├── camera_input.rs
|
||||
└── camera_noclip.rs
|
||||
├── components/ - ECS component definitions
|
||||
├── input.rs
|
||||
├── mesh.rs
|
||||
├── movement.rs
|
||||
├── physics.rs
|
||||
├── player_tag.rs
|
||||
├── jump.rs
|
||||
├── camera.rs
|
||||
└── camera_follow.rs
|
||||
├── utility/ - Utility modules
|
||||
├── input.rs - InputState (raw SDL input handling)
|
||||
├── time.rs - Time singleton (game time tracking)
|
||||
└── transform.rs - Transform struct (position/rotation/scale data type)
|
||||
├── debug/ - Debug utilities
|
||||
├── noclip.rs - Noclip camera controller
|
||||
└── render_collider_debug.rs
|
||||
└── shaders/ - WGSL/WESL shader files
|
||||
├── shared.wesl - Shared shader utilities
|
||||
├── standard.wesl - Standard mesh rendering with directional lighting
|
||||
├── terrain.wesl - Terrain rendering with shadow mapping
|
||||
└── blit.wgsl - Fullscreen blit for upscaling low-res framebuffer
|
||||
```
|
||||
|
||||
### System Execution Order (main.rs game loop)
|
||||
1. **SDL Events → InputState**: Poll events, handle raw input
|
||||
2. **InputState → InputComponent**: `player_input_system()` converts raw input to gameplay commands
|
||||
3. **State Machine Update**: `state_machine_physics_system()` and `state_machine_system()`
|
||||
4. **Physics Simulation**: Fixed timestep physics step
|
||||
5. **Physics → Transforms**: `physics_sync_system()` syncs physics bodies to transforms
|
||||
6. **Rendering**: `render_system()` generates DrawCalls, renderer executes pipeline
|
||||
7. **Cleanup**: Clear just-pressed states
|
||||
|
||||
### ECS Component Storages
|
||||
All storages are owned by the `World` struct:
|
||||
- `TransformStorage` - Position, rotation, scale
|
||||
- `MeshStorage` - Mesh data + render pipeline
|
||||
- `PhysicsStorage` - Rapier3d rigidbody/collider handles
|
||||
- `MovementStorage` - Movement config + state
|
||||
- `JumpStorage` - Jump mechanics state
|
||||
- `InputStorage` - Gameplay input commands
|
||||
- `PlayerTagStorage` - Marker for player entities
|
||||
- `StateMachineStorage` - Behavior state machines
|
||||
- `CameraStorage` - Camera components
|
||||
- `CameraFollowStorage` - Camera follow behavior
|
||||
|
||||
### Input Handling (Two-Layer Pipeline)
|
||||
**Layer 1: Raw Input** (`utility/input.rs` - `InputState`):
|
||||
- Global singleton for SDL event handling
|
||||
- Tracks raw hardware state (W/A/S/D pressed, mouse delta, etc.)
|
||||
- Handles SDL events via `handle_event()` method
|
||||
|
||||
**Layer 2: Gameplay Commands** (`components/input.rs` - `InputComponent`):
|
||||
- Per-entity ECS component
|
||||
- Stores processed gameplay commands (move_direction, jump_pressed)
|
||||
- Filled by `player_input_system()` which reads `InputState`
|
||||
|
||||
**Input Flow**: SDL Events → InputState → InputComponent → Movement Systems
|
||||
|
||||
**Current Controls**:
|
||||
- `W/A/S/D`: Movement
|
||||
- `Space`: Jump
|
||||
- `Shift`: Speed boost (noclip mode)
|
||||
- `I`: Toggle mouse capture
|
||||
- `Escape`: Quit game
|
||||
- `N`: Toggle noclip/follow mode
|
||||
- Mouse motion: Camera look (yaw/pitch)
|
||||
|
||||
### Rendering Pipeline
|
||||
- **Low-res framebuffer** (160×120) renders to texture
|
||||
- **Bayer 8×8 dithering** reduces colors to 3-bit RGB (8 colors)
|
||||
- **Final blit pass** upscales framebuffer to window using nearest-neighbor sampling
|
||||
- **Depth buffer** for 3D rendering with proper occlusion
|
||||
- **Multiple render pipelines**: Standard mesh and terrain pipelines
|
||||
- **Directional lighting**: Diffuse + ambient (basic Phong model)
|
||||
|
||||
### Terrain System
|
||||
- **glTF mesh** exported from Blender 5.0 with baked height values in vertices
|
||||
- **EXR heightmap** loaded for physics colliders (single-channel R32Float format)
|
||||
- **Heightfield collider** created directly from EXR data
|
||||
- **No runtime displacement** - vertices rendered directly
|
||||
- **Separate terrain pipeline** for terrain-specific rendering
|
||||
|
||||
### Build System
|
||||
- **`build.rs`**: Custom build script using `wesl` crate
|
||||
- **Shader compilation**: `package::standard` → `standard` artifact
|
||||
- **No external dependencies** needed for shader compilation at build time
|
||||
|
||||
### Dependencies Rationale
|
||||
- **sdl3**: Modern SDL3 bindings for future-proofing
|
||||
- **wgpu**: Modern GPU API with cross-platform support
|
||||
- **rapier3d**: Fast physics engine with good Rust integration
|
||||
- **gltf**: Standard 3D model format for asset pipeline
|
||||
- **exr**: High-dynamic-range heightmap loading for physics
|
||||
- **kurbo**: Bezier curves for smooth movement acceleration
|
||||
- **bytemuck**: Safe byte casting for GPU buffer uploads
|
||||
|
||||
## Current Implementation Status
|
||||
|
||||
### Implemented Features
|
||||
✅ Full ECS architecture (entities, components, systems)
|
||||
✅ SDL3 windowing and input handling
|
||||
✅ wgpu rendering with low-res framebuffer
|
||||
✅ Multiple render pipelines (standard mesh + terrain)
|
||||
✅ Bayer dithering for retro aesthetic
|
||||
✅ glTF mesh loading
|
||||
✅ EXR heightmap loading for physics
|
||||
✅ rapier3d physics integration
|
||||
✅ State machine system (generic implementation)
|
||||
✅ Event bus (complementary to ECS)
|
||||
✅ Camera system (free look + follow modes)
|
||||
✅ Noclip mode for development
|
||||
✅ Two-layer input pipeline
|
||||
|
||||
### In Progress
|
||||
⚠️ Movement system (apply InputComponent → physics velocities)
|
||||
⚠️ Ground detection and collision response
|
||||
⚠️ Player state machine configuration (transitions not yet set up)
|
||||
⚠️ Camera follow behavior (partial implementation)
|
||||
⚠️ Snow deformation compute shaders
|
||||
⚠️ Debug UI system
|
||||
|
||||
### Known Limitations
|
||||
- Player entity spawns but is inactive (noclip camera used for testing)
|
||||
- Movement physics not yet connected to input
|
||||
- Ground detection not implemented
|
||||
- State machine transitions not configured
|
||||
- Camera follow needs refinement
|
||||
|
||||
## Content Creation Workflow
|
||||
|
||||
### Blender 5.0 (blender/)
|
||||
- **Terrain modeling**: `terrain.blend`
|
||||
- **Player character**: `player_mesh.blend`
|
||||
- **Export formats**:
|
||||
- glTF: `meshes/` for rendering (baked heights in vertices)
|
||||
- EXR: `textures/` single-channel float heightmap for physics
|
||||
|
||||
### GIMP (gimp/)
|
||||
- **Dither patterns**: `dither_patterns.xcf` (Bayer matrix patterns)
|
||||
|
||||
### Export Process
|
||||
1. Model terrain in Blender 5.0
|
||||
2. Export as glTF with baked height values
|
||||
3. Export same terrain as EXR heightmap
|
||||
4. Both files represent same data (visual/physics sync guaranteed)
|
||||
|
||||
## Future Development
|
||||
|
||||
### Next Steps (from CLAUDE.md)
|
||||
1. Implement `movement_system` to apply `InputComponent` to physics velocities
|
||||
2. Configure player state machine transitions (idle → walking → jumping → falling)
|
||||
3. Implement ground detection (raycasting with QueryPipeline)
|
||||
4. Add camera follow system (tracks player entity)
|
||||
5. Integrate snow deformation compute shaders
|
||||
6. Implement debug UI system for parameter tweaking
|
||||
|
||||
### Testing Strategy
|
||||
- Systems are pure functions (easy to test)
|
||||
- Create multiple `World` instances for isolation
|
||||
- Query patterns are predictable
|
||||
- State machine transitions are testable
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### EXR Heightmap Loading (Physics Only)
|
||||
```rust
|
||||
use exr::prelude::{ReadChannels, ReadLayers};
|
||||
|
||||
let builder = exr::Image::new("heightmap.exr")
|
||||
.no_deep_data()
|
||||
.largest_resolution_level()
|
||||
.all_channels()
|
||||
.all_layers()
|
||||
.all_attributes();
|
||||
```
|
||||
|
||||
### Component Storage Pattern
|
||||
```rust
|
||||
pub struct TransformStorage {
|
||||
pub components: HashMap<EntityHandle, Transform>,
|
||||
}
|
||||
|
||||
impl TransformStorage {
|
||||
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut Transform) -> R,
|
||||
{
|
||||
self.components.get_mut(&entity).map(f)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### State Machine Integration
|
||||
- TypeId-based state identification
|
||||
- Transitions as closures (can capture entity ID)
|
||||
- State callbacks receive `&mut World` for component access
|
||||
- Safe pattern: Remove → Update → Insert to avoid borrow conflicts
|
||||
|
||||
### Event System (Complementary to ECS)
|
||||
- Handles irregular, one-time occurrences
|
||||
- Cross-system messaging without tight coupling
|
||||
- Global `add_listener()` and `emit()` functions
|
||||
- Example: FootstepEvent for snow deformation
|
||||
|
||||
### Shader Hot-Reloading
|
||||
- Shaders loaded at runtime via `std::fs::read_to_string()`
|
||||
- Restart application to reload shaders
|
||||
- No recompilation needed
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Running the Project
|
||||
```bash
|
||||
cd /home/jonas/projects/snow_trail_sdl
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Building for Release
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### Formatting Code
|
||||
```bash
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
### Toggling Modes at Runtime
|
||||
- Press **N** to toggle between noclip and follow modes
|
||||
- Press **I** to toggle mouse capture
|
||||
- Press **Escape** to quit
|
||||
|
||||
### Working with Shaders
|
||||
- Edit `.wesl` files in `src/shaders/`
|
||||
- Changes take effect on application restart
|
||||
- Build script compiles `package::standard` → `standard` artifact
|
||||
|
||||
### Adding New Components
|
||||
1. Define component struct (pure data, no Rc<RefCell>)
|
||||
2. Add storage to `world.rs` (HashMap<EntityHandle, Component>)
|
||||
3. Add storage to `World` struct
|
||||
4. Update `World::despawn()` to clean up component
|
||||
5. Create systems that query and modify the component
|
||||
|
||||
### Adding New Systems
|
||||
1. Add function in `systems/` directory
|
||||
2. Import at top of `main.rs`
|
||||
3. Add to system execution order in game loop
|
||||
4. Systems receive `&mut World` (or `&World` for read-only)
|
||||
Reference in New Issue
Block a user