use ecs::component::local::Local; use ecs::sole::Single; use ecs::system::{Into, System}; use ecs::{Component, Query}; use glfw::window::{Key, KeyState}; use crate::camera::Camera; use crate::delta_time::DeltaTime; use crate::event::Update as UpdateEvent; use crate::input::{Cursor, CursorFlags, Keys}; use crate::vector::{Vec2, Vec3}; /// Fly camera extension. pub struct Extension(pub Options); impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { collector.add_system( UpdateEvent, update.into_system().initialize(( CursorState { current_yaw: -90.0, ..Default::default() }, self.0, )), ); } } #[derive(Debug, Component)] pub struct Options { pub camera_speed: f32, pub mouse_sensitivity: f32, } fn update( camera_query: Query<(Camera,)>, keys: Single, cursor: Single, cursor_flags: Single, delta_time: Single, mut cursor_state: Local, options: Local, ) { let Some((mut camera,)) = camera_query.iter().next() else { #[cfg(feature = "debug")] tracing::warn!("No camera"); return; }; if cursor.has_moved && cursor_flags.is_first_move.flag { println!("First cursor move"); cursor_state.last_pos = cursor.position; } let delta_time = delta_time.duration; let mut x_offset = cursor.position.x - cursor_state.last_pos.x; let mut y_offset = cursor_state.last_pos.y - cursor.position.y; cursor_state.last_pos = cursor.position; x_offset *= f64::from(options.mouse_sensitivity); y_offset *= f64::from(options.mouse_sensitivity); cursor_state.current_yaw += x_offset; cursor_state.current_pitch += y_offset; cursor_state.current_pitch = cursor_state.current_pitch.clamp(-89.0, 89.0); // TODO: This casting to a f32 from a f64 is horrible. fix it #[allow(clippy::cast_possible_truncation)] let direction = Vec3 { x: (cursor_state.current_yaw.to_radians().cos() * cursor_state.current_pitch.to_radians().cos()) as f32, y: cursor_state.current_pitch.to_radians().sin() as f32, z: (cursor_state.current_yaw.to_radians().sin() * cursor_state.current_pitch.to_radians().cos()) as f32, } .normalize(); let cam_right = direction.cross(&Vec3::UP).normalize(); camera.global_up = cam_right.cross(&direction).normalize(); if matches!(keys.get_key_state(Key::W), KeyState::Pressed) { camera.position += direction * options.camera_speed * delta_time.as_secs_f32(); } if matches!(keys.get_key_state(Key::S), KeyState::Pressed) { camera.position -= direction * options.camera_speed * delta_time.as_secs_f32(); } if matches!(keys.get_key_state(Key::A), KeyState::Pressed) { let cam_left = -direction.cross(&Vec3::UP).normalize(); camera.position += cam_left * options.camera_speed * delta_time.as_secs_f32(); } if matches!(keys.get_key_state(Key::D), KeyState::Pressed) { let cam_right = direction.cross(&Vec3::UP).normalize(); camera.position += cam_right * options.camera_speed * delta_time.as_secs_f32(); } camera.target = camera.position + direction; } #[derive(Debug, Component)] struct CursorState { last_pos: Vec2, current_pitch: f64, current_yaw: f64, } impl Default for CursorState { fn default() -> Self { Self { last_pos: Vec2::default(), current_pitch: 0.0, current_yaw: 0.0, } } }