Add raidillon_input and FPSCameraController

This commit is contained in:
reo 2025-07-18 12:09:24 +03:00
parent d0440f3da3
commit 97195fbd05
7 changed files with 268 additions and 0 deletions

9
Cargo.lock generated
View file

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

View file

@ -5,4 +5,5 @@ members = [
"raidillon_render", "raidillon_render",
"raidillon_ui", "raidillon_ui",
"raidillon_game", "raidillon_game",
"raidillon_input",
] ]

View file

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

View file

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

View file

@ -0,0 +1,8 @@
[package]
name = "raidillon_input"
version = "0.1.0"
edition = "2021"
[dependencies]
winit = "0.30"
glam = "0.30.4"

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