Add raidillon_input and FPSCameraController
This commit is contained in:
parent
d0440f3da3
commit
97195fbd05
7 changed files with 268 additions and 0 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -1748,11 +1748,20 @@ dependencies = [
|
||||||
"hecs",
|
"hecs",
|
||||||
"raidillon_core",
|
"raidillon_core",
|
||||||
"raidillon_ecs",
|
"raidillon_ecs",
|
||||||
|
"raidillon_input",
|
||||||
"raidillon_render",
|
"raidillon_render",
|
||||||
"raidillon_ui",
|
"raidillon_ui",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "raidillon_input"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"glam",
|
||||||
|
"winit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raidillon_render"
|
name = "raidillon_render"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,5 @@ members = [
|
||||||
"raidillon_render",
|
"raidillon_render",
|
||||||
"raidillon_ui",
|
"raidillon_ui",
|
||||||
"raidillon_game",
|
"raidillon_game",
|
||||||
|
"raidillon_input",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,4 @@ raidillon_ecs = { path = "../raidillon_ecs" }
|
||||||
raidillon_ui = { path = "../raidillon_ui" }
|
raidillon_ui = { path = "../raidillon_ui" }
|
||||||
raidillon_core = { path = "../raidillon_core" }
|
raidillon_core = { path = "../raidillon_core" }
|
||||||
hecs = "0.10.5"
|
hecs = "0.10.5"
|
||||||
|
raidillon_input = { path = "../raidillon_input" }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,17 @@ use raidillon_core::Time;
|
||||||
use raidillon_ecs::Transform;
|
use raidillon_ecs::Transform;
|
||||||
use raidillon_render::{Camera, GliumRenderer, gltf_loader, ECSRenderer};
|
use raidillon_render::{Camera, GliumRenderer, gltf_loader, ECSRenderer};
|
||||||
use raidillon_ui::Gui;
|
use raidillon_ui::Gui;
|
||||||
|
use raidillon_input::{Input, FPSCameraController};
|
||||||
|
use winit::keyboard::KeyCode;
|
||||||
|
use winit::window::CursorGrabMode;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
enum Action {
|
||||||
|
MoveForward,
|
||||||
|
MoveBackward,
|
||||||
|
MoveLeft,
|
||||||
|
MoveRight,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let event_loop = glium::winit::event_loop::EventLoop::builder()
|
let event_loop = glium::winit::event_loop::EventLoop::builder()
|
||||||
|
|
@ -26,6 +37,17 @@ fn main() -> Result<()> {
|
||||||
// Dear ImGui integration
|
// Dear ImGui integration
|
||||||
let mut gui = Gui::new(&display, &window)?;
|
let mut gui = Gui::new(&display, &window)?;
|
||||||
|
|
||||||
|
let mut input = Input::<Action>::new();
|
||||||
|
input.map_key(KeyCode::KeyW, Action::MoveForward);
|
||||||
|
input.map_key(KeyCode::KeyS, Action::MoveBackward);
|
||||||
|
input.map_key(KeyCode::KeyA, Action::MoveLeft);
|
||||||
|
input.map_key(KeyCode::KeyD, Action::MoveRight);
|
||||||
|
|
||||||
|
let mut camera_controller = FPSCameraController::new(Vec3::new(0.0, 0.0, 2.0));
|
||||||
|
|
||||||
|
let mut cursor_grabbed = false;
|
||||||
|
let mut attempted_initial_grab = false;
|
||||||
|
|
||||||
let mut time = Time::new();
|
let mut time = Time::new();
|
||||||
|
|
||||||
let object_ent = {
|
let object_ent = {
|
||||||
|
|
@ -66,6 +88,8 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
gui.handle_event(&window, &event);
|
gui.handle_event(&window, &event);
|
||||||
|
|
||||||
|
input.handle_event(&event);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::WindowEvent { event, .. } => match event {
|
Event::WindowEvent { event, .. } => match event {
|
||||||
WindowEvent::CloseRequested => el.exit(),
|
WindowEvent::CloseRequested => el.exit(),
|
||||||
|
|
@ -110,6 +134,51 @@ fn main() -> Result<()> {
|
||||||
},
|
},
|
||||||
Event::AboutToWait => {
|
Event::AboutToWait => {
|
||||||
time.tick();
|
time.tick();
|
||||||
|
|
||||||
|
if !attempted_initial_grab {
|
||||||
|
attempted_initial_grab = true;
|
||||||
|
if window
|
||||||
|
.set_cursor_grab(CursorGrabMode::Confined)
|
||||||
|
.or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked))
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
window.set_cursor_visible(false);
|
||||||
|
cursor_grabbed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let dt = time.delta_seconds();
|
||||||
|
camera_controller.update(
|
||||||
|
&input,
|
||||||
|
dt,
|
||||||
|
cursor_grabbed,
|
||||||
|
(Action::MoveForward, Action::MoveBackward, Action::MoveLeft, Action::MoveRight),
|
||||||
|
);
|
||||||
|
|
||||||
|
if input.key_pressed(KeyCode::Escape) {
|
||||||
|
if cursor_grabbed {
|
||||||
|
let _ = window.set_cursor_grab(CursorGrabMode::None);
|
||||||
|
window.set_cursor_visible(true);
|
||||||
|
cursor_grabbed = false;
|
||||||
|
} else if window
|
||||||
|
.set_cursor_grab(CursorGrabMode::Confined)
|
||||||
|
.or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked))
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
window.set_cursor_visible(false);
|
||||||
|
cursor_grabbed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mut cam) = ecsr.world.query_one_mut::<&mut Camera>(camera_ent) {
|
||||||
|
cam.eye = camera_controller.position;
|
||||||
|
cam.center = camera_controller.position + camera_controller.front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.end_frame();
|
||||||
|
|
||||||
gui.prepare_frame(&window);
|
gui.prepare_frame(&window);
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
raidillon_input/Cargo.toml
Normal file
8
raidillon_input/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "raidillon_input"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
winit = "0.30"
|
||||||
|
glam = "0.30.4"
|
||||||
73
raidillon_input/src/camera.rs
Normal file
73
raidillon_input/src/camera.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use glam::Vec3;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use super::Input;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FPSCameraController {
|
||||||
|
pub position: Vec3,
|
||||||
|
yaw: f32,
|
||||||
|
pitch: f32,
|
||||||
|
pub speed: f32,
|
||||||
|
pub sensitivity: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FPSCameraController {
|
||||||
|
pub fn new(position: Vec3) -> Self {
|
||||||
|
Self {
|
||||||
|
position,
|
||||||
|
yaw: -90.0,
|
||||||
|
pitch: 0.0,
|
||||||
|
speed: 3.0,
|
||||||
|
sensitivity: 0.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update<A>(&mut self,
|
||||||
|
input: &Input<A>,
|
||||||
|
dt: f32,
|
||||||
|
mouse_enabled: bool,
|
||||||
|
actions: (A, A, A, A))
|
||||||
|
where
|
||||||
|
A: Copy + Eq + Hash,
|
||||||
|
{
|
||||||
|
let (forward, backward, left, right) = actions;
|
||||||
|
|
||||||
|
// Mouse look
|
||||||
|
if mouse_enabled {
|
||||||
|
let (dx, dy) = input.mouse_delta();
|
||||||
|
self.yaw += dx as f32 * self.sensitivity;
|
||||||
|
self.pitch -= dy as f32 * self.sensitivity;
|
||||||
|
self.pitch = self.pitch.clamp(-89.0, 89.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movement
|
||||||
|
let front = self.front();
|
||||||
|
let right_vec = front.cross(Vec3::Y).normalize();
|
||||||
|
let frame_speed = self.speed * dt;
|
||||||
|
|
||||||
|
if input.action_held(forward) {
|
||||||
|
self.position += front * frame_speed;
|
||||||
|
}
|
||||||
|
if input.action_held(backward) {
|
||||||
|
self.position -= front * frame_speed;
|
||||||
|
}
|
||||||
|
if input.action_held(left) {
|
||||||
|
self.position -= right_vec * frame_speed;
|
||||||
|
}
|
||||||
|
if input.action_held(right) {
|
||||||
|
self.position += right_vec * frame_speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn front(&self) -> Vec3 {
|
||||||
|
let yaw_rad = self.yaw.to_radians();
|
||||||
|
let pitch_rad = self.pitch.to_radians();
|
||||||
|
Vec3::new(
|
||||||
|
yaw_rad.cos() * pitch_rad.cos(),
|
||||||
|
pitch_rad.sin(),
|
||||||
|
yaw_rad.sin() * pitch_rad.cos(),
|
||||||
|
)
|
||||||
|
.normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
107
raidillon_input/src/lib.rs
Normal file
107
raidillon_input/src/lib.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use winit::event::{DeviceEvent, ElementState, Event, WindowEvent};
|
||||||
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
|
|
||||||
|
pub mod camera;
|
||||||
|
pub use camera::FPSCameraController;
|
||||||
|
|
||||||
|
pub struct Input<A: Copy + Eq + Hash> {
|
||||||
|
pressed_keys: HashSet<KeyCode>,
|
||||||
|
pressed_once: HashSet<KeyCode>,
|
||||||
|
|
||||||
|
keymap: HashMap<KeyCode, A>,
|
||||||
|
pressed_actions: HashSet<A>,
|
||||||
|
pressed_actions_once: HashSet<A>,
|
||||||
|
|
||||||
|
mouse_delta: (f64, f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Copy + Eq + Hash> Input<A> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pressed_keys: HashSet::new(),
|
||||||
|
pressed_once: HashSet::new(),
|
||||||
|
keymap: HashMap::new(),
|
||||||
|
pressed_actions: HashSet::new(),
|
||||||
|
pressed_actions_once: HashSet::new(),
|
||||||
|
mouse_delta: (0.0, 0.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_key(&mut self, key: KeyCode, action: A) {
|
||||||
|
self.keymap.insert(key, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_keymap(&mut self) {
|
||||||
|
self.keymap.clear();
|
||||||
|
self.pressed_actions.clear();
|
||||||
|
self.pressed_actions_once.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event<T>(&mut self, event: &Event<T>) {
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent { event, .. } => match event {
|
||||||
|
WindowEvent::KeyboardInput { event, .. } => {
|
||||||
|
let key_code = match event.physical_key {
|
||||||
|
PhysicalKey::Code(code) => code,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
match event.state {
|
||||||
|
ElementState::Pressed => {
|
||||||
|
self.pressed_keys.insert(key_code);
|
||||||
|
self.pressed_once.insert(key_code);
|
||||||
|
|
||||||
|
if let Some(&action) = self.keymap.get(&key_code) {
|
||||||
|
self.pressed_actions.insert(action);
|
||||||
|
self.pressed_actions_once.insert(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ElementState::Released => {
|
||||||
|
self.pressed_keys.remove(&key_code);
|
||||||
|
|
||||||
|
if let Some(&action) = self.keymap.get(&key_code) {
|
||||||
|
self.pressed_actions.remove(&action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Event::DeviceEvent { event, .. } => match event {
|
||||||
|
DeviceEvent::MouseMotion { delta } => {
|
||||||
|
self.mouse_delta.0 += delta.0;
|
||||||
|
self.mouse_delta.1 += delta.1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_held(&self, key: KeyCode) -> bool {
|
||||||
|
self.pressed_keys.contains(&key)
|
||||||
|
}
|
||||||
|
pub fn key_pressed(&self, key: KeyCode) -> bool {
|
||||||
|
self.pressed_once.contains(&key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action_held(&self, action: A) -> bool {
|
||||||
|
self.pressed_actions.contains(&action)
|
||||||
|
}
|
||||||
|
pub fn action_pressed(&self, action: A) -> bool {
|
||||||
|
self.pressed_actions_once.contains(&action)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_delta(&self) -> (f64, f64) {
|
||||||
|
self.mouse_delta
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_frame(&mut self) {
|
||||||
|
self.mouse_delta = (0.0, 0.0);
|
||||||
|
self.pressed_once.clear();
|
||||||
|
self.pressed_actions_once.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue