render iteration

This commit is contained in:
Jonas H
2026-02-08 14:06:35 +01:00
parent 2422106725
commit 82c3e1e3b0
67 changed files with 6381 additions and 1564 deletions

351
QWEN.md Normal file
View 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)