use ecs::component::local::Local; use ecs::sole::Single; use ecs::system::{Into, System}; use ecs::{Component, Query}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::delta_time::DeltaTime; use crate::event::Update as UpdateEvent; use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys}; use crate::transform::Position; use crate::util::builder; use crate::vector::{Vec2, Vec3}; builder! { /// A fly camera. #[builder(name = Builder, derives = (Debug))] #[derive(Debug, Component)] #[non_exhaustive] pub struct Fly { pub current_pitch: f64, pub current_yaw: f64, pub speed: f32, } } impl Fly { #[must_use] pub fn builder() -> Builder { Builder::default() } } impl Default for Fly { fn default() -> Self { Self::builder().build() } } impl Default for Builder { fn default() -> Self { Self { current_yaw: -90.0, current_pitch: 0.0, speed: 3.0, } } } /// 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::default(), self.0)), ); } } #[derive(Debug, Component)] pub struct Options { pub mouse_sensitivity: f32, } fn update( camera_query: Query<(Camera, Position, Fly, ActiveCamera)>, keys: Single, cursor: Single, cursor_flags: Single, delta_time: Single, mut cursor_state: Local, options: Local, ) { for (mut camera, mut camera_pos, mut fly_camera, _) in &camera_query { if cursor.has_moved && cursor_flags.is_first_move.flag { tracing::debug!("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); fly_camera.current_yaw += x_offset; fly_camera.current_pitch += y_offset; fly_camera.current_pitch = fly_camera.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: (fly_camera.current_yaw.to_radians().cos() * fly_camera.current_pitch.to_radians().cos()) as f32, y: fly_camera.current_pitch.to_radians().sin() as f32, z: (fly_camera.current_yaw.to_radians().sin() * fly_camera.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_pos.position += direction * fly_camera.speed * delta_time.as_secs_f32(); } if matches!(keys.get_key_state(Key::S), KeyState::Pressed) { camera_pos.position -= direction * fly_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_pos.position += cam_left * fly_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_pos.position += cam_right * fly_camera.speed * delta_time.as_secs_f32(); } camera.target = camera_pos.position + direction; } } #[derive(Debug, Default, Component)] struct CursorState { last_pos: Vec2, }