use glam::{Mat4, Vec3}; pub struct Camera { pub position: Vec3, pub yaw: f32, pub pitch: f32, pub aspect: f32, pub fovy: f32, pub near: f32, pub far: f32, } impl Camera { pub fn new(aspect: f32) -> Self { Self { position: Vec3::new(0.0, 32.0, 0.0), yaw: -std::f32::consts::FRAC_PI_2, pitch: -0.3, aspect, fovy: 70f32.to_radians(), near: 0.05, far: 800.0, } } pub fn forward(&self) -> Vec3 { let cp = self.pitch.cos(); Vec3::new(self.yaw.cos() * cp, self.pitch.sin(), self.yaw.sin() * cp).normalize() } pub fn forward_flat(&self) -> Vec3 { Vec3::new(self.yaw.cos(), 0.0, self.yaw.sin()).normalize_or_zero() } pub fn right_flat(&self) -> Vec3 { let f = self.forward_flat(); Vec3::new(-f.z, 0.0, f.x) } pub fn view_proj(&self) -> Mat4 { let view = Mat4::look_to_rh(self.position, self.forward(), Vec3::Y); let proj = Mat4::perspective_rh(self.fovy, self.aspect, self.near, self.far); proj * view } } /// Sticky keyboard hold state. The KbInput handler writes `true` on KeyDown /// and `false` on KeyUp, so the field always reflects whether the key is /// *currently* held. Merged with `TouchBridge` each tick into a local — never /// folded back into a persistent field, which was the source of the /// "joystick release leaves the player walking" bug. #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct KbHeld { pub forward: bool, pub back: bool, pub left: bool, pub right: bool, pub up: bool, pub down: bool, pub sprint: bool, } /// Per-tick input that *isn't* sticky directional state: pending mouse /// motion to consume, click one-shots, and the currently-selected block. #[derive(Default)] pub struct InputState { pub mouse_dx: f32, pub mouse_dy: f32, pub primary_clicked: bool, pub secondary_clicked: bool, pub selected_block: u8, } impl InputState { pub fn consume_mouse(&mut self) -> (f32, f32) { let r = (self.mouse_dx, self.mouse_dy); self.mouse_dx = 0.0; self.mouse_dy = 0.0; r } }