From 5d2eca0393c9f9a875641f565093f7793342af29 Mon Sep 17 00:00:00 2001 From: Jonas H Date: Thu, 1 Jan 2026 19:54:00 +0100 Subject: [PATCH] rendering, physics, player and camera WIP --- CLAUDE.md | 656 +++++++++ Cargo.lock | 2457 +++++++++++++++++++++++++++++++ Cargo.toml | 19 + blender/player_mesh.blend | Bin 0 -> 112573 bytes blender/player_mesh.blend1 | Bin 0 -> 112566 bytes meshes/burrs.gltf | 195 +++ meshes/player_mesh.glb | Bin 0 -> 27756 bytes rustfmt.toml | 2 + shaders/blit.wgsl | 28 + shaders/standard.wgsl | 97 ++ shaders/terrain.wgsl | 120 ++ src/camera.rs | 168 +++ src/components/camera.rs | 61 + src/components/camera_follow.rs | 32 + src/components/input.rs | 9 + src/components/jump.rs | 82 ++ src/components/mesh.rs | 11 + src/components/mod.rs | 16 + src/components/movement.rs | 73 + src/components/physics.rs | 8 + src/components/player_tag.rs | 2 + src/components/state_machine.rs | 1 + src/debug/collider_debug.rs | 149 ++ src/debug/mod.rs | 5 + src/debug/noclip.rs | 59 + src/draw.rs | 71 + src/entity.rs | 43 + src/event.rs | 70 + src/heightmap.rs | 67 + src/main.rs | 233 +++ src/mesh.rs | 414 ++++++ src/physics.rs | 288 ++++ src/picking.rs | 87 ++ src/player.rs | 604 ++++++++ src/postprocess.rs | 200 +++ src/render.rs | 800 ++++++++++ src/shader.rs | 66 + src/state.rs | 141 ++ src/systems/camera.rs | 202 +++ src/systems/input.rs | 58 + src/systems/mod.rs | 14 + src/systems/physics_sync.rs | 24 + src/systems/render.rs | 23 + src/systems/state_machine.rs | 38 + src/terrain.rs | 167 +++ src/utility/input.rs | 188 +++ src/utility/mod.rs | 3 + src/utility/time.rs | 22 + src/utility/transform.rs | 143 ++ src/world.rs | 518 +++++++ textures/height_map_x0_y0.exr | Bin 0 -> 20816 bytes 51 files changed, 8734 insertions(+) create mode 100644 CLAUDE.md create mode 100755 Cargo.lock create mode 100644 Cargo.toml create mode 100644 blender/player_mesh.blend create mode 100644 blender/player_mesh.blend1 create mode 100755 meshes/burrs.gltf create mode 100644 meshes/player_mesh.glb create mode 100644 rustfmt.toml create mode 100644 shaders/blit.wgsl create mode 100644 shaders/standard.wgsl create mode 100644 shaders/terrain.wgsl create mode 100644 src/camera.rs create mode 100644 src/components/camera.rs create mode 100644 src/components/camera_follow.rs create mode 100644 src/components/input.rs create mode 100644 src/components/jump.rs create mode 100644 src/components/mesh.rs create mode 100644 src/components/mod.rs create mode 100644 src/components/movement.rs create mode 100644 src/components/physics.rs create mode 100644 src/components/player_tag.rs create mode 100644 src/components/state_machine.rs create mode 100644 src/debug/collider_debug.rs create mode 100644 src/debug/mod.rs create mode 100644 src/debug/noclip.rs create mode 100644 src/draw.rs create mode 100644 src/entity.rs create mode 100644 src/event.rs create mode 100644 src/heightmap.rs create mode 100755 src/main.rs create mode 100644 src/mesh.rs create mode 100644 src/physics.rs create mode 100644 src/picking.rs create mode 100644 src/player.rs create mode 100644 src/postprocess.rs create mode 100644 src/render.rs create mode 100644 src/shader.rs create mode 100644 src/state.rs create mode 100644 src/systems/camera.rs create mode 100644 src/systems/input.rs create mode 100644 src/systems/mod.rs create mode 100644 src/systems/physics_sync.rs create mode 100644 src/systems/render.rs create mode 100644 src/systems/state_machine.rs create mode 100644 src/terrain.rs create mode 100755 src/utility/input.rs create mode 100644 src/utility/mod.rs create mode 100644 src/utility/time.rs create mode 100644 src/utility/transform.rs create mode 100644 src/world.rs create mode 100755 textures/height_map_x0_y0.exr diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..74b0704 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,656 @@ +# CLAUDE.md + +This file provides guidance to Claude Code when working with code in this repository. + +## Project Overview + +This is a pure Rust game project using SDL3 for windowing/input, wgpu for rendering, rapier3d for physics, and a low-res retro aesthetic with dithering. This is a migration from the Godot-based snow_trail project, implementing the same snow deformation system and character controller without engine dependencies. + +## Code Style + +**Code Documentation Guidelines**: +- **NO inline comments unless ABSOLUTELY necessary** +- Code must be self-documenting through clear naming and structure +- Use doc comments (`///`) only for public APIs and complex algorithms +- Avoid obvious comments that restate what the code does +- Let the code speak for itself + +**Formatting:** +- All code must follow the project's `rustfmt.toml` configuration +- Always run `cargo fmt` before committing to ensure consistent formatting +- Current rustfmt settings: brace_style = "AlwaysNextLine", control_brace_style = "AlwaysNextLine" +- **NO inline paths** - always add `use` statements at the top of files (e.g., `use std::rc::Rc;` instead of `std::rc::Rc` inline in code) +- **NO inline `use` statements in functions** - all `use` statements must be at the file level (module top), not inside function bodies or impl blocks + +## Architecture Decisions + +### ECS Architecture + +The project uses a **pure ECS (Entity Component System)** architecture: + +**Entities:** +- Just IDs (`EntityHandle = u64`) +- Managed by `EntityManager` (spawn/despawn) +- No data themselves - just containers for components + +**Components:** +- Pure data structures stored in component storages +- Each storage is a `HashMap` +- No `Rc>` - clean ownership model +- Components: Transform, Mesh, Physics, Movement, Input, PlayerTag, StateMachine + +**Systems:** +- Functions that query entities with specific component combinations +- Run each frame in defined order +- Read from and write to component storages +- Examples: `player_input_system`, `state_machine_system`, `physics_sync_system`, `render_system` + +**Component Storages (World-owned):** +- `TransformStorage` - Position, rotation, scale +- `MeshStorage` - Mesh data + render pipeline +- `PhysicsStorage` - Rapier3d rigidbody/collider handles +- `MovementStorage` - Movement config + state +- `InputStorage` - Gameplay input commands +- `PlayerTagStorage` - Marker for player entities +- `StateMachineStorage` - Behavior state machines +- All storages owned by single `World` struct for clean ownership + +**Key Benefits:** +- No `Rc>` needed - components are just data +- Clear data flow through systems +- Easy to add/remove components at runtime +- Testable - systems are pure functions +- StateMachine integrates as a component for complex behaviors +- EventBus remains for irregular events and cross-system messaging + +### SDL3 vs SDL2 +We are using SDL3 (latest stable bindings) rather than SDL2. SDL3 provides: +- Modern GPU API integration +- Better input handling +- Active development and future-proofing + +As of December 2025, SDL3 Rust bindings are usable but still maturing: +- `sdl3` crate: v0.16.2 (high-level bindings) +- `sdl3-sys` crate: v0.5.11 (low-level FFI) +- Some features may be incomplete, but core functionality is stable + +### wgpu for Rendering + +**Using wgpu instead of OpenGL:** +- Modern GPU API abstraction (Vulkan/Metal/DX12/OpenGL backends) +- Better cross-platform support +- WGSL shader language (WebGPU Shading Language) +- Type-safe API with explicit resource management +- Low-res framebuffer rendering with 3-bit RGB dithering (retro aesthetic) + +**Rendering Architecture:** +- wgpu for 3D mesh rendering with custom shaders +- Low-resolution framebuffer (160×120) upscaled to window size +- Bayer 8×8 dithering for 3-bit RGB color (8 colors total) +- Multiple rendering pipelines: standard meshes and terrain +- Separate bind groups for different material types + +### Future: Debug UI +- Debug UI system not yet implemented +- Will be used for real-time parameter tweaking (replacing Godot's exported properties) +- Current debugging relies on println! and recompilation + +## Physics Integration + +Using rapier3d for 3D physics: +- Character controller implemented manually (no built-in CharacterBody equivalent) +- Ground detection via raycasting with QueryPipeline +- Manual rigidbody velocity application +- State machine for movement states (Idle, Walking, Jumping, Falling) + +## Input Handling + +**Two-Layer Input 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 +- Manages global state (mouse capture, quit request, noclip mode) +- Lives in main event loop + +**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` +- Used by movement systems to control entities +- Decouples input source from entity control + +**Input Flow:** +``` +SDL Events → InputState → player_input_system() → InputComponent → movement_system() +``` + +**Current Input Layout:** +- `W/A/S/D`: Movement (converted to Vec3 direction in InputComponent) +- `Space`: Jump (sets jump_pressed in InputComponent) +- `Shift`: Speed boost (for noclip camera) +- `I`: Toggle mouse capture (lock/unlock cursor) +- `Escape`: Quit game +- `N`: Toggle noclip mode +- Mouse motion: Camera look (yaw/pitch) + +## Rendering Pipeline + +**wgpu Rendering System:** +- 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 + +**Terrain Height Deformation:** +- EXR heightmap files loaded via `exr` crate (single-channel R32Float format) +- Height displacement applied in vertex shader +- Separate terrain pipeline with texture sampling in vertex stage +- TerrainUniforms includes height_scale parameter for tweaking displacement strength +- R32Float textures require non-filterable samplers (FilterMode::Nearest) + +**Lighting Model:** +- Directional light (like Godot's DirectionalLight3D) +- Diffuse + ambient lighting (basic Phong model, no specular) +- Light direction is uniform across entire scene +- No attenuation or distance falloff +- Dithering applied after lighting calculations + +## Migration from Godot + +This project ports the snow_trail Godot project (located at `~/shared/projects/snow_trail`) to pure Rust: + +**What carries over:** +- Snow deformation compute shader logic (GLSL can be reused with minor adjustments) +- Character controller state machine architecture +- Movement physics parameters +- Camera follow behavior + +**What changes:** +- No `Base` pattern → **Pure ECS with EntityHandle + Components** +- No Godot scene tree → **Entity-Component-System architecture** +- No exported properties → Components with data (debug UI planned for future) +- rapier3d RigidBodyHandle in PhysicsComponent instead of Gd +- Manual ground detection instead of CharacterBody3D.is_on_floor() +- **Component storages** (TransformStorage, MeshStorage, etc.) instead of Godot nodes +- **Systems** (player_input_system, state_machine_system, etc.) instead of _process() +- **No `Rc>`** - components are just data in hashmaps +- Event bus implemented from scratch (complementary to systems) +- State machine implemented from scratch (integrates as ECS component) + +## Build Commands + +```bash +cargo build +cargo build --release +cargo check +cargo test +cargo run +cargo fmt +``` + +## Shader Files + +WGSL shaders are stored in the `shaders/` directory: +- `shaders/standard.wgsl` - Standard mesh rendering with directional lighting +- `shaders/terrain.wgsl` - Terrain rendering with height displacement +- `shaders/blit.wgsl` - Fullscreen blit for upscaling low-res framebuffer + +Shaders are loaded at runtime via `std::fs::read_to_string()`, allowing hot-reloading by restarting the application. + +## Module Structure + +**Core:** +- `main.rs` - SDL3 event loop, game loop orchestration, system execution order +- `entity.rs` - EntityManager for entity lifecycle (spawn/despawn/query) +- `world.rs` - World struct that owns all component storages and EntityManager + +**ECS Components (`components/`):** +- `input.rs` - InputComponent (gameplay commands) +- `mesh.rs` - MeshComponent (mesh + pipeline) +- `movement.rs` - MovementComponent (movement config/state) +- `physics.rs` - PhysicsComponent (rigidbody/collider handles) +- `player_tag.rs` - PlayerTag marker component +- `state_machine.rs` - (empty, StateMachine defined in state.rs) +- Note: Component *storages* are defined in `world.rs`, not in component files + +**ECS Systems (`systems/`):** +- `input.rs` - player_input_system (InputState → InputComponent) +- `state_machine.rs` - state_machine_system (updates all state machines) +- `physics_sync.rs` - physics_sync_system (physics → transforms) +- `render.rs` - render_system (queries entities, generates DrawCalls) + +**Rendering:** +- `render.rs` - wgpu renderer, pipelines, bind groups, DrawCall execution +- `shader.rs` - Standard mesh shader (WGSL) with diffuse+ambient lighting +- `terrain.rs` - Terrain mesh generation and pipeline creation +- `postprocess.rs` - Low-res framebuffer and blit shader for upscaling +- `mesh.rs` - Vertex/Mesh structs, plane/cube mesh generation, glTF loading +- `heightmap.rs` - EXR heightmap loading using `exr` crate +- `draw.rs` - DrawManager (legacy, kept for compatibility) + +**Game Logic:** +- `player.rs` - Player entity spawning function +- `camera.rs` - 3D camera with rotation and follow behavior +- `movement.rs` - Movement configuration and state structs +- `state.rs` - Generic StateMachine implementation +- `physics.rs` - PhysicsManager singleton (rapier3d world) + +**Utilities:** +- `utility/input.rs` - InputState (raw SDL input handling) +- `utility/time.rs` - Time singleton (game time tracking) +- `utility/transform.rs` - Transform struct (position/rotation/scale data type) + +**Debug:** +- `debug/noclip.rs` - Noclip camera controller for development + +**Other:** +- `event.rs` - Type-safe event bus (complementary to ECS for irregular events) +- `picking.rs` - Ray casting for mouse picking (unused currently) + +## Dependencies Rationale + +- **sdl3**: Windowing, input events, and platform integration +- **wgpu**: Modern GPU API abstraction for rendering (Vulkan/Metal/DX12 backends) +- **pollster**: Simple blocking executor for async wgpu initialization +- **rapier3d**: Fast physics engine with good Rust integration +- **glam**: Fast vector/matrix math library (vec3, mat4, quaternions) +- **nalgebra**: Linear algebra for rapier3d integration (Isometry3 conversions) +- **bytemuck**: Safe byte casting for GPU buffer uploads (Pod/Zeroable for vertex data) +- **anyhow**: Ergonomic error handling +- **gltf**: Loading 3D models in glTF format +- **exr**: Loading EXR heightmap files (single-channel float data) +- **image**: Image loading and processing (includes EXR support) +- **half**: Float16 support (dependency of exr) +- **kurbo**: Bezier curve evaluation for movement acceleration curves + +## Technical Notes + +### EXR Heightmap Loading +When loading EXR files with the `exr` crate: +- Must import traits: `use exr::prelude::{ReadChannels, ReadLayers};` +- Use builder pattern: `.no_deep_data().largest_resolution_level().all_channels().all_layers().all_attributes().from_file(path)` +- Extract float data: `channel.sample_data.values_as_f32().collect()` +- Create R32Float texture for height data +- R32Float is non-filterable, requires `FilterMode::Nearest` sampler + +### wgpu Texture Formats +- R32Float = single-channel 32-bit float, **non-filterable** +- Use `TextureSampleType::Float { filterable: false }` in bind group layout +- Use `SamplerBindingType::NonFiltering` for sampler binding +- Attempting linear filtering on R32Float causes validation errors + +### Multiple Render Pipelines +- `Pipeline` enum determines which pipeline to use per DrawCall +- Different pipelines can have different shaders, bind group layouts, uniforms +- Terrain pipeline: includes height texture binding in vertex stage +- Standard pipeline: basic mesh rendering without height displacement +- Each pipeline writes to its own uniform buffer before rendering + +### ECS Component Storages + +**Pattern:** +All component storages are owned by the `World` struct: + +```rust +pub struct World { + pub entities: EntityManager, + pub transforms: TransformStorage, + pub meshes: MeshStorage, + pub physics: PhysicsStorage, + pub movements: MovementStorage, + pub inputs: InputStorage, + pub player_tags: PlayerTagStorage, + pub state_machines: StateMachineStorage, +} + +pub struct TransformStorage { + pub components: HashMap, +} + +impl TransformStorage { + pub fn insert(&mut self, entity: EntityHandle, component: Transform) { } + pub fn get(&self, entity: EntityHandle) -> Option<&Transform> { } + pub fn with_mut(&mut self, entity: EntityHandle, f: F) -> Option { } + pub fn remove(&mut self, entity: EntityHandle) { } + pub fn all(&self) -> Vec { } +} +``` + +**Key Features:** +- No `Rc>` needed - clean ownership model +- World owns all component data - explicit ownership +- Instance methods instead of static methods +- Systems receive `&mut World` - clear data dependencies +- Easy to test - can create multiple worlds +- Safe lookups return `Option` + +**Example Usage:** +```rust +// Create world and entity +let mut world = World::new(); +let entity = world.spawn(); + +// Insert components via world +world.transforms.insert(entity, Transform::IDENTITY); +world.meshes.insert(entity, MeshComponent { ... }); + +// Query and update via world +world.transforms.with_mut(entity, |transform| { + transform.position += velocity * delta; +}); + +// Systems receive world +pub fn my_system(world: &mut World) { + for entity in world.player_tags.all() { + if let Some(input) = world.inputs.get(entity) { + // Process input... + } + } +} + +// Cleanup +world.despawn(entity); // Removes from all storages +``` + +### State Machine as ECS Component + +StateMachine integrates into ECS as a component for complex entity behaviors: + +**State Machine Pattern:** +- StateMachine owns all states via `HashMap>` +- TypeId-based state identification +- Transition conditions are simple closures (can capture entity ID) +- State callbacks receive `&mut World` and can access any component +- Updated by `state_machine_system()` each frame using safe remove/insert pattern + +**Integration Example:** +```rust +// Create state machine for entity +let mut sm = StateMachine::new(Box::new(IdleState { entity })); +sm.add_state(WalkingState { entity }); + +// Transitions can capture entity for checking +sm.add_transition::(move || { + // Note: transitions run before update, so they don't access world + false // Placeholder - implement proper transition logic +}); + +// Insert into world +world.state_machines.insert(entity, sm); + +// States receive world when updated +impl State for IdleState { + fn on_state_update(&mut self, world: &mut World, delta: f32) { + // States can access any component via world + if let Some(input) = world.inputs.get(self.entity) { + // React to input... + } + } +} +``` + +**State Machine System (Safe Pattern):** +```rust +pub fn state_machine_system(world: &mut World, delta: f32) { + let entities: Vec<_> = world.state_machines.all(); + + for entity in entities { + // Temporarily remove state machine to avoid borrow conflicts + if let Some(mut state_machine) = world.state_machines.components.remove(&entity) { + state_machine.update(world, delta); // States can now safely access world + world.state_machines.components.insert(entity, state_machine); + } + } +} +``` + +### Event System (event.rs) + +**Complementary to ECS:** +- Events handle irregular, one-time occurrences +- Systems handle regular per-frame updates +- Events enable cross-system messaging without tight coupling + +**Event Bus Features:** +- No `Clone` requirement on events (fire-and-forget) +- `FnMut` handlers allow stateful callbacks +- Global `add_listener()` and `emit()` functions +- Handlers can access ECS components directly via storages + +**ECS Integration:** +```rust +#[derive(Debug)] +struct FootstepEvent { position: Vec3, force: f32 } +impl Event for FootstepEvent {} + +// System emits event +pub fn foot_contact_system(world: &World) { + for player in world.player_tags.all() { + if is_on_ground(player) { + let pos = world.transforms.get(player).unwrap().position; + emit(&FootstepEvent { position: pos, force: 10.0 }); + } + } +} + +// Event handler (global listener, not part of World) +add_listener(|event: &FootstepEvent| { + snow_terrain::deform_at_position(event.position, event.force); +}); +``` + +**When to use:** +- ✅ One-time events (collision, death, pickup) +- ✅ Cross-system communication (audio, VFX triggers) +- ✅ Spawning/despawning entities +- ❌ Regular updates (use systems instead) + +### ECS Systems + +**System Execution Order (main.rs game loop):** +```rust +let mut world = World::new(); + +'running: loop { + // 1. SDL Events → InputState + for event in event_pump.poll_iter() { + input_state.handle_event(&event); + } + + // 2. InputState → InputComponent (player_input_system) + player_input_system(&mut world, &input_state); + + // 3. Update state machines (state_machine_system) + state_machine_system(&mut world, delta); + + // 4. Simulate physics (PhysicsManager) + PhysicsManager::physics_step(); + + // 5. Sync physics → transforms (physics_sync_system) + physics_sync_system(&mut world); + + // 6. Render (render_system) + let draw_calls = render_system(&world); + render::render(&camera, &draw_calls, time); + + // 7. Cleanup + input_state.clear_just_pressed(); +} +``` + +**System Patterns:** + +**Query Pattern:** +```rust +pub fn my_system(world: &mut World) { + let entities = world.my_storage.all(); // All entities with this component + + for entity in entities { + world.my_storage.with_mut(entity, |component| { + // Process component + }); + } +} +``` + +**Multi-Component Query:** +```rust +pub fn movement_system(world: &mut World) { + for entity in world.player_tags.all() { + if let Some(input) = world.inputs.get(entity) { + if let Some(movement) = world.movements.get(entity) { + world.transforms.with_mut(entity, |transform| { + // Update position based on input + movement + }); + } + } + } +} +``` + +### Movement System (movement.rs) +Configuration and state for character movement physics: + +**Horizontal Movement:** +- `HorizontalMovementConfig`: Parameters for ground movement (acceleration, damping, speed limits) +- `HorizontalMovementState`: Runtime state (input direction, flooring status, surface normal) +- Uses Bezier curves (kurbo::CubicBez) for smooth acceleration ramps +- Separate damping for walking vs idle states + +**Vertical Movement:** +- `VerticalMovementConfig`: Jump parameters (height, duration, air control) +- `VerticalMovementState`: Jump execution tracking (progress, peak detection, abort state) +- Bezier curves for jump height progression over time +- Peak detection allows early jump termination with smooth falloff + +**Key Features:** +- Physics parameters tuned to match Godot prototype +- Curve-based interpolation for responsive feel +- State tracking for ground detection and jump execution +- Configurable air control and momentum limits +- Integration with Time singleton for execution timing + +**Usage Pattern:** +```rust +let config = HorizontalMovementConfig::new(); +let mut state = HorizontalMovementState::new(); + +state.move_input = Vec3::new(input.x, 0.0, input.z); +state.forward_direction = camera.forward(); +state.is_floored = ground_check.is_grounded; + +// Apply movement physics using config + state +``` + +### Time System (utility/time.rs) +Global game time tracking using OnceLock singleton: + +**Implementation:** +```rust +static GAME_START: OnceLock = OnceLock::new(); + +pub struct Time; +impl Time { + pub fn init() { GAME_START.get_or_init(Instant::now); } + pub fn get_time_elapsed() -> f32 { /* ... */ } +} +``` + +**Key Features:** +- Thread-safe singleton using std::sync::OnceLock +- Single initialization point (call Time::init() at startup) +- Returns elapsed time as f32 seconds +- Used for animation, jump timing, and time-based effects +- Zero-cost after initialization (static lookup) + +**Usage:** +```rust +Time::init(); // In main() before game loop + +let time = Time::get_time_elapsed(); // Anywhere in code +``` + +## Current Implementation Status + +### Implemented Features + +**ECS Architecture:** +- ✅ Full ECS conversion completed +- ✅ Entity system with EntityManager (spawn/despawn/query) +- ✅ Component storages (Transform, Mesh, Physics, Movement, Input, PlayerTag, StateMachine) +- ✅ Systems pipeline (input → state machine → physics → physics sync → render) +- ✅ No `Rc>` - clean component ownership +- ✅ Event bus integrated as complementary to systems + +**Core Rendering:** +- ✅ wgpu renderer with Vulkan backend +- ✅ Low-res framebuffer (160×120) with Bayer dithering +- ✅ Multiple render pipelines (standard mesh + terrain) +- ✅ Directional lighting with diffuse + ambient +- ✅ EXR heightmap loading and terrain displacement +- ✅ glTF mesh loading +- ✅ render_system (ECS-based DrawCall generation) + +**Input System:** +- ✅ Two-layer input pipeline (InputState → InputComponent) +- ✅ player_input_system converts raw input to gameplay commands +- ✅ SDL event handling in InputState +- ✅ Per-entity InputComponent for controllable entities + +**Camera & Debug:** +- ✅ 3D camera with rotation (yaw/pitch) +- ✅ Noclip mode for development (in debug/noclip.rs) +- ✅ Mouse look with relative mouse mode +- ✅ Toggle with 'I' key, 'N' for noclip mode + +**Physics:** +- ✅ rapier3d integration with PhysicsManager singleton +- ✅ PhysicsComponent storage (rigidbody/collider handles) +- ✅ physics_sync_system (syncs physics → transforms) +- ✅ Physics step integrated into game loop +- ⚠️ Ground detection not yet implemented +- ⚠️ Movement physics not yet connected + +**State Machines:** +- ✅ Generic StateMachine implementation +- ✅ StateMachineStorage (ECS component) +- ✅ state_machine_system updates all state machines +- ✅ Transitions can query ECS components +- ⚠️ Player state transitions not yet configured + +**Player:** +- ✅ Player entity spawning function +- ✅ Components: Transform, Mesh, Physics, Movement, Input, PlayerTag +- ⚠️ Movement system not yet implemented +- ⚠️ State machine not yet attached to player +- ⚠️ Currently inactive (noclip camera used instead) + +**Movement Configuration:** +- ✅ Horizontal movement config (Bezier acceleration curves) +- ✅ Vertical movement config (jump mechanics) +- ✅ MovementComponent storage +- ⚠️ Movement system not yet implemented +- ⚠️ Not yet integrated with physics + +### Not Yet Implemented + +- ❌ Movement system (apply InputComponent → physics velocities) +- ❌ Ground detection and collision response +- ❌ Player state machine configuration +- ❌ Camera follow behavior (tracks player entity) +- ❌ Snow deformation compute shaders +- ❌ Debug UI system + +### Current Focus + +**ECS migration is complete!** The architecture is now fully entity-component-system based with clean separation of data and logic. The next steps are: + +1. Implement movement_system to apply InputComponent to physics +2. Configure player state machine transitions +3. Implement ground detection +4. Add camera follow system +5. Integrate snow deformation + +The noclip camera mode serves as the primary navigation method for testing. Press 'N' to toggle noclip mode. diff --git a/Cargo.lock b/Cargo.lock new file mode 100755 index 0000000..e353c72 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2457 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.17", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitstream-io" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cc" +version = "1.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags", + "core-foundation", + "libc", +] + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + +[[package]] +name = "glam" +version = "0.30.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gltf" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" +dependencies = [ + "base64", + "byteorder", + "gltf-json", + "image", + "lazy_static", + "serde_json", + "urlencoding", +] + +[[package]] +name = "gltf-derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gltf-json" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core 0.5.0", + "zune-jpeg 0.5.7", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "metal" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.17", + "unicode-ident", +] + +[[package]] +name = "nalgebra" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" +dependencies = [ + "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.9", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "parry3d" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e99471b7b6870f7fe406d5611dd4b4c9b07aa3e5436b1d27e1515f9832bb0c6b" +dependencies = [ + "approx", + "arrayvec", + "bitflags", + "downcast-rs", + "either", + "ena", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rstar", + "simba", + "slab", + "smallvec", + "spade", + "static_assertions", + "thiserror 2.0.17", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "portable-atomic" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "rapier3d" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68073fdc88f6b709002767ce8deffffb05ac06824bf9f98a23e270bcea64ba9f" +dependencies = [ + "approx", + "arrayvec", + "bit-vec", + "bitflags", + "downcast-rs", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "parry3d", + "profiling", + "rustc-hash 2.1.1", + "simba", + "static_assertions", + "thiserror 2.0.17", + "wide", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "thiserror 2.0.17", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdl3" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ad6a0142275b5a39c20051c071ab6719068d3ecb345cc96f78b9818e73e44a" +dependencies = [ + "bitflags", + "lazy_static", + "libc", + "objc2", + "raw-window-handle", + "sdl3-sys", +] + +[[package]] +name = "sdl3-sys" +version = "0.5.11+SDL3-3.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73979b5f78819ede7fb6b7534161fe70f3d7a56cc09e7e29c7b58c2b525abfa6" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snow_trail_sdl" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytemuck", + "exr", + "glam 0.30.9", + "gltf", + "half", + "image", + "kurbo", + "nalgebra", + "pollster", + "rapier3d", + "sdl3", + "wgpu", +] + +[[package]] +name = "spade" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" +dependencies = [ + "hashbrown 0.15.5", + "num-traits", + "robust", + "smallvec", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.17", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "27.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.17", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows", + "windows-core", +] + +[[package]] +name = "wgpu-types" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +dependencies = [ + "bitflags", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.17", + "web-sys", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28" +dependencies = [ + "zune-core 0.5.0", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0536837 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "snow_trail_sdl" +version = "0.1.0" +edition = "2021" + +[dependencies] +sdl3 = { version = "0.16", features = ["raw-window-handle"] } +wgpu = "27" +pollster = "0.3" +glam = "0.30" +anyhow = "1.0" +rapier3d = "0.31" +bytemuck = { version = "1.14", features = ["derive"] } +gltf = "1.4" +image = { version = "0.25", features = ["exr"] } +exr = "1.72" +half = "2.4" +kurbo = "0.11" +nalgebra = { version = "0.34.1", features = ["convert-glam030"] } diff --git a/blender/player_mesh.blend b/blender/player_mesh.blend new file mode 100644 index 0000000000000000000000000000000000000000..df1d9b23d6f0d53d30c684fa7ae6ddfebc0c240f GIT binary patch literal 112573 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

qy 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 V0J-V_0E`0y00RI3006P-l8^Z33NZix literal 0 HcmV?d00001 diff --git a/blender/player_mesh.blend1 b/blender/player_mesh.blend1 new file mode 100644 index 0000000000000000000000000000000000000000..44076c2d810fe3f6d25c8a09208959ad37354ac8 GIT binary patch literal 112566 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

@hxGsF>3S1 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 literal 0 HcmV?d00001 diff --git a/meshes/burrs.gltf b/meshes/burrs.gltf new file mode 100755 index 0000000..f54f690 --- /dev/null +++ b/meshes/burrs.gltf @@ -0,0 +1,195 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.3.47", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0, + 1, + 2 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"hook" + }, + { + "name":"hook_thickness", + "rotation":[ + 0.7071068286895752, + 0, + 0, + 0.7071068286895752 + ], + "translation":[ + 0, + 1.12388014793396, + 0 + ] + }, + { + "mesh":1, + "name":"loop" + } + ], + "meshes":[ + { + "name":"hook", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3 + } + ] + }, + { + "name":"loop", + "primitives":[ + { + "attributes":{ + "POSITION":4, + "NORMAL":5, + "TEXCOORD_0":6 + }, + "indices":7 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":82, + "max":[ + 0.25674867630004883, + 1.1536540985107422, + 0.11764351278543472 + ], + "min":[ + -0.18208253383636475, + -0.0005484152352437377, + -0.10493266582489014 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":82, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":82, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":348, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":318, + "max":[ + 0.4516136944293976, + 0.8688175678253174, + 0.49221301078796387 + ], + "min":[ + -0.5062326788902283, + -0.06346084177494049, + -0.40157178044319153 + ], + "type":"VEC3" + }, + { + "bufferView":5, + "componentType":5126, + "count":318, + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":318, + "type":"VEC2" + }, + { + "bufferView":7, + "componentType":5123, + "count":1440, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":984, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":984, + "byteOffset":984, + "target":34962 + }, + { + "buffer":0, + "byteLength":656, + "byteOffset":1968, + "target":34962 + }, + { + "buffer":0, + "byteLength":696, + "byteOffset":2624, + "target":34963 + }, + { + "buffer":0, + "byteLength":3816, + "byteOffset":3320, + "target":34962 + }, + { + "buffer":0, + "byteLength":3816, + "byteOffset":7136, + "target":34962 + }, + { + "buffer":0, + "byteLength":2544, + "byteOffset":10952, + "target":34962 + }, + { + "buffer":0, + "byteLength":2880, + "byteOffset":13496, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":16376, + "uri":"data:application/octet-stream;base64,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AgEsAAIBWgBbAC0AXQBbAF0AAgECAV0AGQACARkAXgBaAAIBXgBaAF4AIAAtAFwAAwEtAAMBYABcACwAYQBcAGEAAwEDAWEAIgADASIAYgBgAAMBYgBgAGIAGwAtAGUABAEtAAQBXQBlAC8AZgBlAGYABAEEAWYAJQAEASUAaABdAAQBaABdAGgAGQAuAGQABQEuAAUBaQBkAC0AYABkAGAABQEFAWAAGwAFARsAawBpAAUBawBpAGsAKQAvAGwABgEvAAYBZgBsACwAWgBsAFoABgEGAVoAIAAGASAAbQBmAAYBbQBmAG0AJQAsAGwABwEsAAcBYQBsAC4AaQBsAGkABwEHAWkAKQAHASkAbwBhAAcBbwBhAG8AIgAwAHMACAEwAAgBcQBzADMAdABzAHQACAEIAXQABgAIAQYAdgBxAAgBdgBxAHYAAQAyAHIACQEyAAkBdwByADEAeAByAHgACQEJAXgAAwAJAQMAegB3AAkBegB3AHoACQAzAHwACgEzAAoBdAB8ADUAfQB8AH0ACgEKAX0ACgAKAQoAfwB0AAoBfwB0AH8ABgA0AHsACwE0AAsBgAB7ADIAdwB7AHcACwELAXcACQALAQkAggCAAAsBggCAAIIADAA1AIMADAE1AAwBfQCDADYAhACDAIQADAEMAYQAJwAMAScAhgB9AAwBhgB9AIYACgA3AIMADQE3AA0BhwCDADQAgACDAIAADQENAYAADAANAQwAiQCHAA0BiQCHAIkAKwA2AIoADgE2AA4BhACKADAAcQCKAHEADgEOAXEAAQAOAQEAjACEAA4BjACEAIwAJwAxAIoADwExAA8BeACKADcAhwCKAIcADwEPAYcAKwAPASsAjgB4AA8BjgB4AI4AAwA4AJEAEAE4ABABjwCRADkAkgCRAJIAEAEQAZIAFQAQARUAlACPABABlACPAJQACwA5AJAAEQE5ABEBlQCQADgAlgCQAJYAEQERAZYADQARAQ0AmACVABEBmACVAJgAFwA5AJkAEgE5ABIBkgCZADoAmgCZAJoAEgESAZoAHQASAR0AnACSABIBnACSAJwAFQA6AJkAEwE6ABMBnQCZADkAlQCZAJUAEwETAZUAFwATARcAnwCdABMBnwCdAJ8AHwA6AKEAFAE6ABQBmgChADsAogChAKIAFAEUAaIAIQAUASEApACaABQBpACaAKQAHQA7AKAAFQE7ABUBpQCgADoAnQCgAJ0AFQEVAZ0AHwAVAR8ApwClABUBpwClAKcAIwA7AKgAFgE7ABYBogCoADwAqgCoAKoAFgEWAaoAJgAWASYAbgCiABYBbgCiAG4AIQA8AKkAFwE8ABcBqwCpADsApQCpAKUAFwEXAaUAIwAXASMAcACrABcBcACrAHAAKgA8AK0AGAE8ABgBqgCtADgAjwCtAI8AGAEYAY8ACwAYAQsAhQCqABgBhQCqAIUAJgA4AKwAGQE4ABkBlgCsADwAqwCsAKsAGQEZAasAKgAZASoAiACWABkBiACWAIgADQA9ALAAGgE9ABoBrgCwAD8AsQCwALEAGgEaAbEAGQAaARkAswCuABoBswCuALMADwA/AK8AGwE/ABsBtACvAD4AtQCvALUAGwEbAbUAEgAbARIAtwC0ABsBtwC0ALcAGwA/ALgAHAE/ABwBsQC4AEAAuQC4ALkAHAEcAbkAHAAcARwAugCxABwBugCxALoAGQBBALgAHQFBAB0BvAC4AD8AtAC4ALQAHQEdAbQAGwAdARsAvQC8AB0BvQC8AL0AHgBAAL8AHgFAAB4BuQC/AEIAwQC/AMEAHgEeAcEAFAAeARQAmwC5AB4BmwC5AJsAHABDAMAAHwFDAB8BwgDAAEEAvADAALwAHwEfAbwAHgAfAR4AngDCAB8BngDCAJ4AFgBCAMQAIAFCACABwQDEAD0ArgDEAK4AIAEgAa4ADwAgAQ8AxQDBACABxQDBAMUAFAA+AMMAIQE+ACEBtQDDAEMAwgDDAMIAIQEhAcIAFgAhARYAyAC1ACEByAC1AMgAEgBEAMsAIgFEACIByQDLAEUAzADLAMwAIgEiAcwABQAiAQUAfgDJACIBfgDJAH4ACgBGAMoAIwFGACMBzQDKAEQAzgDKAM4AIwEjAc4ADAAjAQwAgQDNACMBgQDNAIEACABFAM8AJAFFACQBzADPAEcA0ADPANAAJAEkAdAAEAAkARAA0gDMACQB0gDMANIABQBHAM8AJQFHACUB0wDPAEYAzQDPAM0AJQElAc0ACAAlAQgA1QDTACUB1QDTANUAEwBHANYAJgFHACYB0ADWAEQAyQDWAMkAJgEmAckACgAmAQoA2QDQACYB2QDQANkAEABEANcAJwFEACcBzgDXAEcA0wDXANMAJwEnAdMAEwAnARMA2wDOACcB2wDOANsADABIAN0AKAFIACgB3ADdAEsA3gDdAN4AKAEoAd4ACgAoAQoAkwDcACgBkwDcAJMAFABKAN0AKQFKACkB3wDdAEkA4ADdAOAAKQEpAeAAFgApARYAlwDfACkBlwDfAJcADABLAOEAKgFLACoB3gDhAEwA4gDhAOIAKgEqAeIADwAqAQ8A2ADeACoB2ADeANgACgBNAOEAKwFNACsB4wDhAEoA3wDhAN8AKwErAd8ADAArAQwA2gDjACsB2gDjANoAEgBMAOUALAFMACwB4gDlAEgA3ADlANwALAEsAdwAFAAsARQAxgDiACwBxgDiAMYADwBJAOQALQFJAC0B4ADkAE0A4wDkAOMALQEtAeMAEgAtARIAxwDgAC0BxwDgAMcAFgBPAOcALgFPAC4B5gDnAFAA6ADnAOgALgEuAegAHAAuARwAowDmAC4BowDmAKMAIABQAOcALwFQAC8B6QDnAE4A6gDnAOoALwEvAeoAIgAvASIApgDpAC8BpgDpAKYAHgBQAOsAMAFQADAB6ADrAFEA7QDrAO0AMAEwAe0AGQAwARkAuwDoADABuwDoALsAHABSAOwAMQFSADEB7gDsAFAA6QDsAOkAMQExAekAHgAxAR4AvgDuADEBvgDuAL4AGwBRAO8AMgFRADIB7QDvAE8A5gDvAOYAMgEyAeYAIAAyASAAXwDtADIBXwDtAF8AGQBOAO8AMwFOADMB6gDvAFIA7gDvAO4AMwEzAe4AGwAzARsAYwDqADMBYwDqAGMAIgBTAPEANAFTADQB8ADxAFYA8gDxAPIANAE0AfIADgA0AQ4AsgDwADQBsgDwALIAGABVAPEANQFVADUB8wDxAFQA9ADxAPQANQE1AfQAGgA1ARoAtgDzADUBtgDzALYAEQBWAPUANgFWADYB8gD1AFcA9gD1APYANgE2AfYABAA2AQQA0QDyADYB0QDyANEADgBXAPUANwFXADcB9wD1AFUA8wD1APMANwE3AfMAEQA3AREA1AD3ADcB1AD3ANQABwBXAPkAOAFXADgB9gD5AFgA+gD5APoAOAE4AfoAAAA4AQAAdQD2ADgBdQD2AHUABABYAPgAOQFYADkB+wD4AFcA9wD4APcAOQE5AfcABwA5AQcAeQD7ADkBeQD7AHkAAgBYAPwAOgFYADoB+gD8AFkA/gD8AP4AOgE6Af4AJAA6ASQAiwD6ADoBiwD6AIsAAABZAP0AOwFZADsB/wD9AFgA+wD9APsAOwE7AfsAAgA7AQIAjQD/ADsBjQD/AI0AKABZAAABPAFZADwB/gAAAVMA8AAAAfAAPAE8AfAAGAA8ARgAZwD+ADwBZwD+AGcAJABUAAEBPQFUAD0B9AABAVkA/wABAf8APQE9Af8AKAA9ASgAagD0AD0BagD0AGoAGgA=" + } + ] +} diff --git a/meshes/player_mesh.glb b/meshes/player_mesh.glb new file mode 100644 index 0000000000000000000000000000000000000000..05e5a4b4947555ea09ac3ea2e5ca304eb6ea7d90 GIT binary patch literal 27756 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 literal 0 HcmV?d00001 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3763c44 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +brace_style = "AlwaysNextLine" +control_brace_style = "AlwaysNextLine" diff --git a/shaders/blit.wgsl b/shaders/blit.wgsl new file mode 100644 index 0000000..4b728df --- /dev/null +++ b/shaders/blit.wgsl @@ -0,0 +1,28 @@ +struct VertexInput { + @location(0) position: vec2, + @location(1) uv: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) uv: vec2, +} + +@group(0) @binding(0) +var t_texture: texture_2d; + +@group(0) @binding(1) +var t_sampler: sampler; + +@vertex +fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.clip_position = vec4(input.position, 0.0, 1.0); + output.uv = input.uv; + return output; +} + +@fragment +fn fs_main(input: VertexOutput) -> @location(0) vec4 { + return textureSample(t_texture, t_sampler, input.uv); +} diff --git a/shaders/standard.wgsl b/shaders/standard.wgsl new file mode 100644 index 0000000..f80b05e --- /dev/null +++ b/shaders/standard.wgsl @@ -0,0 +1,97 @@ +struct VertexInput { + @location(0) position: vec3, + @location(1) normal: vec3, + @location(2) uv: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec3, + @location(1) world_normal: vec3, +} + +struct Uniforms { + model: mat4x4, + view: mat4x4, + projection: mat4x4, +} + +@group(0) @binding(0) +var uniforms: Uniforms; + +@vertex +fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + + let world_pos = uniforms.model * vec4(input.position, 1.0); + output.world_position = world_pos.xyz; + output.world_normal = (uniforms.model * vec4(input.normal, 0.0)).xyz; + output.clip_position = uniforms.projection * uniforms.view * world_pos; + + return output; +} + +fn bayer_2x2_dither(value: f32, screen_pos: vec2) -> f32 { + let pattern = array( + 0.0/4.0, 2.0/4.0, + 3.0/4.0, 1.0/4.0 + ); + let x = i32(screen_pos.x) % 2; + let y = i32(screen_pos.y) % 2; + let index = y * 2 + x; + return select(0.0, 1.0, value > pattern[index]); +} + +fn bayer_4x4_dither(value: f32, screen_pos: vec2) -> f32 { + let pattern = array( + 0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0, + 12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0, + 3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0, + 15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0 + ); + let x = i32(screen_pos.x) % 4; + let y = i32(screen_pos.y) % 4; + let index = y * 4 + x; + return select(0.0, 1.0, value > pattern[index]); +} + +fn bayer_8x8_dither(value: f32, screen_pos: vec2) -> f32 { + let pattern = array( + 0.0/64.0, 32.0/64.0, 8.0/64.0, 40.0/64.0, 2.0/64.0, 34.0/64.0, 10.0/64.0, 42.0/64.0, + 48.0/64.0, 16.0/64.0, 56.0/64.0, 24.0/64.0, 50.0/64.0, 18.0/64.0, 58.0/64.0, 26.0/64.0, + 12.0/64.0, 44.0/64.0, 4.0/64.0, 36.0/64.0, 14.0/64.0, 46.0/64.0, 6.0/64.0, 38.0/64.0, + 60.0/64.0, 28.0/64.0, 52.0/64.0, 20.0/64.0, 62.0/64.0, 30.0/64.0, 54.0/64.0, 22.0/64.0, + 3.0/64.0, 35.0/64.0, 11.0/64.0, 43.0/64.0, 1.0/64.0, 33.0/64.0, 9.0/64.0, 41.0/64.0, + 51.0/64.0, 19.0/64.0, 59.0/64.0, 27.0/64.0, 49.0/64.0, 17.0/64.0, 57.0/64.0, 25.0/64.0, + 15.0/64.0, 47.0/64.0, 7.0/64.0, 39.0/64.0, 13.0/64.0, 45.0/64.0, 5.0/64.0, 37.0/64.0, + 63.0/64.0, 31.0/64.0, 55.0/64.0, 23.0/64.0, 61.0/64.0, 29.0/64.0, 53.0/64.0, 21.0/64.0 + ); + let x = i32(screen_pos.x) % 8; + let y = i32(screen_pos.y) % 8; + let index = y * 8 + x; + return select(0.0, 1.0, value > pattern[index]); +} + + +@fragment +fn fs_main(input: VertexOutput) -> @location(0) vec4 { + let light_pos = vec3(5.0, 5.0, 5.0); + let light_color = vec3(1.0, 1.0, 1.0); + let object_color = vec3(1.0, 1.0, 1.0); + + let ambient_strength = 0.3; + let ambient = ambient_strength * light_color; + + let norm = normalize(input.world_normal); + let light_dir = normalize(vec3(1.0, -1.0, 1.0)); + let diff = max(dot(norm, light_dir), 0.0); + let diffuse = diff * light_color; + + let result = (ambient + diffuse) * object_color; + + let dithered_r = bayer_8x8_dither(result.r, input.clip_position.xy); + let dithered_g = bayer_8x8_dither(result.g, input.clip_position.xy); + let dithered_b = bayer_8x8_dither(result.b, input.clip_position.xy); + + return vec4(dithered_r, dithered_g, dithered_b, 1.0); +} diff --git a/shaders/terrain.wgsl b/shaders/terrain.wgsl new file mode 100644 index 0000000..b53f38d --- /dev/null +++ b/shaders/terrain.wgsl @@ -0,0 +1,120 @@ +struct VertexInput { + @location(0) position: vec3, + @location(1) normal: vec3, + @location(2) uv: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec3, + @location(1) world_normal: vec3, + @location(2) uv: vec2, +} + +struct Uniforms { + model: mat4x4, + view: mat4x4, + projection: mat4x4, + height_scale: f32, + time: f32, +} + +@group(0) @binding(0) +var uniforms: Uniforms; + +@group(0) @binding(1) +var height_texture: texture_2d; + +@group(0) @binding(2) +var height_sampler: sampler; + +@vertex +fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + + let height = textureSampleLevel(height_texture, height_sampler, input.uv, 0.0).r; + + var displaced_pos = input.position; + displaced_pos.y += height * uniforms.height_scale; + + let texel_size = vec2(1.0 / 512.0, 1.0 / 512.0); + let height_left = textureSampleLevel(height_texture, height_sampler, input.uv - vec2(texel_size.x, 0.0), 0.0).r; + let height_right = textureSampleLevel(height_texture, height_sampler, input.uv + vec2(texel_size.x, 0.0), 0.0).r; + let height_down = textureSampleLevel(height_texture, height_sampler, input.uv - vec2(0.0, texel_size.y), 0.0).r; + let height_up = textureSampleLevel(height_texture, height_sampler, input.uv + vec2(0.0, texel_size.y), 0.0).r; + + let dh_dx = (height_right - height_left) * uniforms.height_scale; + let dh_dz = (height_up - height_down) * uniforms.height_scale; + + let normal = normalize(vec3(-dh_dx, 1.0, -dh_dz)); + + let world_pos = uniforms.model * vec4(displaced_pos, 1.0); + output.world_position = world_pos.xyz; + output.world_normal = normalize((uniforms.model * vec4(normal, 0.0)).xyz); + output.clip_position = uniforms.projection * uniforms.view * world_pos; + output.uv = input.uv; + + return output; +} + +fn hash(p: vec2) -> f32 { + var p3 = fract(vec3(p.xyx) * 0.1031); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); +} + +fn should_glitter(screen_pos: vec2, time: f32) -> bool { + let pixel_pos = floor(screen_pos); + let h = hash(pixel_pos); + let time_offset = h * 6283.18; + let sparkle_rate = 0.2; + let sparkle = sin(time * sparkle_rate + time_offset) * 0.5 + 0.5; + let threshold = 0.95; + return sparkle > threshold && h > 0.95; +} + +fn bayer_8x8_dither(value: f32, screen_pos: vec2) -> f32 { + let pattern = array( + 0.0/64.0, 32.0/64.0, 8.0/64.0, 40.0/64.0, 2.0/64.0, 34.0/64.0, 10.0/64.0, 42.0/64.0, + 48.0/64.0, 16.0/64.0, 56.0/64.0, 24.0/64.0, 50.0/64.0, 18.0/64.0, 58.0/64.0, 26.0/64.0, + 12.0/64.0, 44.0/64.0, 4.0/64.0, 36.0/64.0, 14.0/64.0, 46.0/64.0, 6.0/64.0, 38.0/64.0, + 60.0/64.0, 28.0/64.0, 52.0/64.0, 20.0/64.0, 62.0/64.0, 30.0/64.0, 54.0/64.0, 22.0/64.0, + 3.0/64.0, 35.0/64.0, 11.0/64.0, 43.0/64.0, 1.0/64.0, 33.0/64.0, 9.0/64.0, 41.0/64.0, + 51.0/64.0, 19.0/64.0, 59.0/64.0, 27.0/64.0, 49.0/64.0, 17.0/64.0, 57.0/64.0, 25.0/64.0, + 15.0/64.0, 47.0/64.0, 7.0/64.0, 39.0/64.0, 13.0/64.0, 45.0/64.0, 5.0/64.0, 37.0/64.0, + 63.0/64.0, 31.0/64.0, 55.0/64.0, 23.0/64.0, 61.0/64.0, 29.0/64.0, 53.0/64.0, 21.0/64.0 + ); + let x = i32(screen_pos.x) % 8; + let y = i32(screen_pos.y) % 8; + let index = y * 8 + x; + return select(0.2, 1.0, value > pattern[index]); +} + +@fragment +fn fs_main(input: VertexOutput) -> @location(0) vec4 { + let light_dir = normalize(vec3(-0.5, -1.0, -0.5)); + let light_color = vec3(1.0, 1.0, 1.0); + let object_color = vec3(1.0, 1.0, 1.0); + + let ambient_strength = 0.2; + let ambient = ambient_strength * light_color; + + let norm = normalize(input.world_normal); + let diff = max(dot(norm, -light_dir), 0.0); + let diffuse = diff * light_color; + + let result = (ambient + diffuse) * object_color; + + var dithered_r = bayer_8x8_dither(result.r, input.clip_position.xy); + var dithered_g = bayer_8x8_dither(result.g, input.clip_position.xy); + var dithered_b = bayer_8x8_dither(result.b, input.clip_position.xy); + + let is_grey_or_black = dithered_r == 0.0 || (dithered_r == dithered_g && dithered_g == dithered_b); + if (is_grey_or_black && should_glitter(input.clip_position.xy, uniforms.time)) { + dithered_r = 1.0; + dithered_g = 1.0; + dithered_b = 1.0; + } + + return vec4(dithered_r, dithered_g, dithered_b, 1.0); +} diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 0000000..b14dca1 --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,168 @@ +use bytemuck::{Pod, Zeroable}; +use glam::{Mat4, Vec3}; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +pub struct CameraUniforms +{ + pub model: [[f32; 4]; 4], + pub view: [[f32; 4]; 4], + pub projection: [[f32; 4]; 4], +} + +impl CameraUniforms +{ + pub fn new(model: Mat4, view: Mat4, projection: Mat4) -> Self + { + Self { + model: model.to_cols_array_2d(), + view: view.to_cols_array_2d(), + projection: projection.to_cols_array_2d(), + } + } +} + +pub struct Camera +{ + pub position: Vec3, + pub target: Vec3, + pub up: Vec3, + pub fov: f32, + pub aspect: f32, + pub near: f32, + pub far: f32, + pub yaw: f32, + pub pitch: f32, + pub is_following: bool, + pub follow_offset: Vec3, +} + +impl Camera +{ + pub fn init(aspect: f32) -> Self + { + Self { + position: Vec3::new(15.0, 15.0, 15.0), + target: Vec3::ZERO, + up: Vec3::Y, + fov: 45.0_f32.to_radians(), + aspect, + near: 0.1, + far: 100.0, + yaw: -135.0_f32.to_radians(), + pitch: -30.0_f32.to_radians(), + is_following: true, + follow_offset: Vec3::ZERO, + } + } + + pub fn view_matrix(&self) -> Mat4 + { + Mat4::look_at_rh(self.position, self.target, self.up) + } + + pub fn projection_matrix(&self) -> Mat4 + { + Mat4::perspective_rh(self.fov, self.aspect, self.near, self.far) + } + + pub fn update_rotation(&mut self, mouse_delta: (f32, f32), sensitivity: f32) + { + self.yaw += mouse_delta.0 * sensitivity; + self.pitch -= mouse_delta.1 * sensitivity; + + self.pitch = self + .pitch + .clamp(-89.0_f32.to_radians(), 89.0_f32.to_radians()); + } + + pub fn get_forward(&self) -> Vec3 + { + Vec3::new( + self.yaw.cos() * self.pitch.cos(), + self.pitch.sin(), + self.yaw.sin() * self.pitch.cos(), + ) + .normalize() + } + + pub fn get_right(&self) -> Vec3 + { + self.get_forward().cross(Vec3::Y).normalize() + } + + pub fn get_forward_horizontal(&self) -> Vec3 + { + Vec3::new(self.yaw.cos(), 0.0, self.yaw.sin()).normalize() + } + + pub fn get_right_horizontal(&self) -> Vec3 + { + self.get_forward_horizontal().cross(Vec3::Y).normalize() + } + + pub fn update_noclip(&mut self, input: Vec3, speed: f32) + { + let forward = self.get_forward(); + let right = self.get_right(); + + self.position += forward * input.z * speed; + self.position += right * input.x * speed; + self.position += Vec3::Y * input.y * speed; + + self.target = self.position + forward; + } + + pub fn start_following(&mut self, target_position: Vec3) + { + self.is_following = true; + self.follow_offset = self.position - target_position; + + let distance = self.follow_offset.length(); + if distance > 0.0 + { + self.pitch = (self.follow_offset.y / distance).asin(); + self.yaw = self.follow_offset.z.atan2(self.follow_offset.x) + std::f32::consts::PI; + } + } + + pub fn stop_following(&mut self) + { + self.is_following = false; + + let look_direction = (self.target - self.position).normalize(); + + self.yaw = look_direction.z.atan2(look_direction.x); + self.pitch = look_direction.y.asin(); + } + + pub fn update_follow(&mut self, target_position: Vec3, mouse_delta: (f32, f32), sensitivity: f32) + { + if !self.is_following + { + return; + } + + if mouse_delta.0.abs() > 0.0 || mouse_delta.1.abs() > 0.0 + { + self.yaw += mouse_delta.0 * sensitivity; + self.pitch += mouse_delta.1 * sensitivity; + + self.pitch = self + .pitch + .clamp(-89.0_f32.to_radians(), 89.0_f32.to_radians()); + } + + let distance = self.follow_offset.length(); + + let orbit_yaw = self.yaw + std::f32::consts::PI; + + let offset_x = distance * orbit_yaw.cos() * self.pitch.cos(); + let offset_y = distance * self.pitch.sin(); + let offset_z = distance * orbit_yaw.sin() * self.pitch.cos(); + + self.follow_offset = Vec3::new(offset_x, offset_y, offset_z); + self.position = target_position + self.follow_offset; + self.target = target_position; + } +} diff --git a/src/components/camera.rs b/src/components/camera.rs new file mode 100644 index 0000000..81df1e7 --- /dev/null +++ b/src/components/camera.rs @@ -0,0 +1,61 @@ +use glam::Mat4; + +#[derive(Clone, Copy)] +pub struct CameraComponent +{ + pub fov: f32, + pub aspect: f32, + pub near: f32, + pub far: f32, + pub yaw: f32, + pub pitch: f32, + pub is_active: bool, +} + +impl CameraComponent +{ + pub fn new(aspect: f32) -> Self + { + Self { + fov: 45.0_f32.to_radians(), + aspect, + near: 0.1, + far: 100.0, + yaw: -135.0_f32.to_radians(), + pitch: -30.0_f32.to_radians(), + is_active: true, + } + } + + pub fn projection_matrix(&self) -> Mat4 + { + Mat4::perspective_rh(self.fov, self.aspect, self.near, self.far) + } + + pub fn get_forward(&self) -> glam::Vec3 + { + glam::Vec3::new( + self.yaw.cos() * self.pitch.cos(), + self.pitch.sin(), + self.yaw.sin() * self.pitch.cos(), + ) + .normalize() + } + + pub fn get_right(&self) -> glam::Vec3 + { + self.get_forward().cross(glam::Vec3::Y).normalize() + } + + pub fn get_forward_horizontal(&self) -> glam::Vec3 + { + glam::Vec3::new(self.yaw.cos(), 0.0, self.yaw.sin()).normalize() + } + + pub fn get_right_horizontal(&self) -> glam::Vec3 + { + self.get_forward_horizontal() + .cross(glam::Vec3::Y) + .normalize() + } +} diff --git a/src/components/camera_follow.rs b/src/components/camera_follow.rs new file mode 100644 index 0000000..f081d26 --- /dev/null +++ b/src/components/camera_follow.rs @@ -0,0 +1,32 @@ +use glam::Vec3; + +use crate::entity::EntityHandle; + +#[derive(Clone, Copy)] +pub struct CameraFollowComponent +{ + pub target_entity: EntityHandle, + pub offset: Vec3, + pub is_following: bool, +} + +impl CameraFollowComponent +{ + pub fn new(target_entity: EntityHandle) -> Self + { + Self { + target_entity, + offset: Vec3::ZERO, + is_following: false, + } + } + + pub fn with_offset(target_entity: EntityHandle, offset: Vec3) -> Self + { + Self { + target_entity, + offset, + is_following: true, + } + } +} diff --git a/src/components/input.rs b/src/components/input.rs new file mode 100644 index 0000000..e481aee --- /dev/null +++ b/src/components/input.rs @@ -0,0 +1,9 @@ +use glam::Vec3; + +#[derive(Clone, Default)] +pub struct InputComponent +{ + pub move_direction: Vec3, + pub jump_pressed: bool, + pub jump_just_pressed: bool, +} diff --git a/src/components/jump.rs b/src/components/jump.rs new file mode 100644 index 0000000..f07788f --- /dev/null +++ b/src/components/jump.rs @@ -0,0 +1,82 @@ +use glam::Vec3; +use kurbo::CubicBez; + +#[derive(Clone)] +pub struct JumpComponent +{ + pub jump_config: JumpConfig, +} + +impl JumpComponent +{ + pub fn new() -> Self + { + Self { + jump_config: JumpConfig::default(), + } + } +} + +#[derive(Clone, Copy)] +pub struct JumpConfig +{ + pub jump_height: f32, + pub jump_duration: f32, + pub air_control_force: f32, + pub max_air_momentum: f32, + pub air_damping_active: f32, + pub air_damping_passive: f32, + pub jump_curve: CubicBez, + pub jump_context: JumpContext, +} + +impl Default for JumpConfig +{ + fn default() -> Self + { + Self { + jump_height: 2.0, + jump_duration: 0.15, + air_control_force: 10.0, + max_air_momentum: 8.0, + air_damping_active: 0.4, + air_damping_passive: 0.9, + jump_curve: CubicBez::new( + (0.0, 0.0), + (0.4, 0.75), + (0.7, 0.9), + (1.0, 1.0), + ), + jump_context: JumpContext::default(), + } + } +} + +#[derive(Default, Clone, Copy)] +pub struct JumpContext +{ + pub in_progress: bool, + pub duration: f32, + pub execution_time: f32, + pub origin_height: f32, + pub normal: Vec3, +} + +impl JumpContext +{ + fn start(time: f32, current_height: f32, surface_normal: Vec3) -> Self + { + Self { + in_progress: false, + duration: 0.0, + execution_time: time, + origin_height: current_height, + normal: surface_normal, + } + } + + pub fn stop(&mut self) + { + self.in_progress = false; + } +} diff --git a/src/components/mesh.rs b/src/components/mesh.rs new file mode 100644 index 0000000..cc0a4c4 --- /dev/null +++ b/src/components/mesh.rs @@ -0,0 +1,11 @@ +use std::rc::Rc; + +use crate::mesh::Mesh; +use crate::render::Pipeline; + +#[derive(Clone)] +pub struct MeshComponent +{ + pub mesh: Rc, + pub pipeline: Pipeline, +} diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..c61dbb1 --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,16 @@ +pub mod camera; +pub mod camera_follow; +pub mod input; +pub mod jump; +pub mod mesh; +pub mod movement; +pub mod physics; +pub mod player_tag; +pub mod state_machine; + +pub use camera::CameraComponent; +pub use camera_follow::CameraFollowComponent; +pub use input::InputComponent; +pub use mesh::MeshComponent; +pub use movement::MovementComponent; +pub use physics::PhysicsComponent; diff --git a/src/components/movement.rs b/src/components/movement.rs new file mode 100644 index 0000000..ab17df3 --- /dev/null +++ b/src/components/movement.rs @@ -0,0 +1,73 @@ +use glam::Vec3; +use kurbo::CubicBez; + +#[derive(Clone)] +pub struct MovementComponent +{ + pub movement_config: MovementConfig, +} + +impl MovementComponent +{ + pub fn new() -> Self + { + Self { + movement_config: MovementConfig::new(), + } + } +} + +#[derive(Clone)] +pub struct MovementConfig +{ + pub walking_acceleration: f32, + pub walking_acceleration_duration: f32, + pub walking_acceleration_curve: CubicBez, + pub walking_damping: f32, + pub max_walking_speed: f32, + pub idle_damping: f32, + pub movement_context: MovementContext, +} + +impl MovementConfig +{ + pub fn new() -> Self + { + Self { + walking_acceleration: 250.0, + walking_acceleration_duration: 0.1, + walking_acceleration_curve: CubicBez::new( + (0.0, 0.0), + (0.5, 0.3), + (0.75, 0.9), + (1.0, 1.0), + ), + walking_damping: 0.8, + max_walking_speed: 6.0, + idle_damping: 0.1, + movement_context: MovementContext::new(), + } + } +} + +#[derive(Clone, Copy)] +pub struct MovementContext +{ + pub forward_direction: Vec3, + pub is_floored: bool, + pub last_floored_time: u64, + pub surface_normal: Vec3, +} + +impl MovementContext +{ + pub fn new() -> Self + { + Self { + forward_direction: Vec3::Z, + is_floored: false, + last_floored_time: 0, + surface_normal: Vec3::Y, + } + } +} diff --git a/src/components/physics.rs b/src/components/physics.rs new file mode 100644 index 0000000..3f59c07 --- /dev/null +++ b/src/components/physics.rs @@ -0,0 +1,8 @@ +use rapier3d::prelude::{ColliderHandle, RigidBodyHandle}; + +#[derive(Copy, Clone, Debug)] +pub struct PhysicsComponent +{ + pub rigidbody: RigidBodyHandle, + pub collider: Option, +} diff --git a/src/components/player_tag.rs b/src/components/player_tag.rs new file mode 100644 index 0000000..ae1e27b --- /dev/null +++ b/src/components/player_tag.rs @@ -0,0 +1,2 @@ +#[derive(Copy, Clone, Debug)] +pub struct PlayerTag; diff --git a/src/components/state_machine.rs b/src/components/state_machine.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/components/state_machine.rs @@ -0,0 +1 @@ + diff --git a/src/debug/collider_debug.rs b/src/debug/collider_debug.rs new file mode 100644 index 0000000..33dd0e4 --- /dev/null +++ b/src/debug/collider_debug.rs @@ -0,0 +1,149 @@ +use std::cell::OnceCell; +use std::rc::Rc; + +use glam::{Mat4, Vec3}; +use nalgebra::DMatrix; +use rapier3d::parry::shape::HeightField; + +use crate::{ + mesh::{Mesh, Vertex}, + physics::PhysicsManager, + render::{self, DrawCall, Pipeline}, +}; + +thread_local! { + static WIREFRAME_BOX: OnceCell> = OnceCell::new(); + static DEBUG_HEIGHTFIELD: OnceCell>> = OnceCell::new(); +} + +pub fn set_debug_heightfield(heightfield: &HeightField, scale: [f32; 3], offset: [f32; 3]) +{ + DEBUG_HEIGHTFIELD.with(|cell| { + cell.get_or_init(|| { + render::with_device(|device| { + Some(Rc::new(create_heightfield_wireframe( + device, + heightfield, + scale, + offset, + ))) + }) + }); + }); +} + +fn create_heightfield_wireframe( + device: &wgpu::Device, + heightfield: &HeightField, + scale: [f32; 3], + offset: [f32; 3], +) -> Mesh +{ + let nrows = heightfield.nrows(); + let ncols = heightfield.ncols(); + + let mut vertices = Vec::new(); + let mut indices = Vec::new(); + + for row in 0..nrows + { + for col in 0..ncols + { + let x = col as f32 * scale[0]; + let y = heightfield.heights()[(row, col)] * scale[1]; + let z = row as f32 * scale[2]; + + vertices.push(Vertex { + position: [x + offset[0], y + offset[1], z + offset[2]], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }); + } + } + + for row in 0..nrows + { + for col in 0..ncols + { + let idx = (row * ncols + col) as u32; + + if col < ncols - 1 + { + indices.push(idx); + indices.push(idx + 1); + } + + if row < nrows - 1 + { + indices.push(idx); + indices.push(idx + ncols as u32); + } + } + } + + if !vertices.is_empty() + { + let first = &vertices[0].position; + let last = &vertices[vertices.len() - 1].position; + println!( + "Heightfield bounds: ({}, {}, {}) to ({}, {}, {})", + first[0], first[1], first[2], last[0], last[1], last[2] + ); + println!( + "Total vertices: {}, indices: {}", + vertices.len(), + indices.len() + ); + } + + Mesh::new(device, &vertices, &indices) +} + +pub fn render_collider_debug() -> Vec +{ + let mut draw_calls = Vec::new(); + + let aabbs = PhysicsManager::get_all_collider_aabbs(); + + WIREFRAME_BOX.with(|cell| { + let wireframe_box = cell.get_or_init(|| { + render::with_device(|device| Rc::new(Mesh::create_wireframe_box(device))) + }); + + for (mins, maxs) in aabbs + { + let min = Vec3::from(mins); + let max = Vec3::from(maxs); + + let center = (min + max) * 0.5; + let size = max - min; + + let scale = Mat4::from_scale(size); + let translation = Mat4::from_translation(center); + let model = translation * scale; + + draw_calls.push(DrawCall { + vertex_buffer: wireframe_box.vertex_buffer.clone(), + index_buffer: wireframe_box.index_buffer.clone(), + num_indices: wireframe_box.num_indices, + model, + pipeline: Pipeline::Wireframe, + }); + } + }); + + DEBUG_HEIGHTFIELD.with(|cell| { + if let Some(Some(heightfield_mesh)) = cell.get() + { + draw_calls.push(DrawCall { + vertex_buffer: heightfield_mesh.vertex_buffer.clone(), + index_buffer: heightfield_mesh.index_buffer.clone(), + num_indices: heightfield_mesh.num_indices, + model: Mat4::IDENTITY, + pipeline: Pipeline::Wireframe, + }); + } + }); + + draw_calls +} diff --git a/src/debug/mod.rs b/src/debug/mod.rs new file mode 100644 index 0000000..c7e4bce --- /dev/null +++ b/src/debug/mod.rs @@ -0,0 +1,5 @@ +pub mod collider_debug; +pub mod noclip; + +pub use collider_debug::{render_collider_debug, set_debug_heightfield}; +pub use noclip::{update_follow_camera, update_noclip_camera}; diff --git a/src/debug/noclip.rs b/src/debug/noclip.rs new file mode 100644 index 0000000..d2f6073 --- /dev/null +++ b/src/debug/noclip.rs @@ -0,0 +1,59 @@ +use glam::Vec3; + +use crate::camera::Camera; +use crate::utility::input::InputState; +use crate::world::World; + +pub fn update_noclip_camera(camera: &mut Camera, input_state: &InputState, delta: f32) +{ + camera.update_rotation(input_state.mouse_delta, 0.0008); + + let mut input_vec = Vec3::ZERO; + + if input_state.w + { + input_vec.z += 1.0; + } + if input_state.s + { + input_vec.z -= 1.0; + } + if input_state.d + { + input_vec.x += 1.0; + } + if input_state.a + { + input_vec.x -= 1.0; + } + if input_state.space + { + input_vec.y += 1.0; + } + + if input_vec.length_squared() > 0.0 + { + input_vec = input_vec.normalize(); + } + + let mut speed = 10.0 * delta; + if input_state.shift + { + speed *= 2.0; + } + + camera.update_noclip(input_vec, speed); +} + +pub fn update_follow_camera(camera: &mut Camera, world: &World, input_state: &InputState) +{ + let player_entities = world.player_tags.all(); + + if let Some(&player_entity) = player_entities.first() + { + if let Some(player_transform) = world.transforms.get(player_entity) + { + camera.update_follow(player_transform.position, input_state.mouse_delta, 0.0008); + } + } +} diff --git a/src/draw.rs b/src/draw.rs new file mode 100644 index 0000000..0761904 --- /dev/null +++ b/src/draw.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; +use std::rc::Rc; + +use crate::entity::EntityHandle; +use crate::mesh::Mesh; +use crate::render::{DrawCall, Pipeline}; + +pub type DrawHandle = usize; + +struct DrawEntry +{ + mesh: Rc, + entity: EntityHandle, + pipeline: Pipeline, +} + +pub struct DrawManager +{ + entries: HashMap, + next_handle: DrawHandle, +} + +impl DrawManager +{ + pub fn new() -> Self + { + Self { + entries: HashMap::new(), + next_handle: 0, + } + } + + pub fn draw_mesh_internal( + &mut self, + mesh: Rc, + entity: EntityHandle, + pipeline: Pipeline, + ) -> DrawHandle + { + let handle = self.next_handle; + self.next_handle += 1; + + self.entries.insert( + handle, + DrawEntry { + mesh, + entity, + pipeline, + }, + ); + + handle + } + + pub fn clear_mesh_internal(&mut self, handle: DrawHandle) + { + self.entries.remove(&handle); + } + + pub fn collect_draw_calls(&self) -> Vec + { + vec![] + } + + pub fn draw_mesh(_mesh: Rc, _entity: EntityHandle, _pipeline: Pipeline) -> DrawHandle + { + 0 + } + + pub fn clear_mesh(_handle: DrawHandle) {} +} diff --git a/src/entity.rs b/src/entity.rs new file mode 100644 index 0000000..acd062e --- /dev/null +++ b/src/entity.rs @@ -0,0 +1,43 @@ +use std::collections::HashSet; + +pub type EntityHandle = u64; + +pub struct EntityManager +{ + next_id: EntityHandle, + alive: HashSet, +} + +impl EntityManager +{ + pub fn new() -> Self + { + Self { + next_id: 0, + alive: HashSet::new(), + } + } + + pub fn spawn(&mut self) -> EntityHandle + { + let id = self.next_id; + self.next_id += 1; + self.alive.insert(id); + id + } + + pub fn despawn(&mut self, entity: EntityHandle) + { + self.alive.remove(&entity); + } + + pub fn is_alive(&self, entity: EntityHandle) -> bool + { + self.alive.contains(&entity) + } + + pub fn all_entities(&self) -> Vec + { + self.alive.iter().copied().collect() + } +} diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..285c91f --- /dev/null +++ b/src/event.rs @@ -0,0 +1,70 @@ +use std::any::{Any, TypeId}; +use std::cell::RefCell; +use std::collections::HashMap; + +pub trait Event: std::fmt::Debug {} + +type EventHandler = Box; + +pub struct EventBus +{ + handlers: HashMap>, +} + +impl EventBus +{ + fn new() -> Self + { + Self { + handlers: HashMap::new(), + } + } + + fn subscribe_internal(&mut self, handler: F) + { + let type_id = TypeId::of::(); + let handlers: &mut Vec> = self + .handlers + .entry(type_id) + .or_insert_with(|| Box::new(Vec::>::new())) + .downcast_mut() + .unwrap(); + + handlers.push(Box::new(handler)); + } + + fn publish_internal(&mut self, event: &T) + { + let type_id = TypeId::of::(); + + if let Some(handlers) = self.handlers.get_mut(&type_id) + { + let typed_handlers = handlers.downcast_mut::>>().unwrap(); + for handler in typed_handlers + { + handler(event); + } + } + } + + pub fn subscribe(handler: F) + { + EVENT_BUS.with(|bus| bus.borrow_mut().subscribe_internal(handler)); + } + + pub fn publish(event: &T) + { + EVENT_BUS.with(|bus| bus.borrow_mut().publish_internal(event)); + } +} + +thread_local! { + static EVENT_BUS: RefCell = RefCell::new(EventBus::new()); +} + +#[derive(Debug, Clone)] +pub struct UpdateEvent +{ + pub delta: f32, +} +impl Event for UpdateEvent {} diff --git a/src/heightmap.rs b/src/heightmap.rs new file mode 100644 index 0000000..baff890 --- /dev/null +++ b/src/heightmap.rs @@ -0,0 +1,67 @@ +use exr::prelude::{ReadChannels, ReadLayers}; +use std::path::Path; + +pub fn load_exr_heightmap( + device: &wgpu::Device, + queue: &wgpu::Queue, + path: impl AsRef, +) -> Result<(wgpu::Texture, wgpu::TextureView, wgpu::Sampler), Box> +{ + let image = exr::prelude::read() + .no_deep_data() + .largest_resolution_level() + .all_channels() + .all_layers() + .all_attributes() + .from_file(path)?; + + let layer = &image.layer_data[0]; + let width = layer.size.width() as u32; + let height = layer.size.height() as u32; + + let channel = &layer.channel_data.list[0]; + let float_data: Vec = channel.sample_data.values_as_f32().collect(); + + let texture_size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Height Map Texture"), + size: texture_size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R32Float, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + queue.write_texture( + texture.as_image_copy(), + bytemuck::cast_slice(&float_data), + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(4 * width), + rows_per_image: Some(height), + }, + texture_size, + ); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("Height Map Sampler"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + Ok((texture, view, sampler)) +} diff --git a/src/main.rs b/src/main.rs new file mode 100755 index 0000000..52a3634 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,233 @@ +mod camera; +mod components; +mod debug; +mod draw; +mod entity; +mod event; +mod heightmap; +mod mesh; +mod physics; +mod picking; +mod player; +mod postprocess; +mod render; +mod shader; +mod state; +mod systems; +mod terrain; +mod utility; +mod world; + +use std::time::{Duration, Instant}; + +use glam::Vec3; +use render::Renderer; +use utility::input::InputState; +use world::{Transform, World}; + +use crate::components::{CameraComponent, CameraFollowComponent}; +use crate::debug::render_collider_debug; +use crate::entity::EntityHandle; +use crate::physics::PhysicsManager; +use crate::player::Player; +use crate::systems::{ + camera_follow_system, camera_input_system, camera_noclip_system, physics_sync_system, + player_input_system, render_system, start_camera_following, state_machine_physics_system, + state_machine_system, stop_camera_following, +}; +use crate::terrain::Terrain; +use crate::utility::time::Time; + +fn main() -> Result<(), Box> +{ + let sdl_context = sdl3::init()?; + let video_subsystem = sdl_context.video()?; + + let window = video_subsystem + .window("snow_trail", 800, 600) + .position_centered() + .resizable() + .vulkan() + .build()?; + + let renderer = pollster::block_on(Renderer::new(&window, 1))?; + render::init(renderer); + + let terrain_data = render::with_device(|device| { + render::with_queue(|queue| { + let height_map = + heightmap::load_exr_heightmap(device, queue, "textures/height_map_x0_y0.exr"); + let (height_texture, height_view, height_sampler) = height_map.unwrap(); + render::TerrainData { + height_texture, + height_view, + height_sampler, + } + }) + }); + + render::set_terrain_data(terrain_data); + + let mut world = World::new(); + let player_entity = Player::spawn(&mut world); + let _terrain_entity = Terrain::spawn(&mut world, "textures/height_map_x0_y0.exr", 10.0)?; + + let camera_entity = spawn_camera(&mut world, player_entity); + start_camera_following(&mut world, camera_entity); + + let mut noclip_mode = false; + + let mut event_pump = sdl_context.event_pump()?; + let mut input_state = InputState::new(); + + 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; + + '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; + + for event in event_pump.poll_iter() + { + let mouse_capture_changed = input_state.handle_event(&event); + + if mouse_capture_changed + { + sdl_context + .mouse() + .set_relative_mouse_mode(&window, input_state.mouse_captured); + } + } + + if input_state.quit_requested + { + break 'running; + } + + input_state.process_post_events(); + + if input_state.noclip_just_pressed + { + noclip_mode = !noclip_mode; + + if noclip_mode + { + stop_camera_following(&mut world, camera_entity); + } + else + { + start_camera_following(&mut world, camera_entity); + } + } + + camera_input_system(&mut world, &input_state); + + if noclip_mode + { + camera_noclip_system(&mut world, &input_state, delta); + } + else + { + camera_follow_system(&mut world); + player_input_system(&mut world, &input_state); + } + + + physics_accumulator += delta; + + while physics_accumulator >= FIXED_TIMESTEP + { + state_machine_physics_system(&mut world, FIXED_TIMESTEP); + + PhysicsManager::physics_step(); + + physics_sync_system(&mut world); + + physics_accumulator -= FIXED_TIMESTEP; + } + + state_machine_system(&mut world, delta); + + let mut draw_calls = render_system(&world); + draw_calls.extend(render_collider_debug()); + + if let Some((camera_entity, camera_component)) = world.cameras.get_active() + { + if let Some(camera_transform) = world.transforms.get(camera_entity) + { + let view = get_view_matrix(&world, camera_entity, camera_transform, camera_component); + let projection = camera_component.projection_matrix(); + + render::render_with_matrices(&view, &projection, &draw_calls, time); + } + } + + input_state.clear_just_pressed(); + + let frame_time = frame_start.elapsed(); + if frame_time < frame_duration + { + std::thread::sleep(frame_duration - frame_time); + } + } + + Ok(()) +} + +fn spawn_camera(world: &mut World, target_entity: EntityHandle) -> EntityHandle +{ + let camera_entity = world.spawn(); + + let camera_component = CameraComponent::new(render::aspect_ratio()); + let camera_follow = CameraFollowComponent::new(target_entity); + + let initial_position = Vec3::new(15.0, 15.0, 15.0); + let transform = Transform { + position: initial_position, + rotation: glam::Quat::IDENTITY, + scale: Vec3::ONE, + }; + + world.cameras.insert(camera_entity, camera_component); + world.camera_follows.insert(camera_entity, camera_follow); + world.transforms.insert(camera_entity, transform); + + camera_entity +} + +fn get_view_matrix( + world: &World, + camera_entity: EntityHandle, + camera_transform: &Transform, + camera_component: &CameraComponent, +) -> glam::Mat4 +{ + if let Some(follow) = world.camera_follows.get(camera_entity) + { + if follow.is_following + { + if let Some(target_transform) = world.transforms.get(follow.target_entity) + { + return glam::Mat4::look_at_rh( + camera_transform.position, + target_transform.position, + Vec3::Y, + ); + } + } + } + + let forward = camera_component.get_forward(); + let target = camera_transform.position + forward; + glam::Mat4::look_at_rh(camera_transform.position, target, Vec3::Y) +} diff --git a/src/mesh.rs b/src/mesh.rs new file mode 100644 index 0000000..4849025 --- /dev/null +++ b/src/mesh.rs @@ -0,0 +1,414 @@ +use bytemuck::{Pod, Zeroable}; +use glam::{Mat4, Vec3}; +use std::path::Path; +use std::rc::Rc; + +use crate::utility::transform::Transform; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +pub struct Vertex +{ + pub position: [f32; 3], + pub normal: [f32; 3], + pub uv: [f32; 2], +} + +impl Vertex +{ + pub fn desc() -> wgpu::VertexBufferLayout<'static> + { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: (std::mem::size_of::<[f32; 3]>() * 2) as wgpu::BufferAddress, + shader_location: 2, + format: wgpu::VertexFormat::Float32x2, + }, + ], + } + } +} + +pub struct Mesh +{ + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub num_indices: u32, +} + +impl Mesh +{ + pub fn new(device: &wgpu::Device, vertices: &[Vertex], indices: &[u32]) -> Self + { + use wgpu::util::DeviceExt; + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(indices), + usage: wgpu::BufferUsages::INDEX, + }); + + Self { + vertex_buffer, + index_buffer, + num_indices: indices.len() as u32, + } + } + + pub fn create_cube_mesh(device: &wgpu::Device) -> Mesh + { + let vertices = vec![ + Vertex { + position: [-0.5, -0.5, 0.5], + normal: [0.0, 0.0, 1.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [0.5, -0.5, 0.5], + normal: [0.0, 0.0, 1.0], + uv: [1.0, 0.0], + }, + Vertex { + position: [0.5, 0.5, 0.5], + normal: [0.0, 0.0, 1.0], + uv: [1.0, 1.0], + }, + Vertex { + position: [-0.5, 0.5, 0.5], + normal: [0.0, 0.0, 1.0], + uv: [0.0, 1.0], + }, + Vertex { + position: [0.5, -0.5, 0.5], + normal: [1.0, 0.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [0.5, -0.5, -0.5], + normal: [1.0, 0.0, 0.0], + uv: [1.0, 0.0], + }, + Vertex { + position: [0.5, 0.5, -0.5], + normal: [1.0, 0.0, 0.0], + uv: [1.0, 1.0], + }, + Vertex { + position: [0.5, 0.5, 0.5], + normal: [1.0, 0.0, 0.0], + uv: [0.0, 1.0], + }, + Vertex { + position: [0.5, -0.5, -0.5], + normal: [0.0, 0.0, -1.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [-0.5, -0.5, -0.5], + normal: [0.0, 0.0, -1.0], + uv: [1.0, 0.0], + }, + Vertex { + position: [-0.5, 0.5, -0.5], + normal: [0.0, 0.0, -1.0], + uv: [1.0, 1.0], + }, + Vertex { + position: [0.5, 0.5, -0.5], + normal: [0.0, 0.0, -1.0], + uv: [0.0, 1.0], + }, + Vertex { + position: [-0.5, -0.5, -0.5], + normal: [-1.0, 0.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [-0.5, -0.5, 0.5], + normal: [-1.0, 0.0, 0.0], + uv: [1.0, 0.0], + }, + Vertex { + position: [-0.5, 0.5, 0.5], + normal: [-1.0, 0.0, 0.0], + uv: [1.0, 1.0], + }, + Vertex { + position: [-0.5, 0.5, -0.5], + normal: [-1.0, 0.0, 0.0], + uv: [0.0, 1.0], + }, + Vertex { + position: [-0.5, 0.5, 0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [0.5, 0.5, 0.5], + normal: [0.0, 1.0, 0.0], + uv: [1.0, 0.0], + }, + Vertex { + position: [0.5, 0.5, -0.5], + normal: [0.0, 1.0, 0.0], + uv: [1.0, 1.0], + }, + Vertex { + position: [-0.5, 0.5, -0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 1.0], + }, + Vertex { + position: [-0.5, -0.5, -0.5], + normal: [0.0, -1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [0.5, -0.5, -0.5], + normal: [0.0, -1.0, 0.0], + uv: [1.0, 0.0], + }, + Vertex { + position: [0.5, -0.5, 0.5], + normal: [0.0, -1.0, 0.0], + uv: [1.0, 1.0], + }, + Vertex { + position: [-0.5, -0.5, 0.5], + normal: [0.0, -1.0, 0.0], + uv: [0.0, 1.0], + }, + ]; + + let indices = vec![ + 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, 12, 13, 14, 14, 15, 12, 16, + 17, 18, 18, 19, 16, 20, 21, 22, 22, 23, 20, + ]; + + Mesh::new(device, &vertices, &indices) + } + + pub fn create_plane_mesh( + device: &wgpu::Device, + width: f32, + height: f32, + subdivisions_x: u32, + subdivisions_y: u32, + ) -> Mesh + { + let mut vertices = Vec::new(); + let mut indices = Vec::new(); + + for y in 0..=subdivisions_y + { + for x in 0..=subdivisions_x + { + let fx = x as f32 / subdivisions_x as f32; + let fy = y as f32 / subdivisions_y as f32; + + let px = (fx - 0.5) * width; + let py = 0.0; + let pz = (fy - 0.5) * height; + + vertices.push(Vertex { + position: [px, py, pz], + normal: [0.0, 1.0, 0.0], + uv: [fx, fy], + }); + } + } + + for y in 0..subdivisions_y + { + for x in 0..subdivisions_x + { + let row_stride = subdivisions_x + 1; + let i0 = y * row_stride + x; + let i1 = i0 + 1; + let i2 = i0 + row_stride; + let i3 = i2 + 1; + + indices.push(i0); + indices.push(i2); + indices.push(i1); + + indices.push(i1); + indices.push(i2); + indices.push(i3); + } + } + + Mesh::new(device, &vertices, &indices) + } + + pub fn create_wireframe_box(device: &wgpu::Device) -> Mesh + { + let vertices = vec![ + Vertex { + position: [-0.5, -0.5, -0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [0.5, -0.5, -0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [0.5, -0.5, 0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [-0.5, -0.5, 0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [-0.5, 0.5, -0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [0.5, 0.5, -0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [0.5, 0.5, 0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex { + position: [-0.5, 0.5, 0.5], + normal: [0.0, 1.0, 0.0], + uv: [0.0, 0.0], + }, + ]; + + let indices = vec![ + 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7, + ]; + + Mesh::new(device, &vertices, &indices) + } + + pub fn load_gltf_mesh( + device: &wgpu::Device, + path: impl AsRef, + ) -> Result> + { + let (gltf, buffers, _images) = gltf::import(path)?; + + let mut all_vertices = Vec::new(); + let mut all_indices = Vec::new(); + + for scene in gltf.scenes() + { + for node in scene.nodes() + { + Self::process_node( + &node, + &buffers, + Mat4::IDENTITY, + &mut all_vertices, + &mut all_indices, + )?; + } + } + + Ok(Mesh::new(device, &all_vertices, &all_indices)) + } + + fn process_node( + node: &gltf::Node, + buffers: &[gltf::buffer::Data], + parent_transform: Mat4, + all_vertices: &mut Vec, + all_indices: &mut Vec, + ) -> Result<(), Box> + { + let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix()); + let global_transform = parent_transform * local_transform; + + if let Some(mesh) = node.mesh() + { + for primitive in mesh.primitives() + { + let reader = + primitive.reader(|buffer| buffers.get(buffer.index()).map(|data| &data[..])); + + let positions = reader + .read_positions() + .ok_or("Missing position data")? + .collect::>(); + + let normals = reader + .read_normals() + .ok_or("Missing normal data")? + .collect::>(); + + let uvs = reader + .read_tex_coords(0) + .map(|iter| iter.into_f32().collect::>()) + .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]); + + let base_index = all_vertices.len() as u32; + + let normal_matrix = global_transform.inverse().transpose(); + + for ((pos, normal), uv) in positions.iter().zip(normals.iter()).zip(uvs.iter()) + { + let pos_vec3 = Vec3::from(*pos); + let normal_vec3 = Vec3::from(*normal); + + let transformed_pos = global_transform.transform_point3(pos_vec3); + let transformed_normal = + normal_matrix.transform_vector3(normal_vec3).normalize(); + + all_vertices.push(Vertex { + position: transformed_pos.into(), + normal: transformed_normal.into(), + uv: *uv, + }); + } + + if let Some(indices_reader) = reader.read_indices() + { + all_indices.extend(indices_reader.into_u32().map(|i| i + base_index)); + } + } + } + + for child in node.children() + { + Self::process_node(&child, buffers, global_transform, all_vertices, all_indices)?; + } + + Ok(()) + } + + pub fn load_mesh(path: impl AsRef) -> Result> + { + crate::render::with_device(|device| Mesh::load_gltf_mesh(device, path)) + } +} diff --git a/src/physics.rs b/src/physics.rs new file mode 100644 index 0000000..1640692 --- /dev/null +++ b/src/physics.rs @@ -0,0 +1,288 @@ +use std::cell::RefCell; + +use nalgebra::DMatrix; +use rapier3d::{ + math::Vector, + na::vector, + prelude::{ + CCDSolver, Collider, ColliderHandle, ColliderSet, DefaultBroadPhase, ImpulseJointSet, + IntegrationParameters, IslandManager, MultibodyJointSet, NarrowPhase, PhysicsPipeline, Ray, + RigidBody, RigidBodyHandle, RigidBodySet, + }, +}; + +thread_local! { + static GLOBAL_PHYSICS: RefCell = RefCell::new(PhysicsManager::new()); +} + +pub struct HeightfieldData +{ + pub heights: DMatrix, + pub scale: Vector, + pub position: Vector, +} + +pub struct PhysicsManager +{ + rigidbody_set: RigidBodySet, + collider_set: ColliderSet, + gravity: Vector, + integration_parameters: IntegrationParameters, + physics_pipeline: PhysicsPipeline, + island_manager: IslandManager, + broad_phase: DefaultBroadPhase, + narrow_phase: NarrowPhase, + impulse_joint_set: ImpulseJointSet, + multibody_joint_set: MultibodyJointSet, + ccd_solver: CCDSolver, + physics_hooks: (), + event_handler: (), + heightfield_data: Option, +} + +impl PhysicsManager +{ + pub fn new() -> Self + { + Self { + rigidbody_set: RigidBodySet::new(), + collider_set: ColliderSet::new(), + gravity: vector![0.0, -9.81, 0.0], + integration_parameters: IntegrationParameters::default(), + physics_pipeline: PhysicsPipeline::new(), + island_manager: IslandManager::new(), + broad_phase: DefaultBroadPhase::new(), + narrow_phase: NarrowPhase::new(), + impulse_joint_set: ImpulseJointSet::new(), + multibody_joint_set: MultibodyJointSet::new(), + ccd_solver: CCDSolver::new(), + physics_hooks: (), + event_handler: (), + heightfield_data: None, + } + } + + fn step(&mut self) + { + self.physics_pipeline.step( + &self.gravity, + &self.integration_parameters, + &mut self.island_manager, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.rigidbody_set, + &mut self.collider_set, + &mut self.impulse_joint_set, + &mut self.multibody_joint_set, + &mut self.ccd_solver, + &self.physics_hooks, + &self.event_handler, + ); + } + + fn add_collider_internal( + &mut self, + collider: Collider, + parent: Option, + ) -> ColliderHandle + { + if let Some(parent) = parent + { + self.collider_set + .insert_with_parent(collider, parent, &mut self.rigidbody_set) + } + else + { + self.collider_set.insert(collider) + } + } + + pub fn physics_step() + { + GLOBAL_PHYSICS.with(|manager| manager.borrow_mut().step()); + } + + pub fn add_rigidbody(rigidbody: RigidBody) -> RigidBodyHandle + { + GLOBAL_PHYSICS.with(|manager| manager.borrow_mut().rigidbody_set.insert(rigidbody)) + } + + pub fn add_collider(collider: Collider, parent: Option) -> ColliderHandle + { + GLOBAL_PHYSICS.with(|manager| manager.borrow_mut().add_collider_internal(collider, parent)) + } + + pub fn with_rigidbody_mut(handle: RigidBodyHandle, f: F) -> Option + where + F: FnOnce(&mut RigidBody) -> R, + { + GLOBAL_PHYSICS.with(|manager| manager.borrow_mut().rigidbody_set.get_mut(handle).map(f)) + } + + pub fn with_collider_mut(handle: ColliderHandle, f: F) -> Option + where + F: FnOnce(&mut Collider) -> R, + { + GLOBAL_PHYSICS.with(|manager| manager.borrow_mut().collider_set.get_mut(handle).map(f)) + } + + pub fn get_rigidbody_position(handle: RigidBodyHandle) -> Option> + { + GLOBAL_PHYSICS.with(|manager| { + manager + .borrow() + .rigidbody_set + .get(handle) + .map(|rb| *rb.position()) + }) + } + + pub fn get_all_collider_aabbs() -> Vec<([f32; 3], [f32; 3])> + { + GLOBAL_PHYSICS.with(|manager| { + let manager = manager.borrow(); + manager + .collider_set + .iter() + .map(|(_, collider)| { + let aabb = collider.compute_aabb(); + ( + [aabb.mins.x, aabb.mins.y, aabb.mins.z], + [aabb.maxs.x, aabb.maxs.y, aabb.maxs.z], + ) + }) + .collect() + }) + } + + pub fn raycast( + origin: Vector, + direction: Vector, + max_distance: f32, + exclude_rigidbody: Option, + ) -> Option<(ColliderHandle, f32)> + { + GLOBAL_PHYSICS.with(|manager| { + let manager = manager.borrow(); + let ray = Ray::new(origin.into(), direction); + + let mut closest_hit: Option<(ColliderHandle, f32)> = None; + + for (handle, collider) in manager.collider_set.iter() + { + if let Some(exclude_rb) = exclude_rigidbody + { + if collider.parent() == Some(exclude_rb) + { + continue; + } + } + + if let Some(toi) = + collider + .shape() + .cast_ray(collider.position(), &ray, max_distance, true) + { + if let Some((_, closest_toi)) = closest_hit + { + if toi < closest_toi + { + closest_hit = Some((handle, toi)); + } + } + else + { + closest_hit = Some((handle, toi)); + } + } + } + + closest_hit + }) + } + + pub fn set_heightfield_data(heights: DMatrix, scale: Vector, position: Vector) + { + GLOBAL_PHYSICS.with(|manager| { + manager.borrow_mut().heightfield_data = Some(HeightfieldData { + heights, + scale, + position, + }); + }); + } + + pub fn get_terrain_height_at(x: f32, z: f32) -> Option + { + GLOBAL_PHYSICS.with(|manager| { + let manager = manager.borrow(); + let data = manager.heightfield_data.as_ref()?; + + let local_x = x - data.position.x; + let local_z = z - data.position.z; + + let normalized_x = (local_x / data.scale.x) + 0.5; + let normalized_z = (local_z / data.scale.z) + 0.5; + + let nrows = data.heights.nrows(); + let ncols = data.heights.ncols(); + + let row_f = normalized_z * (nrows - 1) as f32; + let col_f = normalized_x * (ncols - 1) as f32; + + if row_f < 0.0 + || row_f >= (nrows - 1) as f32 + || col_f < 0.0 + || col_f >= (ncols - 1) as f32 + { + return None; + } + + let row0 = row_f.floor() as usize; + let row1 = (row0 + 1).min(nrows - 1); + let col0 = col_f.floor() as usize; + let col1 = (col0 + 1).min(ncols - 1); + + let frac_row = row_f - row0 as f32; + let frac_col = col_f - col0 as f32; + + let h00 = data.heights[(row0, col0)]; + let h01 = data.heights[(row0, col1)]; + let h10 = data.heights[(row1, col0)]; + let h11 = data.heights[(row1, col1)]; + + let h0 = h00 * (1.0 - frac_col) + h01 * frac_col; + let h1 = h10 * (1.0 - frac_col) + h11 * frac_col; + let height = h0 * (1.0 - frac_row) + h1 * frac_row; + + Some(height * data.scale.y + data.position.y) + }) + } + + pub fn get_terrain_slope_in_direction(x: f32, z: f32, direction_x: f32, direction_z: f32) + -> f32 + { + const SAMPLE_DISTANCE: f32 = 0.5; + + let dir_len = (direction_x * direction_x + direction_z * direction_z).sqrt(); + if dir_len < 0.001 + { + return 0.0; + } + + let norm_dir_x = direction_x / dir_len; + let norm_dir_z = direction_z / dir_len; + + let height_current = Self::get_terrain_height_at(x, z).unwrap_or(0.0); + let height_forward = Self::get_terrain_height_at( + x + norm_dir_x * SAMPLE_DISTANCE, + z + norm_dir_z * SAMPLE_DISTANCE, + ) + .unwrap_or(height_current); + + let height_diff = height_forward - height_current; + let slope_angle = (height_diff / SAMPLE_DISTANCE).atan(); + + slope_angle + } +} diff --git a/src/picking.rs b/src/picking.rs new file mode 100644 index 0000000..e331a5b --- /dev/null +++ b/src/picking.rs @@ -0,0 +1,87 @@ +use crate::camera::Camera; +use crate::mesh::Mesh; +use glam::{Mat4, Vec3, Vec4}; + +pub struct Ray +{ + pub origin: Vec3, + pub direction: Vec3, +} + +impl Ray +{ + pub fn from_screen_position( + screen_x: f32, + screen_y: f32, + screen_width: u32, + screen_height: u32, + camera: &Camera, + ) -> Self + { + let ndc_x = (2.0 * screen_x) / screen_width as f32 - 1.0; + let ndc_y = 1.0 - (2.0 * screen_y) / screen_height as f32; + + let clip_coords = Vec4::new(ndc_x, ndc_y, -1.0, 1.0); + + let view_matrix = camera.view_matrix(); + let projection_matrix = camera.projection_matrix(); + let inv_projection = projection_matrix.inverse(); + let inv_view = view_matrix.inverse(); + + let eye_coords = inv_projection * clip_coords; + let eye_coords = Vec4::new(eye_coords.x, eye_coords.y, -1.0, 0.0); + + let world_coords = inv_view * eye_coords; + let direction = Vec3::new(world_coords.x, world_coords.y, world_coords.z).normalize(); + + Ray { + origin: camera.position, + direction, + } + } + + pub fn intersects_mesh(&self, mesh: &Mesh, transform: &Mat4) -> Option + { + let inv_transform = transform.inverse(); + let local_origin = inv_transform.transform_point3(self.origin); + let local_direction = inv_transform.transform_vector3(self.direction).normalize(); + + let mut closest_distance = f32::MAX; + let mut hit = false; + + for triangle_idx in (0..mesh.num_indices).step_by(3) + { + let distance = + self.intersects_triangle_local(local_origin, local_direction, mesh, triangle_idx); + + if let Some(d) = distance + { + if d < closest_distance + { + closest_distance = d; + hit = true; + } + } + } + + if hit + { + Some(closest_distance) + } + else + { + None + } + } + + fn intersects_triangle_local( + &self, + local_origin: Vec3, + local_direction: Vec3, + _mesh: &Mesh, + _triangle_idx: u32, + ) -> Option + { + None + } +} diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..9ed81a4 --- /dev/null +++ b/src/player.rs @@ -0,0 +1,604 @@ +use std::rc::Rc; + +use glam::Vec3; +use kurbo::ParamCurve; +use rapier3d::{ + control::{CharacterAutostep, KinematicCharacterController}, + math::Vector, + prelude::{ColliderBuilder, RigidBodyBuilder}, +}; + +use crate::{ + components::{ + jump::JumpComponent, InputComponent, MeshComponent, MovementComponent, PhysicsComponent, + }, + entity::EntityHandle, + mesh::Mesh, + physics::PhysicsManager, + render::Pipeline, + state::{State, StateMachine}, + world::{Transform, World}, +}; + +pub struct Player; + +impl Player +{ + pub fn spawn(world: &mut World) -> EntityHandle + { + let entity = world.spawn(); + + let initial_position = Vec3::new(0.0, 5.0, 0.0); + + let rigidbody = RigidBodyBuilder::kinematic_position_based() + .translation(initial_position.into()) + .build(); + let collider = ColliderBuilder::capsule_y(0.5, 0.5).build(); + let _controller = KinematicCharacterController { + slide: true, + autostep: Some(CharacterAutostep::default()), + max_slope_climb_angle: 45.0, + ..Default::default() + }; + + let rigidbody_handle = PhysicsManager::add_rigidbody(rigidbody); + let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle)); + + let mesh = Mesh::load_mesh("meshes/player_mesh.glb").expect("missing player mesh"); + + let falling_state = PlayerFallingState { entity }; + let idle_state = PlayerIdleState { entity }; + let walking_state = PlayerWalkingState { + entity, + enter_time_stamp: 0.0, + }; + let jumping_state = PlayerJumpingState { + entity, + enter_time_stamp: 0.0, + }; + + let mut state_machine = StateMachine::new(Box::new(falling_state)); + state_machine.add_state(walking_state); + state_machine.add_state(idle_state); + state_machine.add_state(jumping_state); + + let entity_id = entity; + + state_machine.add_transition::(move |world| { + let is_grounded = world + .movements + .with(entity_id, |m| m.movement_config.movement_context.is_floored) + .unwrap_or(false); + let has_input = world + .inputs + .with(entity_id, |i| i.move_direction.length() > 0.01) + .unwrap_or(false); + is_grounded && !has_input + }); + + state_machine.add_transition::(move |world| { + let is_grounded = world + .movements + .with(entity_id, |m| m.movement_config.movement_context.is_floored) + .unwrap_or(false); + let has_input = world + .inputs + .with(entity_id, |i| i.move_direction.length() > 0.01) + .unwrap_or(false); + is_grounded && has_input + }); + + state_machine.add_transition::(move |world| { + let is_grounded = world + .movements + .with(entity_id, |m| m.movement_config.movement_context.is_floored) + .unwrap_or(false); + let has_input = world + .inputs + .with(entity_id, |i| i.move_direction.length() > 0.01) + .unwrap_or(false); + is_grounded && has_input + }); + + state_machine.add_transition::(move |world| { + let is_grounded = world + .movements + .with(entity_id, |m| m.movement_config.movement_context.is_floored) + .unwrap_or(false); + let has_input = world + .inputs + .with(entity_id, |i| i.move_direction.length() > 0.01) + .unwrap_or(false); + is_grounded && !has_input + }); + + state_machine.add_transition::(move |world| { + let is_grounded = world + .movements + .with(entity_id, |m| m.movement_config.movement_context.is_floored) + .unwrap_or(false); + !is_grounded + }); + + state_machine.add_transition::(move |world| { + let is_grounded = world + .movements + .with(entity_id, |m| m.movement_config.movement_context.is_floored) + .unwrap_or(false); + !is_grounded + }); + + state_machine.add_transition::(move |world| { + let is_grounded = world + .movements + .with(entity_id, |m| m.movement_config.movement_context.is_floored) + .unwrap_or(false); + let jump_pressed = world + .inputs + .with(entity_id, |i| i.jump_just_pressed) + .unwrap_or(false); + is_grounded && jump_pressed + }); + + state_machine.add_transition::(move |world| { + let is_grounded = world + .movements + .with(entity_id, |m| m.movement_config.movement_context.is_floored) + .unwrap_or(false); + let jump_pressed = world + .inputs + .with(entity_id, |i| i.jump_just_pressed) + .unwrap_or(false); + is_grounded && jump_pressed + }); + + state_machine.add_transition::(move |world| { + world + .jumps + .with(entity_id, |jump| { + jump.jump_config.jump_context.duration >= jump.jump_config.jump_duration + }) + .unwrap_or(true) + }); + + world + .transforms + .insert(entity, Transform::from_position(initial_position)); + world.movements.insert(entity, MovementComponent::new()); + world.jumps.insert(entity, JumpComponent::new()); + world.inputs.insert(entity, InputComponent::default()); + world.physics.insert( + entity, + PhysicsComponent { + rigidbody: rigidbody_handle, + collider: Some(collider_handle), + }, + ); + world.meshes.insert( + entity, + MeshComponent { + mesh: Rc::new(mesh), + pipeline: Pipeline::Render, + }, + ); + world.player_tags.insert(entity); + world.state_machines.insert(entity, state_machine); + + entity + } + + pub fn despawn(world: &mut World, entity: EntityHandle) + { + world.despawn(entity); + } +} + +pub struct PlayerFallingState +{ + entity: EntityHandle, +} + +impl State for PlayerFallingState +{ + fn get_state_name(&self) -> &'static str + { + "PlayerFallingState" + } + + fn on_state_enter(&mut self, world: &mut World) + { + println!("entered falling"); + } + + fn on_state_exit(&mut self, world: &mut World) {} + + fn on_state_update(&mut self, world: &mut World, delta: f32) {} + + fn on_state_physics_update(&mut self, world: &mut World, delta: f32) + { + const GRAVITY: f32 = -9.81 * 5.0; + const GROUND_CHECK_DISTANCE: f32 = 0.6; + + let (current_pos, velocity) = world + .physics + .with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + let mut vel = *rigidbody.linvel(); + vel.y += GRAVITY * delta; + (*rigidbody.translation(), vel) + }) + }) + .flatten() + .unwrap(); + + let terrain_height = PhysicsManager::get_terrain_height_at(current_pos.x, current_pos.z); + + let is_grounded = if let Some(height) = terrain_height + { + let target_y = height + 1.0; + let distance_to_ground = current_pos.y - target_y; + + if distance_to_ground < GROUND_CHECK_DISTANCE && velocity.y <= 0.01 + { + world.physics.with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + let next_pos = Vector::new(current_pos.x, target_y, current_pos.z); + rigidbody.set_next_kinematic_translation(next_pos); + rigidbody.set_linvel(Vector::new(velocity.x, 0.0, velocity.z), true); + }); + }); + true + } + else + { + world.physics.with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + let next_pos = current_pos + velocity * delta; + rigidbody.set_next_kinematic_translation(next_pos); + rigidbody.set_linvel(velocity, true); + }); + }); + false + } + } + else + { + world.physics.with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + let next_pos = current_pos + velocity * delta; + rigidbody.set_next_kinematic_translation(next_pos); + rigidbody.set_linvel(velocity, true); + }); + }); + false + }; + + world.movements.with_mut(self.entity, |movement| { + movement.movement_config.movement_context.is_floored = is_grounded; + }); + } +} + +pub struct PlayerIdleState +{ + entity: EntityHandle, +} + +impl State for PlayerIdleState +{ + fn get_state_name(&self) -> &'static str + { + "PlayerIdleState" + } + + fn on_state_enter(&mut self, world: &mut World) + { + println!("entered idle"); + + world.physics.with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + let current_velocity = *rigidbody.linvel(); + let idle_damping = world + .movements + .with(self.entity, |m| m.movement_config.idle_damping) + .unwrap_or(0.1); + + let horizontal_velocity = Vec3::new(current_velocity.x, 0.0, current_velocity.z); + let new_horizontal_velocity = horizontal_velocity * idle_damping; + + rigidbody.set_linvel( + Vector::new( + new_horizontal_velocity.x, + current_velocity.y, + new_horizontal_velocity.z, + ), + true, + ); + }); + }); + } + + fn on_state_exit(&mut self, _world: &mut World) {} + + fn on_state_update(&mut self, _world: &mut World, _delta: f32) {} + + fn on_state_physics_update(&mut self, world: &mut World, delta: f32) + { + const GROUND_CHECK_DISTANCE: f32 = 0.6; + + let current_translation = world + .physics + .with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + *rigidbody.translation() + }) + }) + .flatten() + .unwrap(); + + let terrain_height = + PhysicsManager::get_terrain_height_at(current_translation.x, current_translation.z); + + if let Some(height) = terrain_height + { + let target_y = height + 1.0; + let distance_to_ground = current_translation.y - target_y; + + if distance_to_ground.abs() < GROUND_CHECK_DISTANCE + { + world.physics.with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + let next_translation = + Vector::new(current_translation.x, target_y, current_translation.z); + rigidbody.set_next_kinematic_translation(next_translation); + }); + }); + + world.movements.with_mut(self.entity, |movement| { + movement.movement_config.movement_context.is_floored = true; + }); + } + } + } +} + +pub struct PlayerWalkingState +{ + entity: EntityHandle, + enter_time_stamp: f32, +} + +impl State for PlayerWalkingState +{ + fn get_state_name(&self) -> &'static str + { + "PlayerWalkingState" + } + + fn on_state_enter(&mut self, _world: &mut World) + { + use crate::utility::time::Time; + self.enter_time_stamp = Time::get_time_elapsed(); + println!("entered walking"); + } + + fn on_state_exit(&mut self, _world: &mut World) {} + + fn on_state_update(&mut self, world: &mut World, delta: f32) {} + + fn on_state_physics_update(&mut self, world: &mut World, delta: f32) + { + use crate::utility::time::Time; + + let (movement_input, walking_config) = world + .movements + .with(self.entity, |movement| { + let input = world + .inputs + .with(self.entity, |input| input.move_direction) + .unwrap_or(Vec3::ZERO); + (input, movement.movement_config.clone()) + }) + .unwrap(); + + let current_time = Time::get_time_elapsed(); + let elapsed_time = current_time - self.enter_time_stamp; + + let t = (elapsed_time / walking_config.walking_acceleration_duration).clamp(0.0, 1.0); + let acceleration_amount = walking_config.walking_acceleration_curve.eval(t as f64).y as f32; + + let current_translation = world + .physics + .with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + *rigidbody.translation() + }) + }) + .flatten() + .unwrap(); + + let terrain_height = + PhysicsManager::get_terrain_height_at(current_translation.x, current_translation.z); + + let target_y = if let Some(height) = terrain_height + { + height + 1.0 + } + else + { + current_translation.y + }; + + let slope_angle = if movement_input.length_squared() > 0.01 + { + PhysicsManager::get_terrain_slope_in_direction( + current_translation.x, + current_translation.z, + movement_input.x, + movement_input.z, + ) + } + else + { + 0.0 + }; + + let slope_multiplier = { + const MAX_SLOPE_ANGLE: f32 = std::f32::consts::PI / 4.0; + + if slope_angle > 0.0 + { + let uphill_factor = (slope_angle / MAX_SLOPE_ANGLE).min(1.0); + 1.0 - uphill_factor * 0.9 + } + else + { + let downhill_factor = (slope_angle.abs() / MAX_SLOPE_ANGLE).min(1.0); + 1.0 + downhill_factor * 0.5 + } + }; + + world.physics.with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + let current_velocity = *rigidbody.linvel(); + + let horizontal_velocity = Vec3::new(current_velocity.x, 0.0, current_velocity.z); + + let walking_force = movement_input + * walking_config.walking_acceleration + * delta + * acceleration_amount; + + let new_horizontal_velocity = (walking_force + + horizontal_velocity * walking_config.walking_damping) + .clamp_length_max(walking_config.max_walking_speed * slope_multiplier); + + let next_translation = Vector::new( + current_translation.x + new_horizontal_velocity.x * delta, + target_y, + current_translation.z + new_horizontal_velocity.z * delta, + ); + + rigidbody.set_linvel( + Vector::new(new_horizontal_velocity.x, 0.0, new_horizontal_velocity.z), + true, + ); + rigidbody.set_next_kinematic_translation(next_translation); + }); + }); + + world.movements.with_mut(self.entity, |movement| { + movement.movement_config.movement_context.is_floored = terrain_height.is_some(); + }); + + if movement_input.length_squared() > 0.1 + { + world.transforms.with_mut(self.entity, |transform| { + let target_rotation = f32::atan2(movement_input.x, movement_input.z); + transform.rotation.y = target_rotation; + }); + } + } +} + +pub struct PlayerJumpingState +{ + entity: EntityHandle, + enter_time_stamp: f32, +} + +impl State for PlayerJumpingState +{ + fn get_state_name(&self) -> &'static str + { + "PlayerJumpingState" + } + + fn on_state_enter(&mut self, world: &mut World) + { + use crate::utility::time::Time; + self.enter_time_stamp = Time::get_time_elapsed(); + + let current_position = world.transforms.get(self.entity).unwrap().position; + + world.jumps.with_mut(self.entity, |jump| { + jump.jump_config.jump_context.in_progress = true; + jump.jump_config.jump_context.execution_time = self.enter_time_stamp; + jump.jump_config.jump_context.origin_height = current_position.y; + jump.jump_config.jump_context.duration = 0.0; + jump.jump_config.jump_context.normal = Vec3::Y; + }); + + println!("entered jumping"); + } + + fn on_state_exit(&mut self, world: &mut World) + { + world.jumps.with_mut(self.entity, |jump| { + jump.jump_config.jump_context.in_progress = false; + jump.jump_config.jump_context.duration = 0.0; + }); + + println!("exited jumping"); + } + + fn on_state_update(&mut self, _world: &mut World, _delta: f32) {} + + fn on_state_physics_update(&mut self, world: &mut World, delta: f32) + { + use crate::utility::time::Time; + + let current_time = Time::get_time_elapsed(); + + world.jumps.with_mut(self.entity, |jump| { + jump.jump_config.jump_context.duration = current_time - jump.jump_config.jump_context.execution_time; + }); + + let jump_config = world + .jumps + .with_mut(self.entity, |jump| jump.jump_config.clone()) + .unwrap(); + + let elapsed_time = jump_config.jump_context.duration; + let normalized_time = (elapsed_time / jump_config.jump_duration).min(1.0); + let height_progress = jump_config.jump_curve.eval(normalized_time as f64).y as f32; + + let origin_height = jump_config.jump_context.origin_height; + let target_height = origin_height + height_progress * jump_config.jump_height; + + let current_translation = world + .physics + .with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + *rigidbody.translation() + }) + }) + .flatten() + .unwrap(); + + let current_y = current_translation.y; + let height_diff = target_height - current_y; + let required_velocity = height_diff / delta; + + world.physics.with(self.entity, |physics| { + PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { + let current_velocity = *rigidbody.linvel(); + let next_translation = Vector::new( + current_translation.x + current_velocity.x * delta, + current_translation.y + required_velocity * delta, + current_translation.z + current_velocity.z * delta, + ); + + rigidbody.set_linvel( + Vector::new(current_velocity.x, required_velocity, current_velocity.z), + true, + ); + rigidbody.set_next_kinematic_translation(next_translation); + }); + }); + + world.movements.with_mut(self.entity, |movement| { + movement.movement_config.movement_context.is_floored = false; + }); + } +} diff --git a/src/postprocess.rs b/src/postprocess.rs new file mode 100644 index 0000000..4d614df --- /dev/null +++ b/src/postprocess.rs @@ -0,0 +1,200 @@ +use bytemuck::{Pod, Zeroable}; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +pub struct ScreenVertex +{ + pub position: [f32; 2], + pub uv: [f32; 2], +} + +impl ScreenVertex +{ + pub fn desc() -> wgpu::VertexBufferLayout<'static> + { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + }, + ], + } + } +} + +pub struct LowResFramebuffer +{ + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub depth_view: wgpu::TextureView, + pub sampler: wgpu::Sampler, + pub width: u32, + pub height: u32, +} + +impl LowResFramebuffer +{ + pub fn new(device: &wgpu::Device, width: u32, height: u32, format: wgpu::TextureFormat) + -> Self + { + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Low Res Color Texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Low Res Depth Texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + + let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("Low Res Sampler"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + Self { + texture, + view, + depth_view, + sampler, + width, + height, + } + } +} + +pub fn create_fullscreen_quad(device: &wgpu::Device) -> (wgpu::Buffer, wgpu::Buffer, u32) +{ + use wgpu::util::DeviceExt; + + let vertices = [ + ScreenVertex { + position: [-1.0, -1.0], + uv: [0.0, 1.0], + }, + ScreenVertex { + position: [1.0, -1.0], + uv: [1.0, 1.0], + }, + ScreenVertex { + position: [1.0, 1.0], + uv: [1.0, 0.0], + }, + ScreenVertex { + position: [-1.0, 1.0], + uv: [0.0, 0.0], + }, + ]; + + let indices: [u16; 6] = [0, 1, 2, 2, 3, 0]; + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Fullscreen Quad Vertex Buffer"), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Fullscreen Quad Index Buffer"), + contents: bytemuck::cast_slice(&indices), + usage: wgpu::BufferUsages::INDEX, + }); + + (vertex_buffer, index_buffer, indices.len() as u32) +} + +pub fn create_blit_pipeline( + device: &wgpu::Device, + format: wgpu::TextureFormat, + bind_group_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline +{ + let shader_source = + std::fs::read_to_string("shaders/blit.wgsl").expect("Failed to read blit shader"); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Blit Shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.into()), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Blit Pipeline Layout"), + bind_group_layouts: &[bind_group_layout], + push_constant_ranges: &[], + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Blit Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[ScreenVertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }) +} diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..9eacb47 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,800 @@ +use crate::camera::{Camera, CameraUniforms}; +use crate::mesh::Mesh; +use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer}; +use crate::shader::create_render_pipeline; +use crate::terrain::create_terrain_render_pipeline; +use crate::utility::transform::Transform; +use bytemuck::{Pod, Zeroable}; +use glam::Mat4; +use std::cell::RefCell; +use std::rc::Rc; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct TerrainUniforms +{ + model: [[f32; 4]; 4], + view: [[f32; 4]; 4], + projection: [[f32; 4]; 4], + height_scale: f32, + time: f32, + _padding: [f32; 2], +} + +impl TerrainUniforms +{ + fn new(model: Mat4, view: Mat4, projection: Mat4, height_scale: f32, time: f32) -> Self + { + Self { + model: model.to_cols_array_2d(), + view: view.to_cols_array_2d(), + projection: projection.to_cols_array_2d(), + height_scale, + time, + _padding: [0.0; 2], + } + } +} + +#[derive(Clone, Copy)] +pub enum Pipeline +{ + Render, + Terrain, + Wireframe, +} + +pub struct DrawCall +{ + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub num_indices: u32, + pub model: Mat4, + pub pipeline: Pipeline, +} + +pub struct TerrainData +{ + pub height_texture: wgpu::Texture, + pub height_view: wgpu::TextureView, + pub height_sampler: wgpu::Sampler, +} + +pub struct Renderer +{ + pub device: wgpu::Device, + pub queue: wgpu::Queue, + pub surface: wgpu::Surface<'static>, + pub config: wgpu::SurfaceConfiguration, + + framebuffer: LowResFramebuffer, + render_pipeline: wgpu::RenderPipeline, + + uniform_buffer: wgpu::Buffer, + bind_group: wgpu::BindGroup, + + quad_vb: wgpu::Buffer, + quad_ib: wgpu::Buffer, + quad_num_indices: u32, + blit_pipeline: wgpu::RenderPipeline, + blit_bind_group: wgpu::BindGroup, + + terrain_pipeline: Option, + terrain_bind_group_layout: wgpu::BindGroupLayout, + terrain_uniform_buffer: wgpu::Buffer, + terrain_bind_group: Option, + terrain_height_scale: f32, + + wireframe_pipeline: wgpu::RenderPipeline, +} + +impl Renderer +{ + pub async fn new( + window: &sdl3::video::Window, + render_scale: u32, + ) -> Result> + { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::VULKAN, + ..Default::default() + }); + + let surface = unsafe { + let target = wgpu::SurfaceTargetUnsafe::from_window(window)?; + instance.create_surface_unsafe(target)? + }; + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .map_err(|_| "Failed to find adapter")?; + + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor::default()) + .await?; + + let size = window.size(); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface.get_capabilities(&adapter).formats[0], + width: size.0, + height: size.1, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + surface.configure(&device, &config); + + let low_res_width = config.width / render_scale; + let low_res_height = config.height / render_scale; + + let framebuffer = + LowResFramebuffer::new(&device, low_res_width, low_res_height, config.format); + + let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Uniform Buffer"), + size: std::mem::size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Bind Group Layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Bind Group"), + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }], + }); + + let render_pipeline = create_render_pipeline(&device, &config, &bind_group_layout); + + let (quad_vb, quad_ib, quad_num_indices) = create_fullscreen_quad(&device); + + let blit_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Blit Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Blit Bind Group"), + layout: &blit_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&framebuffer.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&framebuffer.sampler), + }, + ], + }); + + let blit_pipeline = create_blit_pipeline(&device, config.format, &blit_bind_group_layout); + + let terrain_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Terrain Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + ], + }); + + let terrain_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Terrain Uniform Buffer"), + size: std::mem::size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let wireframe_pipeline = + create_wireframe_pipeline(&device, config.format, &bind_group_layout); + + Ok(Self { + device, + queue, + surface, + config, + framebuffer, + render_pipeline, + uniform_buffer, + bind_group, + quad_vb, + quad_ib, + quad_num_indices, + blit_pipeline, + blit_bind_group, + terrain_pipeline: None, + terrain_bind_group_layout, + terrain_uniform_buffer, + terrain_bind_group: None, + terrain_height_scale: 10.0, + wireframe_pipeline, + }) + } + + pub fn render(&mut self, camera: &Camera, draw_calls: &[DrawCall], time: f32) + { + let view = camera.view_matrix(); + let projection = camera.projection_matrix(); + + for (i, draw_call) in draw_calls.iter().enumerate() + { + match draw_call.pipeline + { + Pipeline::Render | Pipeline::Wireframe => + { + let uniforms = CameraUniforms::new(draw_call.model, view, projection); + self.queue.write_buffer( + &self.uniform_buffer, + 0, + bytemuck::cast_slice(&[uniforms]), + ); + } + Pipeline::Terrain => + { + let uniforms = TerrainUniforms::new( + draw_call.model, + view, + projection, + self.terrain_height_scale, + time, + ); + self.queue.write_buffer( + &self.terrain_uniform_buffer, + 0, + bytemuck::cast_slice(&[uniforms]), + ); + } + } + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("3D Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.framebuffer.view, + resolve_target: None, + ops: wgpu::Operations { + load: if i == 0 + { + wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }) + } + else + { + wgpu::LoadOp::Load + }, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.framebuffer.depth_view, + depth_ops: Some(wgpu::Operations { + load: if i == 0 + { + wgpu::LoadOp::Clear(1.0) + } + else + { + wgpu::LoadOp::Load + }, + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + let pipeline = match draw_call.pipeline + { + Pipeline::Render => &self.render_pipeline, + Pipeline::Terrain => &self + .terrain_pipeline + .as_ref() + .expect("terrain_data_missing"), + Pipeline::Wireframe => &self.wireframe_pipeline, + }; + let bind_group = match draw_call.pipeline + { + Pipeline::Render => &self.bind_group, + Pipeline::Terrain => &self + .terrain_bind_group + .as_ref() + .expect("terrain data missing"), + Pipeline::Wireframe => &self.bind_group, + }; + + render_pass.set_pipeline(pipeline); + render_pass.set_bind_group(0, bind_group, &[]); + render_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..)); + render_pass + .set_index_buffer(draw_call.index_buffer.slice(..), wgpu::IndexFormat::Uint32); + render_pass.draw_indexed(0..draw_call.num_indices, 0, 0..1); + } + + self.queue.submit(std::iter::once(encoder.finish())); + } + + let frame = match self.surface.get_current_texture() + { + Ok(frame) => frame, + Err(_) => + { + self.surface.configure(&self.device, &self.config); + self.surface + .get_current_texture() + .expect("Failed to acquire next surface texture") + } + }; + + let screen_view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut blit_encoder = + self.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Blit Encoder"), + }); + + { + let mut blit_pass = blit_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Blit Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &screen_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + blit_pass.set_pipeline(&self.blit_pipeline); + blit_pass.set_bind_group(0, &self.blit_bind_group, &[]); + blit_pass.set_vertex_buffer(0, self.quad_vb.slice(..)); + blit_pass.set_index_buffer(self.quad_ib.slice(..), wgpu::IndexFormat::Uint16); + blit_pass.draw_indexed(0..self.quad_num_indices, 0, 0..1); + } + + self.queue.submit(std::iter::once(blit_encoder.finish())); + frame.present(); + } + + pub fn render_with_matrices( + &mut self, + view: &glam::Mat4, + projection: &glam::Mat4, + draw_calls: &[DrawCall], + time: f32, + ) + { + for (i, draw_call) in draw_calls.iter().enumerate() + { + match draw_call.pipeline + { + Pipeline::Render | Pipeline::Wireframe => + { + let uniforms = CameraUniforms::new(draw_call.model, *view, *projection); + self.queue.write_buffer( + &self.uniform_buffer, + 0, + bytemuck::cast_slice(&[uniforms]), + ); + } + Pipeline::Terrain => + { + let uniforms = TerrainUniforms::new( + draw_call.model, + *view, + *projection, + self.terrain_height_scale, + time, + ); + self.queue.write_buffer( + &self.terrain_uniform_buffer, + 0, + bytemuck::cast_slice(&[uniforms]), + ); + } + } + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("3D Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.framebuffer.view, + resolve_target: None, + ops: wgpu::Operations { + load: if i == 0 + { + wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }) + } + else + { + wgpu::LoadOp::Load + }, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.framebuffer.depth_view, + depth_ops: Some(wgpu::Operations { + load: if i == 0 + { + wgpu::LoadOp::Clear(1.0) + } + else + { + wgpu::LoadOp::Load + }, + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + let pipeline = match draw_call.pipeline + { + Pipeline::Render => &self.render_pipeline, + Pipeline::Terrain => &self + .terrain_pipeline + .as_ref() + .expect("terrain_data_missing"), + Pipeline::Wireframe => &self.wireframe_pipeline, + }; + let bind_group = match draw_call.pipeline + { + Pipeline::Render => &self.bind_group, + Pipeline::Terrain => &self + .terrain_bind_group + .as_ref() + .expect("terrain_data_missing"), + Pipeline::Wireframe => &self.bind_group, + }; + + render_pass.set_pipeline(pipeline); + render_pass.set_bind_group(0, bind_group, &[]); + + render_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..)); + render_pass.set_index_buffer( + draw_call.index_buffer.slice(..), + wgpu::IndexFormat::Uint32, + ); + render_pass.draw_indexed(0..draw_call.num_indices, 0, 0..1); + } + + self.queue.submit(std::iter::once(encoder.finish())); + } + + let frame = match self.surface.get_current_texture() + { + Ok(frame) => frame, + Err(_) => + { + self.surface.configure(&self.device, &self.config); + self.surface + .get_current_texture() + .expect("Failed to acquire next surface texture") + } + }; + + let screen_view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut blit_encoder = + self.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Blit Encoder"), + }); + + { + let mut blit_pass = blit_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Blit Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &screen_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + blit_pass.set_pipeline(&self.blit_pipeline); + blit_pass.set_bind_group(0, &self.blit_bind_group, &[]); + blit_pass.set_vertex_buffer(0, self.quad_vb.slice(..)); + blit_pass.set_index_buffer(self.quad_ib.slice(..), wgpu::IndexFormat::Uint16); + blit_pass.draw_indexed(0..self.quad_num_indices, 0, 0..1); + } + + self.queue.submit(std::iter::once(blit_encoder.finish())); + frame.present(); + } + + pub fn render_scale(&self) -> (u32, u32) + { + ( + self.config.width / self.framebuffer.width, + self.config.height / self.framebuffer.height, + ) + } + + pub fn set_terrain_data(&mut self, terrain_data: TerrainData) + { + let terrain_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Terrain Bind Group"), + layout: &self.terrain_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: self.terrain_uniform_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&terrain_data.height_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&terrain_data.height_sampler), + }, + ], + }); + + let terrain_pipeline = create_terrain_render_pipeline( + &self.device, + &self.config, + &self.terrain_bind_group_layout, + ); + + self.terrain_bind_group = Some(terrain_bind_group); + self.terrain_pipeline = Some(terrain_pipeline); + } + + pub fn get_device(&self) -> &wgpu::Device + { + &self.device + } + + pub fn aspect_ratio(&self) -> f32 + { + self.config.width as f32 / self.config.height as f32 + } +} + +thread_local! { + static GLOBAL_RENDERER: RefCell> = RefCell::new(None); +} + +pub fn init(renderer: Renderer) +{ + GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer)); +} + +pub fn with_device(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: 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(terrain_data: TerrainData) +{ + GLOBAL_RENDERER.with(|r| { + let mut renderer = r.borrow_mut(); + let renderer = renderer.as_mut().expect("Renderer not set"); + renderer.set_terrain_data(terrain_data); + }); +} + +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(camera: &Camera, draw_calls: &[DrawCall], time: f32) +{ + GLOBAL_RENDERER.with(|r| { + let mut renderer = r.borrow_mut(); + let renderer = renderer.as_mut().expect("Renderer not set"); + renderer.render(camera, draw_calls, time); + }); +} + +pub fn render_with_matrices( + view: &glam::Mat4, + projection: &glam::Mat4, + draw_calls: &[DrawCall], + time: f32, +) +{ + GLOBAL_RENDERER.with(|r| { + let mut renderer = r.borrow_mut(); + let renderer = renderer.as_mut().expect("Renderer not set"); + renderer.render_with_matrices(view, projection, draw_calls, time); + }); +} + +fn create_wireframe_pipeline( + device: &wgpu::Device, + format: wgpu::TextureFormat, + bind_group_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline +{ + let shader_source = + std::fs::read_to_string("shaders/standard.wgsl").expect("Failed to read shader"); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Wireframe Shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.into()), + }); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Wireframe Pipeline Layout"), + bind_group_layouts: &[bind_group_layout], + push_constant_ranges: &[], + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Wireframe Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[crate::mesh::Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::LineList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }) +} diff --git a/src/shader.rs b/src/shader.rs new file mode 100644 index 0000000..a89a009 --- /dev/null +++ b/src/shader.rs @@ -0,0 +1,66 @@ +use crate::mesh::Vertex; + +pub fn create_render_pipeline( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + bind_group_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline +{ + let shader_source = + std::fs::read_to_string("shaders/standard.wgsl").expect("Failed to read standard shader"); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.into()), + }); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[bind_group_layout], + push_constant_ranges: &[], + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }) +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..bfb947c --- /dev/null +++ b/src/state.rs @@ -0,0 +1,141 @@ +use std::any::{Any, TypeId}; +use std::collections::HashMap; + +use crate::world::World; + +pub trait StateAgent {} + +pub trait State: Any +{ + fn get_state_name(&self) -> &'static str; + fn on_state_enter(&mut self, world: &mut World) {} + fn on_state_exit(&mut self, world: &mut World) {} + fn on_state_update(&mut self, world: &mut World, delta: f32) {} + fn on_state_physics_update(&mut self, world: &mut World, delta: f32) {} +} + +impl dyn State +{ + fn dyn_type_id(&self) -> std::any::TypeId + { + Any::type_id(self) + } +} + +pub struct StateTransition +{ + to_state_id: TypeId, + condition: Box bool>, +} + +pub struct StateMachine +{ + state_transitions: HashMap>, + current_state_id: TypeId, + states: HashMap>, + pub time_in_state: f32, +} + +impl StateMachine +{ + pub fn new(enter_state: Box) -> Self + { + let state_id = enter_state.dyn_type_id(); + let mut states = HashMap::new(); + states.insert(state_id, enter_state); + + Self { + state_transitions: HashMap::new(), + current_state_id: state_id, + states, + time_in_state: 0.0, + } + } + + pub fn update(&mut self, world: &mut World, delta: f32) + { + if let Some(next_state_id) = self.get_transition_state_id(world) + { + self.time_in_state = 0.0; + self.transition_to(world, next_state_id); + } + + if let Some(current_state) = self.states.get_mut(&self.current_state_id) + { + current_state.on_state_update(world, delta); + } + + self.time_in_state += delta; + } + + fn get_transition_state_id(&self, world: &World) -> Option + { + if let Some(transitions) = self.state_transitions.get(&self.current_state_id) + { + for transition in transitions + { + if (transition.condition)(world) + { + return Some(transition.to_state_id); + } + } + } + None + } + + fn transition_to(&mut self, world: &mut World, new_state_id: TypeId) + { + if let Some(current_state) = self.states.get_mut(&self.current_state_id) + { + current_state.on_state_exit(world); + } + + self.current_state_id = new_state_id; + + if let Some(new_state) = self.states.get_mut(&self.current_state_id) + { + new_state.on_state_enter(world); + } + } + + pub fn get_current_state(&self) -> Option<&dyn State> + { + self.states.get(&self.current_state_id).map(|b| b.as_ref()) + } + + pub fn get_current_state_mut(&mut self) -> Option<&mut dyn State> + { + self.states + .get_mut(&self.current_state_id) + .map(|b| b.as_mut()) + } + + pub fn add_state(&mut self, state: T) + { + let state_id = TypeId::of::(); + self.states.insert(state_id, Box::new(state)); + } + + pub fn add_transition( + &mut self, + condition: impl Fn(&World) -> bool + 'static, + ) + { + let from_id = TypeId::of::(); + let to_id = TypeId::of::(); + + let transitions = self.state_transitions.entry(from_id).or_default(); + transitions.push(StateTransition { + to_state_id: to_id, + condition: Box::new(condition), + }); + } + + pub fn get_available_transitions_count(&self) -> usize + { + self.state_transitions + .get(&self.current_state_id) + .map(|transitions| transitions.len()) + .unwrap_or(0) + } +} diff --git a/src/systems/camera.rs b/src/systems/camera.rs new file mode 100644 index 0000000..19a94ed --- /dev/null +++ b/src/systems/camera.rs @@ -0,0 +1,202 @@ +use glam::Vec3; + +use crate::utility::input::InputState; +use crate::world::World; + +pub fn camera_input_system(world: &mut World, input_state: &InputState) +{ + let cameras: Vec<_> = world.cameras.all(); + + for camera_entity in cameras + { + if let Some(camera) = world.cameras.get_mut(camera_entity) + { + if !camera.is_active + { + continue; + } + + if input_state.mouse_delta.0.abs() > 0.0 || input_state.mouse_delta.1.abs() > 0.0 + { + let is_following = world + .camera_follows + .get(camera_entity) + .map(|f| f.is_following) + .unwrap_or(false); + + camera.yaw += input_state.mouse_delta.0 * 0.0008; + + if is_following + { + camera.pitch += input_state.mouse_delta.1 * 0.0008; + } + else + { + camera.pitch -= input_state.mouse_delta.1 * 0.0008; + } + + camera.pitch = camera + .pitch + .clamp(-89.0_f32.to_radians(), 89.0_f32.to_radians()); + } + } + } +} + +pub fn camera_follow_system(world: &mut World) +{ + let camera_entities: Vec<_> = world.camera_follows.all(); + + for camera_entity in camera_entities + { + if let Some(follow) = world.camera_follows.get(camera_entity) + { + if !follow.is_following + { + continue; + } + + let target_entity = follow.target_entity; + let offset = follow.offset; + + if let Some(target_transform) = world.transforms.get(target_entity) + { + let target_position = target_transform.position; + + if let Some(camera) = world.cameras.get_mut(camera_entity) + { + let distance = offset.length(); + + let orbit_yaw = camera.yaw + std::f32::consts::PI; + + let offset_x = distance * orbit_yaw.cos() * camera.pitch.cos(); + let offset_y = distance * camera.pitch.sin(); + let offset_z = distance * orbit_yaw.sin() * camera.pitch.cos(); + + let new_offset = Vec3::new(offset_x, offset_y, offset_z); + + if let Some(camera_transform) = world.transforms.get_mut(camera_entity) + { + camera_transform.position = target_position + new_offset; + } + + if let Some(follow_mut) = world.camera_follows.get_mut(camera_entity) + { + follow_mut.offset = new_offset; + } + } + } + } + } +} + +pub fn camera_noclip_system(world: &mut World, input_state: &InputState, delta: f32) +{ + let cameras: Vec<_> = world.cameras.all(); + + for camera_entity in cameras + { + if let Some(camera) = world.cameras.get(camera_entity) + { + if !camera.is_active + { + continue; + } + + let forward = camera.get_forward(); + let right = camera.get_right(); + + let mut input_vec = Vec3::ZERO; + + if input_state.w + { + input_vec.z += 1.0; + } + if input_state.s + { + input_vec.z -= 1.0; + } + if input_state.d + { + input_vec.x += 1.0; + } + if input_state.a + { + input_vec.x -= 1.0; + } + if input_state.space + { + input_vec.y += 1.0; + } + + if input_vec.length_squared() > 0.0 + { + input_vec = input_vec.normalize(); + } + + let mut speed = 10.0 * delta; + if input_state.shift + { + speed *= 2.0; + } + + if let Some(camera_transform) = world.transforms.get_mut(camera_entity) + { + camera_transform.position += forward * input_vec.z * speed; + camera_transform.position += right * input_vec.x * speed; + camera_transform.position += Vec3::Y * input_vec.y * speed; + } + } + } +} + +pub fn start_camera_following(world: &mut World, camera_entity: crate::entity::EntityHandle) +{ + if let Some(follow) = world.camera_follows.get_mut(camera_entity) + { + let target_entity = follow.target_entity; + + if let Some(target_transform) = world.transforms.get(target_entity) + { + if let Some(camera_transform) = world.transforms.get(camera_entity) + { + let offset = camera_transform.position - target_transform.position; + follow.offset = offset; + follow.is_following = true; + + let distance = offset.length(); + if distance > 0.0 + { + if let Some(camera) = world.cameras.get_mut(camera_entity) + { + camera.pitch = (offset.y / distance).asin(); + camera.yaw = offset.z.atan2(offset.x) + std::f32::consts::PI; + } + } + } + } + } +} + +pub fn stop_camera_following(world: &mut World, camera_entity: crate::entity::EntityHandle) +{ + if let Some(follow) = world.camera_follows.get_mut(camera_entity) + { + follow.is_following = false; + + if let Some(camera_transform) = world.transforms.get(camera_entity) + { + if let Some(target_transform) = world.transforms.get(follow.target_entity) + { + let look_direction = + (target_transform.position - camera_transform.position).normalize(); + + if let Some(camera) = world.cameras.get_mut(camera_entity) + { + camera.yaw = look_direction.z.atan2(look_direction.x); + camera.pitch = look_direction.y.asin(); + } + } + } + } +} diff --git a/src/systems/input.rs b/src/systems/input.rs new file mode 100644 index 0000000..f21e1eb --- /dev/null +++ b/src/systems/input.rs @@ -0,0 +1,58 @@ +use glam::Vec3; + +use crate::utility::input::InputState; +use crate::world::World; + +pub fn player_input_system(world: &mut World, input_state: &InputState) +{ + let active_camera = world.cameras.get_active(); + + if active_camera.is_none() + { + return; + } + + let (_, camera) = active_camera.unwrap(); + + let forward = camera.get_forward_horizontal(); + let right = camera.get_right_horizontal(); + + let players = world.player_tags.all(); + + for player in players + { + world.inputs.with_mut(player, |input_component| { + let mut local_input = Vec3::ZERO; + + if input_state.w + { + local_input.z += 1.0; + } + if input_state.s + { + local_input.z -= 1.0; + } + if input_state.a + { + local_input.x -= 1.0; + } + if input_state.d + { + local_input.x += 1.0; + } + + let move_direction = if local_input.length_squared() > 0.0 + { + (forward * local_input.z + right * local_input.x).normalize() + } + else + { + Vec3::ZERO + }; + + input_component.move_direction = move_direction; + input_component.jump_pressed = input_state.space; + input_component.jump_just_pressed = input_state.space_just_pressed; + }); + } +} diff --git a/src/systems/mod.rs b/src/systems/mod.rs new file mode 100644 index 0000000..fc54fc6 --- /dev/null +++ b/src/systems/mod.rs @@ -0,0 +1,14 @@ +pub mod camera; +pub mod input; +pub mod physics_sync; +pub mod render; +pub mod state_machine; + +pub use camera::{ + camera_follow_system, camera_input_system, camera_noclip_system, start_camera_following, + stop_camera_following, +}; +pub use input::player_input_system; +pub use physics_sync::physics_sync_system; +pub use render::render_system; +pub use state_machine::{state_machine_physics_system, state_machine_system}; diff --git a/src/systems/physics_sync.rs b/src/systems/physics_sync.rs new file mode 100644 index 0000000..0addf0b --- /dev/null +++ b/src/systems/physics_sync.rs @@ -0,0 +1,24 @@ +use crate::physics::PhysicsManager; +use crate::utility::transform::Transform; +use crate::world::World; + +pub fn physics_sync_system(world: &mut World) +{ + let all_entities = world.entities.all_entities(); + + for entity in all_entities + { + if let Some(physics) = world.physics.get(entity) + { + if let Some(rigidbody_position) = + PhysicsManager::get_rigidbody_position(physics.rigidbody) + { + let transform = Transform::from(rigidbody_position); + + world.transforms.with_mut(entity, |t| { + *t = transform; + }); + } + } + } +} diff --git a/src/systems/render.rs b/src/systems/render.rs new file mode 100644 index 0000000..619963d --- /dev/null +++ b/src/systems/render.rs @@ -0,0 +1,23 @@ +use crate::render::DrawCall; +use crate::world::World; + +pub fn render_system(world: &World) -> Vec +{ + let all_entities = world.entities.all_entities(); + + all_entities + .iter() + .filter_map(|&entity| { + let transform = world.transforms.get(entity)?; + let mesh_component = world.meshes.get(entity)?; + + Some(DrawCall { + vertex_buffer: mesh_component.mesh.vertex_buffer.clone(), + index_buffer: mesh_component.mesh.index_buffer.clone(), + num_indices: mesh_component.mesh.num_indices, + model: transform.to_matrix(), + pipeline: mesh_component.pipeline, + }) + }) + .collect() +} diff --git a/src/systems/state_machine.rs b/src/systems/state_machine.rs new file mode 100644 index 0000000..d52c250 --- /dev/null +++ b/src/systems/state_machine.rs @@ -0,0 +1,38 @@ +use crate::world::World; + +pub fn state_machine_system(world: &mut World, delta: f32) +{ + let entities: Vec<_> = world.state_machines.all(); + + for entity in entities + { + if let Some(mut state_machine) = world.state_machines.components.remove(&entity) + { + state_machine.update(world, delta); + world + .state_machines + .components + .insert(entity, state_machine); + } + } +} + +pub fn state_machine_physics_system(world: &mut World, delta: f32) +{ + let entities: Vec<_> = world.state_machines.all(); + + for entity in entities + { + if let Some(mut state_machine) = world.state_machines.components.remove(&entity) + { + if let Some(current_state) = state_machine.get_current_state_mut() + { + current_state.on_state_physics_update(world, delta); + } + world + .state_machines + .components + .insert(entity, state_machine); + } + } +} diff --git a/src/terrain.rs b/src/terrain.rs new file mode 100644 index 0000000..1388f4b --- /dev/null +++ b/src/terrain.rs @@ -0,0 +1,167 @@ +use std::rc::Rc; + +use exr::prelude::{ReadChannels, ReadLayers}; +use glam::{Vec2, Vec3}; +use nalgebra::{vector, DMatrix}; +use rapier3d::{ + math::Isometry, + prelude::{ColliderBuilder, RigidBodyBuilder}, +}; + +use crate::{ + components::{MeshComponent, PhysicsComponent}, + entity::EntityHandle, + mesh::{Mesh, Vertex}, + physics::PhysicsManager, + render, + world::{Transform, World}, +}; + +pub struct Terrain; + +impl Terrain +{ + pub fn spawn( + world: &mut World, + heightmap_path: &str, + height_scale: f32, + ) -> anyhow::Result + { + let entity = world.spawn(); + + let plane_size = Vec2::new(100.0, 100.0); + + let plane_mesh = render::with_device(|device| { + Mesh::create_plane_mesh(device, plane_size.x, plane_size.y, 100, 100) + }); + + let transform = Transform::IDENTITY; + + world.transforms.insert(entity, transform); + world.meshes.insert( + entity, + MeshComponent { + mesh: Rc::new(plane_mesh), + pipeline: render::Pipeline::Terrain, + }, + ); + + let heights = Self::load_heightfield_data(heightmap_path)?; + + println!( + "Heightmap dimensions: {} rows × {} cols", + heights.nrows(), + heights.ncols() + ); + + let scale = vector![plane_size.x, height_scale, plane_size.y,]; + + let body = RigidBodyBuilder::fixed() + .translation(transform.get_position().into()) + .build(); + + let rigidbody_handle = PhysicsManager::add_rigidbody(body); + + let collider = ColliderBuilder::heightfield(heights.clone(), scale).build(); + + let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle)); + + PhysicsManager::set_heightfield_data(heights, scale, transform.get_position().into()); + + world.physics.insert( + entity, + PhysicsComponent { + rigidbody: rigidbody_handle, + collider: Some(collider_handle), + }, + ); + + Ok(entity) + } + + fn load_heightfield_data(path: &str) -> anyhow::Result> + { + let image = exr::prelude::read() + .no_deep_data() + .largest_resolution_level() + .all_channels() + .all_layers() + .all_attributes() + .from_file(path)?; + + let layer = &image.layer_data[0]; + let channel = &layer.channel_data.list[0]; + + let width = layer.size.width(); + let height = layer.size.height(); + + let heights: Vec = channel.sample_data.values_as_f32().collect(); + + Ok(DMatrix::from_row_slice(height, width, &heights)) + } +} + +pub fn create_terrain_render_pipeline( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + bind_group_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline +{ + let shader_source = + std::fs::read_to_string("shaders/terrain.wgsl").expect("Failed to read terrain shader"); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Terrain Shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.into()), + }); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Terrain Render Pipeline Layout"), + bind_group_layouts: &[bind_group_layout], + push_constant_ranges: &[], + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Terrain Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }) +} diff --git a/src/utility/input.rs b/src/utility/input.rs new file mode 100755 index 0000000..b0ecc0e --- /dev/null +++ b/src/utility/input.rs @@ -0,0 +1,188 @@ +use glam::Vec2; +use sdl3::{event::Event, keyboard::Keycode}; + +pub struct InputState +{ + pub w: bool, + pub a: bool, + pub s: bool, + pub d: bool, + pub space: bool, + pub shift: bool, + + pub space_just_pressed: bool, + pub noclip_just_pressed: bool, + + pub mouse_delta: (f32, f32), + pub mouse_captured: bool, + pub noclip_mode: bool, + pub quit_requested: bool, +} + +impl InputState +{ + pub fn new() -> Self + { + Self { + w: false, + a: false, + s: false, + d: false, + space: false, + shift: false, + space_just_pressed: false, + noclip_just_pressed: false, + mouse_delta: (0.0, 0.0), + mouse_captured: true, + noclip_mode: false, + quit_requested: false, + } + } + + pub fn handle_event(&mut self, event: &Event) -> bool + { + match event + { + Event::Quit { .. } => + { + self.quit_requested = true; + return true; + } + + Event::KeyDown { + keycode: Some(key), + repeat, + .. + } => + { + if !repeat + { + self.handle_keydown(*key); + + if *key == Keycode::Escape + { + self.quit_requested = true; + return true; + } + + if *key == Keycode::I + { + self.mouse_captured = !self.mouse_captured; + return true; + } + } + } + + Event::KeyUp { + keycode: Some(key), .. + } => + { + self.handle_keyup(*key); + } + + Event::MouseMotion { xrel, yrel, .. } => + { + self.handle_mouse_motion(*xrel as f32, *yrel as f32); + } + + _ => + {} + } + + false + } + + pub fn handle_keydown(&mut self, key: Keycode) + { + match key + { + Keycode::W => self.w = true, + Keycode::A => self.a = true, + Keycode::S => self.s = true, + Keycode::D => self.d = true, + Keycode::Space => + { + if !self.space + { + self.space_just_pressed = true; + } + self.space = true; + } + Keycode::LShift | Keycode::RShift => self.shift = true, + Keycode::N => self.noclip_just_pressed = true, + _ => + {} + } + } + + pub fn handle_keyup(&mut self, key: Keycode) + { + match key + { + Keycode::W => self.w = false, + Keycode::A => self.a = false, + Keycode::S => self.s = false, + Keycode::D => self.d = false, + Keycode::Space => self.space = false, + Keycode::LShift | Keycode::RShift => self.shift = false, + _ => + {} + } + } + + pub fn handle_mouse_motion(&mut self, xrel: f32, yrel: f32) + { + if self.mouse_captured + { + self.mouse_delta = (xrel, yrel); + } + } + + pub fn process_post_events(&mut self) + { + if self.noclip_just_pressed + { + self.noclip_mode = !self.noclip_mode; + println!( + "Noclip mode: {}", + if self.noclip_mode { "ON" } else { "OFF" } + ); + } + } + + pub fn clear_just_pressed(&mut self) + { + self.space_just_pressed = false; + self.noclip_just_pressed = false; + self.mouse_delta = (0.0, 0.0); + } + + pub fn get_movement_input(&self) -> Vec2 + { + let mut input = Vec2::ZERO; + + if self.w + { + input.y += 1.0; + } + if self.s + { + input.y -= 1.0; + } + if self.a + { + input.x -= 1.0; + } + if self.d + { + input.x += 1.0; + } + + if input.length_squared() > 1.0 + { + input = input.normalize(); + } + + input + } +} diff --git a/src/utility/mod.rs b/src/utility/mod.rs new file mode 100644 index 0000000..5f8564a --- /dev/null +++ b/src/utility/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod input; +pub(crate) mod time; +pub(crate) mod transform; diff --git a/src/utility/time.rs b/src/utility/time.rs new file mode 100644 index 0000000..57682de --- /dev/null +++ b/src/utility/time.rs @@ -0,0 +1,22 @@ +use std::sync::OnceLock; +use std::time::Instant; + +static GAME_START: OnceLock = OnceLock::new(); + +pub struct Time; + +impl Time +{ + pub fn init() + { + GAME_START.get_or_init(Instant::now); + } + + pub fn get_time_elapsed() -> f32 + { + GAME_START + .get() + .map(|start| start.elapsed().as_secs_f32()) + .unwrap_or(0.0) + } +} diff --git a/src/utility/transform.rs b/src/utility/transform.rs new file mode 100644 index 0000000..fdfa30b --- /dev/null +++ b/src/utility/transform.rs @@ -0,0 +1,143 @@ +use glam::{Mat4, Quat, Vec3}; +use nalgebra::{self as na, Isometry3}; + +#[derive(Copy, Clone, Debug)] +pub struct Transform +{ + pub position: Vec3, + pub rotation: Quat, + pub scale: Vec3, +} + +impl Transform +{ + pub const IDENTITY: Self = Self { + position: Vec3::ZERO, + rotation: Quat::IDENTITY, + scale: Vec3::ONE, + }; + + pub fn from_position(position: Vec3) -> Self + { + Self::IDENTITY.translated(position) + } + + pub fn to_matrix(&self) -> Mat4 + { + Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position) + } + + pub fn get_matrix(&self) -> Mat4 + { + self.to_matrix() + } + + pub fn translated(mut self, new_position: Vec3) -> Self + { + self.set_position(new_position); + self + } + + pub fn get_position(&self) -> Vec3 + { + self.position + } + + pub fn set_position(&mut self, position: Vec3) + { + self.position = position; + } + + pub fn set_rotation(&mut self, rotation: Quat) + { + self.rotation = rotation; + } + + pub fn set_scale(&mut self, scale: Vec3) + { + self.scale = scale; + } + + pub fn set_uniform_scale(&mut self, scale: f32) + { + self.scale = Vec3::splat(scale); + } + + pub fn set_matrix(&mut self, matrix: Mat4) + { + let (scale, rotation, translation) = matrix.to_scale_rotation_translation(); + self.position = translation; + self.rotation = rotation; + self.scale = scale; + } +} + +impl Default for Transform +{ + fn default() -> Self + { + Self::IDENTITY + } +} + +impl From for Mat4 +{ + fn from(t: Transform) -> Self + { + t.to_matrix() + } +} + +impl From<&Transform> for Mat4 +{ + fn from(t: &Transform) -> Self + { + t.to_matrix() + } +} + +impl From for Isometry3 +{ + fn from(t: Transform) -> Self + { + let translation = na::Vector3::new(t.position.x, t.position.y, t.position.z); + let rotation = na::UnitQuaternion::from_quaternion(na::Quaternion::new( + t.rotation.w, + t.rotation.x, + t.rotation.y, + t.rotation.z, + )); + Isometry3::from_parts(translation.into(), rotation) + } +} + +impl From<&Transform> for Isometry3 +{ + fn from(t: &Transform) -> Self + { + (*t).into() + } +} + +impl From> for Transform +{ + fn from(iso: Isometry3) -> Self + { + let pos = iso.translation.vector; + let rot = iso.rotation; + + Self { + position: Vec3::new(pos.x, pos.y, pos.z), + rotation: Quat::from_xyzw(rot.i, rot.j, rot.k, rot.w), + scale: Vec3::ONE, + } + } +} + +impl From<&Isometry3> for Transform +{ + fn from(iso: &Isometry3) -> Self + { + (*iso).into() + } +} diff --git a/src/world.rs b/src/world.rs new file mode 100644 index 0000000..04696b7 --- /dev/null +++ b/src/world.rs @@ -0,0 +1,518 @@ +use std::collections::HashMap; + +use crate::components::jump::JumpComponent; +use crate::components::{ + CameraComponent, CameraFollowComponent, InputComponent, MeshComponent, MovementComponent, + PhysicsComponent, +}; +use crate::entity::{EntityHandle, EntityManager}; +use crate::state::StateMachine; + +pub use crate::utility::transform::Transform; + +pub struct TransformStorage +{ + pub components: HashMap, +} + +impl TransformStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: Transform) + { + self.components.insert(entity, component); + } + + pub fn get(&self, entity: EntityHandle) -> Option<&Transform> + { + self.components.get(&entity) + } + + pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut Transform> + { + self.components.get_mut(&entity) + } + + pub fn with(&self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&Transform) -> R, + { + self.components.get(&entity).map(f) + } + + pub fn with_mut(&mut self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&mut Transform) -> R, + { + self.components.get_mut(&entity).map(f) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct MeshStorage +{ + pub components: HashMap, +} + +impl MeshStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: MeshComponent) + { + self.components.insert(entity, component); + } + + pub fn get(&self, entity: EntityHandle) -> Option<&MeshComponent> + { + self.components.get(&entity) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct PhysicsStorage +{ + pub components: HashMap, +} + +impl PhysicsStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: PhysicsComponent) + { + self.components.insert(entity, component); + } + + pub fn get(&self, entity: EntityHandle) -> Option + { + self.components.get(&entity).copied() + } + + pub fn with(&self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&PhysicsComponent) -> R, + { + self.components.get(&entity).map(f) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct MovementStorage +{ + pub components: HashMap, +} + +impl MovementStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: MovementComponent) + { + self.components.insert(entity, component); + } + + pub fn get(&self, entity: EntityHandle) -> Option<&MovementComponent> + { + self.components.get(&entity) + } + + pub fn with(&self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&MovementComponent) -> R, + { + self.components.get(&entity).map(f) + } + + pub fn with_mut(&mut self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&mut MovementComponent) -> R, + { + self.components.get_mut(&entity).map(f) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct JumpStorage +{ + pub components: HashMap, +} + +impl JumpStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: JumpComponent) + { + self.components.insert(entity, component); + } + + pub fn get(&self, entity: EntityHandle) -> Option<&JumpComponent> + { + self.components.get(&entity) + } + + pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut JumpComponent> + { + self.components.get_mut(&entity) + } + + pub fn with(&self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&JumpComponent) -> R, + { + self.components.get(&entity).map(f) + } + + pub fn with_mut(&mut self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&mut JumpComponent) -> R, + { + self.components.get_mut(&entity).map(f) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct InputStorage +{ + pub components: HashMap, +} + +impl InputStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: InputComponent) + { + self.components.insert(entity, component); + } + + pub fn get(&self, entity: EntityHandle) -> Option<&InputComponent> + { + self.components.get(&entity) + } + + pub fn with(&self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&InputComponent) -> R, + { + self.components.get(&entity).map(f) + } + + pub fn with_mut(&mut self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&mut InputComponent) -> R, + { + self.components.get_mut(&entity).map(f) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct PlayerTagStorage +{ + pub components: HashMap, +} + +impl PlayerTagStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle) + { + self.components.insert(entity, ()); + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct StateMachineStorage +{ + pub components: HashMap, +} + +impl StateMachineStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: StateMachine) + { + self.components.insert(entity, component); + } + + pub fn with_mut(&mut self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&mut StateMachine) -> R, + { + self.components.get_mut(&entity).map(f) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct CameraStorage +{ + pub components: HashMap, +} + +impl CameraStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: CameraComponent) + { + self.components.insert(entity, component); + } + + pub fn get(&self, entity: EntityHandle) -> Option<&CameraComponent> + { + self.components.get(&entity) + } + + pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut CameraComponent> + { + self.components.get_mut(&entity) + } + + pub fn with_mut(&mut self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&mut CameraComponent) -> R, + { + self.components.get_mut(&entity).map(f) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } + + pub fn get_active(&self) -> Option<(EntityHandle, &CameraComponent)> + { + self.components + .iter() + .find(|(_, cam)| cam.is_active) + .map(|(e, c)| (*e, c)) + } +} + +pub struct CameraFollowStorage +{ + pub components: HashMap, +} + +impl CameraFollowStorage +{ + pub fn new() -> Self + { + Self { + components: HashMap::new(), + } + } + + pub fn insert(&mut self, entity: EntityHandle, component: CameraFollowComponent) + { + self.components.insert(entity, component); + } + + pub fn get(&self, entity: EntityHandle) -> Option<&CameraFollowComponent> + { + self.components.get(&entity) + } + + pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut CameraFollowComponent> + { + self.components.get_mut(&entity) + } + + pub fn with_mut(&mut self, entity: EntityHandle, f: F) -> Option + where + F: FnOnce(&mut CameraFollowComponent) -> R, + { + self.components.get_mut(&entity).map(f) + } + + pub fn remove(&mut self, entity: EntityHandle) + { + self.components.remove(&entity); + } + + pub fn all(&self) -> Vec + { + self.components.keys().copied().collect() + } +} + +pub struct World +{ + pub entities: EntityManager, + pub transforms: TransformStorage, + pub meshes: MeshStorage, + pub physics: PhysicsStorage, + pub movements: MovementStorage, + pub jumps: JumpStorage, + pub inputs: InputStorage, + pub player_tags: PlayerTagStorage, + pub state_machines: StateMachineStorage, + pub cameras: CameraStorage, + pub camera_follows: CameraFollowStorage, +} + +impl World +{ + pub fn new() -> Self + { + Self { + entities: EntityManager::new(), + transforms: TransformStorage::new(), + meshes: MeshStorage::new(), + physics: PhysicsStorage::new(), + movements: MovementStorage::new(), + jumps: JumpStorage::new(), + inputs: InputStorage::new(), + player_tags: PlayerTagStorage::new(), + state_machines: StateMachineStorage::new(), + cameras: CameraStorage::new(), + camera_follows: CameraFollowStorage::new(), + } + } + + pub fn spawn(&mut self) -> EntityHandle + { + self.entities.spawn() + } + + pub fn despawn(&mut self, entity: EntityHandle) + { + self.transforms.remove(entity); + self.meshes.remove(entity); + self.physics.remove(entity); + self.movements.remove(entity); + self.jumps.remove(entity); + self.inputs.remove(entity); + self.player_tags.remove(entity); + self.state_machines.remove(entity); + self.cameras.remove(entity); + self.camera_follows.remove(entity); + self.entities.despawn(entity); + } +} diff --git a/textures/height_map_x0_y0.exr b/textures/height_map_x0_y0.exr new file mode 100755 index 0000000000000000000000000000000000000000..801b1d95351dc25c4705b7433187f4284aceb4d2 GIT binary patch literal 20816 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 z z4cA+=!9mM(9J)YkeHeR*p8BjJHAIjB*2lp2lMhAVgjk5T~{ztePPp2eA z>UY^Bhqz{@*4*ORoOEir=21#(-n`3mg^$n4tCgC2-XYF<=V7aTjY-UTpKQe+rvK7) z=P|0{^r%e^<{kX4bVVQg92{O93x{ z%JkXK&L4MEdIi&t2=;b!@X8=DfH{U0%gQHM0;Sl7O*X?zE(rv(G@}qg48IAn$AB1t ze7Q7vwm;>z&T=yoMaYLq1k{MRcnQP60X-uscA_DQ=EbV^)F0<1+FWBTp8Z-r{&uaZ zgu9->C=?92J%e_cYaW$^#qB9a&%c!3)SRud$3k+A35u|01SvUHvyvUieBmlm{{m&m{@4-%5{dXf zJ)C(wlx-Ksrz{Ufo{?RijHxh_EuJK0nTn*bL}~1UL6&3*kzK|zgs}`E>mX8gBD=AS zBF0!648lahn>0N&c<&y)?;qFy=X>3s>)fC7JHKj z&dtJUT=LA6Fsy4Bb1{rB@d=r`yMy(}gNf)s&c~e5A5d)S%A5Du?9Jba83FBr}FfmYPh0^FW@Y(hD?LArXryKPd> z9WGxkLp@Brlj1dR2DveBW3z^F&YE67KEZW)<6 z;dd&rfsu3$pQ*w?kWQm+~7Prn&;srR}Fuz(tt4A9IM>}sA7<^Yh*6^lP*MDlu>9k zQ)%Ym#y?%>FKG$|#4_na?eF`etgzyKrXDX$jy1T3E?vb0p`YvH;?LSGeVIl->n1wCCD_r7 z<+YEYcD9B}%v!i>v$0Y*r4!p&sHB>eu18q}dk5azSPcpe3x9bN*Y4(9yTe|mQH$e*gFzyo~*F>Ld zvkEquqZck7V+qHDrIZW{ac3|fPwA;>6{y(J?9TiT&skk=1WDjZA+XHr@5KK2>C|BL ze*~l7Zrjh?LjF`miY&e)4JjI(;VDYSRStEt+K4Zd3~LHLt!*gWmbY0`P2noe;L0J} zW42zIJisx7=H?h`!4uha!HIu5C5OKB;NyKsGP)3Ibk1viXR+g+>?Qh57x(Ou*yfL3 z8N*zvsW+?5svJlIZ^ckkPVoc;z479FOetJMBfs^Om+g?xHL8kj|!Aq7&CwDf|$LNIeb9IHXYRes5Nij)@wS=l!^bdi#i)!a*A6{VX4T}U^y+LqX z8V{nU#lmkFu_n2QUkn0OXJR|7{48lr-1;%qDFOn+W`yM4q2cCA#Xre2cg>d%n>gl7 zS?$EW{pMIotCguCEv~*7rxg;R4|Cl?@_dSjH4vy~m7?|Y@gZHFpp{sOEjqq-D!fej zBlvHeQ@srUaIgm9?C`j3;-xnC^inm{x-7yjL?TR9Weq`PIT><)$jB_kTPX}0> z)&3i?4wHNYk?ED;6zm17fZ`Ydo}C=t?OdReJ@6Hn06Y6T1Swi8{p6Etoh`u944mI4 z1z=Ukg{E40=TemZD+5bx{orh(quTM7FxEilGWWir88<)w%v&r5mNN>Y1kBEMpX~74 zXlG0^+q8F{Ca0lZA=2ku#Pw#CTS)k@JEV)b5sxJWgRg(8P}Krpbp&=+UK>06_N!wq(!cq}dvBe}D3-v@y>pz)u_eiMZbz7N%qK01>#lHH zW1>BJ_|YbP(C^mR4}s9HT*LOx^>#q)od@w>|7AUb^vNfThGR@|aWqG2LE@>(s6iNv zgBa3O0+o%m&aSR5;1|XsI2_2TC~l+$XP1MS>;S60>5sX1#kE+ZO zlY3|KT8DO1oWEO8tD`)}EF~iS*G0x|WK=!)G1w%{-Yvc}_TY47m=(b(wbC_taWlH$ zzEY{S2NO?gfqOUT2NI|OaIfvZK$k!UoDf0GJhUpgo3}fOPsV_7IJjS+KmY+LdKuog zCmn0%UT+UD9BkD2*Y^x`1l8ctT`sj0X-wfG1#UbNn-XdEC}}Cwt|Yz7d$7D!K|b9$ zk4!V7+{d#e@54{60jdit?0Z;#u=dDyNPbHF=ho+pw|mWRwut@MkoI9jUYt%oSRkvA zs@jyT{mFK8(U?T{>3V{BYSW>NppUjFXCRQA`4@LT*RIL%_ zl(Z%}bPe}A5Px%IJcy?vMwh7Rc8rrJ(xIp&;KW47O*S)il-Ge1ZL|-{J?&PH0dRW$ z{VZn_fBmY(W9qs)S9Gd@N=+~(@`gl0b)!laMukV{2z|pAg0di&t00*eBtNY5A1)z1 zh^T9-NOoMDUR&UHC$ME^)9)K`_Jn@(GhYMz$kjEsyL2m3R7ujL)}f_NxFzGEC)F_hWc<}~b>=$}zhH}xM|<8(eM*3WaRP@P z=?s~2gAR|N31X(nQ6KY8LLC;4(jXtSqqX->Xe%2oirs>{*Es+f2j(`dUl|v8Az_c?k}!#g(vV*y9~ z&(Cd>a`&lHK}_mO$;w`8`dn3}9El>V`h2R?vJkGU@MC8or(%Ndf@g2E*Od-|Cvx;v zu|MWmCs)Ukd0z~hvMei!_!wG)f^GU|7v(a_ja3_=>)(8?@IsG+3t#Bw#&H&OJQ%we z5!4;|sdYh&=XKXHg{~}b z`CZVxV%J(HfZ`ze{%=y_GQ3wBbH+41Qg0fqEW{DL+A^vi#!qC0Cg&o%p+ae0MN36s zAUe(vk8`V3A`wY2!IbIpGNTo%hf literal 0 HcmV?d00001