use gizmo_core::input::Input;
use gizmo_core::world::World;
use gizmo_math::{Quat, Vec3};
use gizmo_physics_core::Transform;
use gizmo_renderer::components::Camera;
use winit::keyboard::KeyCode;
#[derive(Debug, Clone, Copy)]
pub struct FpsLook {
pub yaw: f32,
pub pitch: f32,
pub sensitivity: f32,
pub move_speed: f32,
pub pitch_limit: f32,
pub enabled: bool,
}
impl Default for FpsLook {
fn default() -> Self {
Self {
yaw: 0.0,
pitch: 0.0,
sensitivity: 0.0025,
move_speed: 0.0,
pitch_limit: std::f32::consts::FRAC_PI_2 - 0.05,
enabled: true,
}
}
}
impl FpsLook {
pub fn new() -> Self {
Self::default()
}
pub fn with_move_speed(mut self, speed: f32) -> Self {
self.move_speed = speed;
self
}
pub fn with_sensitivity(mut self, s: f32) -> Self {
self.sensitivity = s;
self
}
pub fn looking(mut self, yaw: f32, pitch: f32) -> Self {
self.yaw = yaw;
self.pitch = pitch;
self
}
pub fn apply_look(&mut self, mouse_dx: f32, mouse_dy: f32) {
self.yaw += mouse_dx * self.sensitivity;
self.pitch -= mouse_dy * self.sensitivity;
self.pitch = self.pitch.clamp(-self.pitch_limit, self.pitch_limit);
}
pub fn forward(&self) -> Vec3 {
Camera::forward_from(self.yaw, self.pitch)
}
pub fn right(&self) -> Vec3 {
Camera::right_from(self.yaw)
}
}
gizmo_core::impl_component!(FpsLook);
pub struct FpsLookSystem;
impl gizmo_core::system::System for FpsLookSystem {
fn access_info(&self) -> gizmo_core::system::AccessInfo {
let mut info = gizmo_core::system::AccessInfo::new();
info.is_exclusive = true; info
}
fn run(&mut self, world: &World, dt: f32) {
let (mdx, mdy) = world
.get_resource::<Input>()
.map(|i| i.mouse_delta())
.unwrap_or((0.0, 0.0));
let key = |c: KeyCode| {
world
.get_resource::<Input>()
.map(|i| i.is_key_pressed(c as u32))
.unwrap_or(false)
};
if let Some(mut q) = unsafe {
world.query_unchecked::<(
gizmo_core::query::Mut<FpsLook>,
gizmo_core::query::Mut<Camera>,
gizmo_core::query::Mut<Transform>,
)>()
} {
for (_id, (mut look, mut cam, mut t)) in q.iter_mut() {
if look.enabled {
look.apply_look(mdx, mdy);
if look.move_speed > 0.0 {
let mut dir = Vec3::ZERO;
let (fwd, right) = (look.forward(), look.right());
if key(KeyCode::KeyW) {
dir += fwd;
}
if key(KeyCode::KeyS) {
dir -= fwd;
}
if key(KeyCode::KeyD) {
dir += right;
}
if key(KeyCode::KeyA) {
dir -= right;
}
if key(KeyCode::Space) {
dir += Vec3::Y;
}
if key(KeyCode::ShiftLeft) {
dir -= Vec3::Y;
}
if dir.length_squared() > 1e-9 {
t.position += dir.normalize() * look.move_speed * dt;
}
}
}
cam.yaw = look.yaw;
cam.pitch = look.pitch;
t.rotation = Quat::from_rotation_y(-look.yaw);
t.update_local_matrix();
}
}
}
}
pub struct FpsLookPlugin;
impl<State: 'static> crate::app::Plugin<State> for FpsLookPlugin {
fn build(&self, app: &mut crate::app::App<State>) {
app.update_schedule.add_di_system(
gizmo_core::system::SystemConfig::new(Box::new(FpsLookSystem)).label("fps_look"),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use gizmo_core::system::System;
#[test]
fn apply_look_updates_and_clamps() {
let mut look = FpsLook::new().with_sensitivity(0.01);
look.apply_look(100.0, 0.0); assert!((look.yaw - 1.0).abs() < 1e-5);
look.apply_look(0.0, 100000.0);
assert!(look.pitch >= -look.pitch_limit - 1e-6);
assert!((look.pitch + look.pitch_limit).abs() < 1e-4, "aşağı-clamp: {}", look.pitch);
look.apply_look(0.0, -1_000_000.0);
assert!((look.pitch - look.pitch_limit).abs() < 1e-4, "yukarı-clamp: {}", look.pitch);
}
#[test]
fn forward_matches_camera_helper() {
let look = FpsLook::new().looking(0.7, 0.3);
let f = look.forward();
let c = Camera::forward_from(0.7, 0.3);
assert!((f - c).length() < 1e-6);
assert!((f.length() - 1.0).abs() < 1e-5);
}
#[test]
fn system_syncs_yaw_pitch_to_camera() {
let mut world = World::new();
let e = world.spawn();
world.add_component(e, Transform::new(Vec3::ZERO));
world.add_component(e, Camera::new(1.0, 0.1, 100.0, 0.0, 0.0, true));
world.add_component(e, FpsLook::new().looking(1.2, 0.4));
let mut sys = FpsLookSystem;
sys.run(&world, 1.0 / 60.0);
let cams = world.borrow::<Camera>();
let cam = cams.get(e.id()).unwrap();
assert!((cam.yaw - 1.2).abs() < 1e-5 && (cam.pitch - 0.4).abs() < 1e-5);
}
}