rendering, physics, player and camera WIP

This commit is contained in:
Jonas H
2026-01-01 19:54:00 +01:00
commit 5d2eca0393
51 changed files with 8734 additions and 0 deletions

168
src/camera.rs Normal file
View File

@@ -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;
}
}

61
src/components/camera.rs Normal file
View File

@@ -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()
}
}

View File

@@ -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,
}
}
}

9
src/components/input.rs Normal file
View File

@@ -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,
}

82
src/components/jump.rs Normal file
View File

@@ -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;
}
}

11
src/components/mesh.rs Normal file
View File

@@ -0,0 +1,11 @@
use std::rc::Rc;
use crate::mesh::Mesh;
use crate::render::Pipeline;
#[derive(Clone)]
pub struct MeshComponent
{
pub mesh: Rc<Mesh>,
pub pipeline: Pipeline,
}

16
src/components/mod.rs Normal file
View File

@@ -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;

View File

@@ -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,
}
}
}

View File

@@ -0,0 +1,8 @@
use rapier3d::prelude::{ColliderHandle, RigidBodyHandle};
#[derive(Copy, Clone, Debug)]
pub struct PhysicsComponent
{
pub rigidbody: RigidBodyHandle,
pub collider: Option<ColliderHandle>,
}

View File

@@ -0,0 +1,2 @@
#[derive(Copy, Clone, Debug)]
pub struct PlayerTag;

View File

@@ -0,0 +1 @@

149
src/debug/collider_debug.rs Normal file
View File

@@ -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<Rc<Mesh>> = OnceCell::new();
static DEBUG_HEIGHTFIELD: OnceCell<Option<Rc<Mesh>>> = 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<DrawCall>
{
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
}

5
src/debug/mod.rs Normal file
View File

@@ -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};

59
src/debug/noclip.rs Normal file
View File

@@ -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);
}
}
}

71
src/draw.rs Normal file
View File

@@ -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<Mesh>,
entity: EntityHandle,
pipeline: Pipeline,
}
pub struct DrawManager
{
entries: HashMap<DrawHandle, DrawEntry>,
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<Mesh>,
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<DrawCall>
{
vec![]
}
pub fn draw_mesh(_mesh: Rc<Mesh>, _entity: EntityHandle, _pipeline: Pipeline) -> DrawHandle
{
0
}
pub fn clear_mesh(_handle: DrawHandle) {}
}

43
src/entity.rs Normal file
View File

@@ -0,0 +1,43 @@
use std::collections::HashSet;
pub type EntityHandle = u64;
pub struct EntityManager
{
next_id: EntityHandle,
alive: HashSet<EntityHandle>,
}
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<EntityHandle>
{
self.alive.iter().copied().collect()
}
}

70
src/event.rs Normal file
View File

@@ -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<T> = Box<dyn FnMut(&T)>;
pub struct EventBus
{
handlers: HashMap<TypeId, Box<dyn Any>>,
}
impl EventBus
{
fn new() -> Self
{
Self {
handlers: HashMap::new(),
}
}
fn subscribe_internal<T: Event + 'static, F: FnMut(&T) + 'static>(&mut self, handler: F)
{
let type_id = TypeId::of::<T>();
let handlers: &mut Vec<EventHandler<T>> = self
.handlers
.entry(type_id)
.or_insert_with(|| Box::new(Vec::<EventHandler<T>>::new()))
.downcast_mut()
.unwrap();
handlers.push(Box::new(handler));
}
fn publish_internal<T: Event + 'static>(&mut self, event: &T)
{
let type_id = TypeId::of::<T>();
if let Some(handlers) = self.handlers.get_mut(&type_id)
{
let typed_handlers = handlers.downcast_mut::<Vec<EventHandler<T>>>().unwrap();
for handler in typed_handlers
{
handler(event);
}
}
}
pub fn subscribe<T: Event + 'static, F: FnMut(&T) + 'static>(handler: F)
{
EVENT_BUS.with(|bus| bus.borrow_mut().subscribe_internal(handler));
}
pub fn publish<T: Event + 'static>(event: &T)
{
EVENT_BUS.with(|bus| bus.borrow_mut().publish_internal(event));
}
}
thread_local! {
static EVENT_BUS: RefCell<EventBus> = RefCell::new(EventBus::new());
}
#[derive(Debug, Clone)]
pub struct UpdateEvent
{
pub delta: f32,
}
impl Event for UpdateEvent {}

67
src/heightmap.rs Normal file
View File

@@ -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<Path>,
) -> Result<(wgpu::Texture, wgpu::TextureView, wgpu::Sampler), Box<dyn std::error::Error>>
{
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<f32> = 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))
}

233
src/main.rs Executable file
View File

@@ -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<dyn std::error::Error>>
{
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)
}

414
src/mesh.rs Normal file
View File

@@ -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::<Vertex>() 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<Path>,
) -> Result<Mesh, Box<dyn std::error::Error>>
{
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<Vertex>,
all_indices: &mut Vec<u32>,
) -> Result<(), Box<dyn std::error::Error>>
{
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::<Vec<[f32; 3]>>();
let normals = reader
.read_normals()
.ok_or("Missing normal data")?
.collect::<Vec<[f32; 3]>>();
let uvs = reader
.read_tex_coords(0)
.map(|iter| iter.into_f32().collect::<Vec<[f32; 2]>>())
.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<Path>) -> Result<Mesh, Box<dyn std::error::Error>>
{
crate::render::with_device(|device| Mesh::load_gltf_mesh(device, path))
}
}

288
src/physics.rs Normal file
View File

@@ -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<PhysicsManager> = RefCell::new(PhysicsManager::new());
}
pub struct HeightfieldData
{
pub heights: DMatrix<f32>,
pub scale: Vector<f32>,
pub position: Vector<f32>,
}
pub struct PhysicsManager
{
rigidbody_set: RigidBodySet,
collider_set: ColliderSet,
gravity: Vector<f32>,
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<HeightfieldData>,
}
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<RigidBodyHandle>,
) -> 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<RigidBodyHandle>) -> ColliderHandle
{
GLOBAL_PHYSICS.with(|manager| manager.borrow_mut().add_collider_internal(collider, parent))
}
pub fn with_rigidbody_mut<F, R>(handle: RigidBodyHandle, f: F) -> Option<R>
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<F, R>(handle: ColliderHandle, f: F) -> Option<R>
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<rapier3d::na::Isometry3<f32>>
{
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<f32>,
direction: Vector<f32>,
max_distance: f32,
exclude_rigidbody: Option<RigidBodyHandle>,
) -> 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<f32>, scale: Vector<f32>, position: Vector<f32>)
{
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<f32>
{
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
}
}

87
src/picking.rs Normal file
View File

@@ -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<f32>
{
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<f32>
{
None
}
}

604
src/player.rs Normal file
View File

@@ -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::<PlayerFallingState, PlayerIdleState>(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::<PlayerFallingState, PlayerWalkingState>(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::<PlayerIdleState, PlayerWalkingState>(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::<PlayerWalkingState, PlayerIdleState>(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::<PlayerIdleState, PlayerFallingState>(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::<PlayerWalkingState, PlayerFallingState>(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::<PlayerIdleState, PlayerJumpingState>(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::<PlayerWalkingState, PlayerJumpingState>(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::<PlayerJumpingState, PlayerFallingState>(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;
});
}
}

200
src/postprocess.rs Normal file
View File

@@ -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::<ScreenVertex>() 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,
})
}

800
src/render.rs Normal file
View File

@@ -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<wgpu::RenderPipeline>,
terrain_bind_group_layout: wgpu::BindGroupLayout,
terrain_uniform_buffer: wgpu::Buffer,
terrain_bind_group: Option<wgpu::BindGroup>,
terrain_height_scale: f32,
wireframe_pipeline: wgpu::RenderPipeline,
}
impl Renderer
{
pub async fn new(
window: &sdl3::video::Window,
render_scale: u32,
) -> Result<Self, Box<dyn std::error::Error>>
{
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::<CameraUniforms>() 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::<TerrainUniforms>() 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<Option<Renderer>> = RefCell::new(None);
}
pub fn init(renderer: Renderer)
{
GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer));
}
pub fn with_device<F, R>(f: F) -> R
where
F: FnOnce(&wgpu::Device) -> R,
{
GLOBAL_RENDERER.with(|r| {
let renderer = r.borrow();
let renderer = renderer.as_ref().expect("Renderer not set");
f(&renderer.device)
})
}
pub fn with_queue<F, R>(f: F) -> R
where
F: FnOnce(&wgpu::Queue) -> R,
{
GLOBAL_RENDERER.with(|r| {
let renderer = r.borrow();
let renderer = renderer.as_ref().expect("Renderer not set");
f(&renderer.queue)
})
}
pub fn set_terrain_data(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,
})
}

66
src/shader.rs Normal file
View File

@@ -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,
})
}

141
src/state.rs Normal file
View File

@@ -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<dyn Fn(&World) -> bool>,
}
pub struct StateMachine
{
state_transitions: HashMap<TypeId, Vec<StateTransition>>,
current_state_id: TypeId,
states: HashMap<TypeId, Box<dyn State>>,
pub time_in_state: f32,
}
impl StateMachine
{
pub fn new(enter_state: Box<dyn State>) -> 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<TypeId>
{
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<T: State + 'static>(&mut self, state: T)
{
let state_id = TypeId::of::<T>();
self.states.insert(state_id, Box::new(state));
}
pub fn add_transition<TFrom: State + 'static, TTo: State + 'static>(
&mut self,
condition: impl Fn(&World) -> bool + 'static,
)
{
let from_id = TypeId::of::<TFrom>();
let to_id = TypeId::of::<TTo>();
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)
}
}

202
src/systems/camera.rs Normal file
View File

@@ -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();
}
}
}
}
}

58
src/systems/input.rs Normal file
View File

@@ -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;
});
}
}

14
src/systems/mod.rs Normal file
View File

@@ -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};

View File

@@ -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;
});
}
}
}
}

23
src/systems/render.rs Normal file
View File

@@ -0,0 +1,23 @@
use crate::render::DrawCall;
use crate::world::World;
pub fn render_system(world: &World) -> Vec<DrawCall>
{
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()
}

View File

@@ -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);
}
}
}

167
src/terrain.rs Normal file
View File

@@ -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<EntityHandle>
{
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<DMatrix<f32>>
{
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<f32> = 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,
})
}

188
src/utility/input.rs Executable file
View File

@@ -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
}
}

3
src/utility/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub(crate) mod input;
pub(crate) mod time;
pub(crate) mod transform;

22
src/utility/time.rs Normal file
View File

@@ -0,0 +1,22 @@
use std::sync::OnceLock;
use std::time::Instant;
static GAME_START: OnceLock<Instant> = 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)
}
}

143
src/utility/transform.rs Normal file
View File

@@ -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<Transform> 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<Transform> for Isometry3<f32>
{
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<f32>
{
fn from(t: &Transform) -> Self
{
(*t).into()
}
}
impl From<Isometry3<f32>> for Transform
{
fn from(iso: Isometry3<f32>) -> 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<f32>> for Transform
{
fn from(iso: &Isometry3<f32>) -> Self
{
(*iso).into()
}
}

518
src/world.rs Normal file
View File

@@ -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<EntityHandle, Transform>,
}
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<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&Transform) -> R,
{
self.components.get(&entity).map(f)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut Transform) -> R,
{
self.components.get_mut(&entity).map(f)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct MeshStorage
{
pub components: HashMap<EntityHandle, MeshComponent>,
}
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<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct PhysicsStorage
{
pub components: HashMap<EntityHandle, PhysicsComponent>,
}
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<PhysicsComponent>
{
self.components.get(&entity).copied()
}
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
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<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct MovementStorage
{
pub components: HashMap<EntityHandle, MovementComponent>,
}
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<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&MovementComponent) -> R,
{
self.components.get(&entity).map(f)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
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<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct JumpStorage
{
pub components: HashMap<EntityHandle, JumpComponent>,
}
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<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&JumpComponent) -> R,
{
self.components.get(&entity).map(f)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
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<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct InputStorage
{
pub components: HashMap<EntityHandle, InputComponent>,
}
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<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&InputComponent) -> R,
{
self.components.get(&entity).map(f)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
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<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct PlayerTagStorage
{
pub components: HashMap<EntityHandle, ()>,
}
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<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct StateMachineStorage
{
pub components: HashMap<EntityHandle, StateMachine>,
}
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<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
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<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct CameraStorage
{
pub components: HashMap<EntityHandle, CameraComponent>,
}
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<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
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<EntityHandle>
{
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<EntityHandle, CameraFollowComponent>,
}
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<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
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<EntityHandle>
{
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);
}
}