use glam::Vec3;
use winit::keyboard::KeyCode;
use crate::camera::Camera;
use crate::input::Input;
#[derive(Clone, Debug)]
pub struct SeatedConfig {
pub position: Vec3,
pub base_yaw: f32,
pub base_pitch: f32,
pub min_yaw_offset: f32,
pub max_yaw_offset: f32,
pub min_pitch_offset: f32,
pub max_pitch_offset: f32,
}
impl SeatedConfig {
pub fn new(position: impl Into<Vec3>) -> Self {
Self {
position: position.into(),
base_yaw: 0.0,
base_pitch: 0.0,
min_yaw_offset: -std::f32::consts::PI,
max_yaw_offset: std::f32::consts::PI,
min_pitch_offset: -std::f32::consts::FRAC_PI_2 + 0.01,
max_pitch_offset: std::f32::consts::FRAC_PI_2 - 0.01,
}
}
pub fn yaw_range(mut self, min: f32, max: f32) -> Self {
self.min_yaw_offset = min;
self.max_yaw_offset = max;
self
}
pub fn pitch_range(mut self, min: f32, max: f32) -> Self {
self.min_pitch_offset = min;
self.max_pitch_offset = max;
self
}
pub fn facing(mut self, direction: impl Into<Vec3>) -> Self {
let dir = direction.into().normalize_or_zero();
self.base_yaw = dir.x.atan2(-dir.z);
self.base_pitch = dir.y.asin();
self
}
pub fn facing_angles(mut self, yaw: f32, pitch: f32) -> Self {
self.base_yaw = yaw;
self.base_pitch = pitch;
self
}
}
#[derive(Clone, Debug)]
pub enum FreelookMode {
Unseated,
Seated(SeatedConfig),
}
impl Default for FreelookMode {
fn default() -> Self {
Self::Unseated
}
}
impl FreelookMode {
pub fn seated(position: impl Into<Vec3>) -> SeatedConfig {
SeatedConfig::new(position)
}
}
#[derive(Clone, Debug)]
pub struct FreelookCamera {
pub position: Vec3,
pub yaw: f32,
pub pitch: f32,
pub fov: f32,
pub mode: FreelookMode,
pub sensitivity: f32,
pub speed: f32,
pub near: f32,
pub far: f32,
}
impl Default for FreelookCamera {
fn default() -> Self {
Self {
position: Vec3::ZERO,
yaw: 0.0,
pitch: 0.0,
fov: std::f32::consts::FRAC_PI_2,
mode: FreelookMode::Unseated,
sensitivity: 0.003,
speed: 5.0,
near: 0.1,
far: 1000.0,
}
}
}
impl FreelookCamera {
pub fn new() -> Self {
Self::default()
}
pub fn position(mut self, position: impl Into<Vec3>) -> Self {
self.position = position.into();
self
}
pub fn mode(mut self, mode: FreelookMode) -> Self {
self.mode = mode;
self
}
pub fn fov(mut self, fov_degrees: f32) -> Self {
self.fov = fov_degrees.to_radians();
self
}
pub fn yaw(mut self, yaw: f32) -> Self {
self.yaw = yaw;
self
}
pub fn pitch(mut self, pitch: f32) -> Self {
self.pitch = pitch.clamp(
-std::f32::consts::FRAC_PI_2 + 0.01,
std::f32::consts::FRAC_PI_2 - 0.01,
);
self
}
pub fn looking_toward(mut self, direction: impl Into<Vec3>) -> Self {
let dir = direction.into().normalize_or_zero();
self.yaw = dir.x.atan2(-dir.z);
self.pitch = dir.y.asin().clamp(
-std::f32::consts::FRAC_PI_2 + 0.01,
std::f32::consts::FRAC_PI_2 - 0.01,
);
self
}
pub fn sensitivity(mut self, sensitivity: f32) -> Self {
self.sensitivity = sensitivity;
self
}
pub fn speed(mut self, speed: f32) -> Self {
self.speed = speed;
self
}
pub fn clip_planes(mut self, near: f32, far: f32) -> Self {
self.near = near;
self.far = far;
self
}
pub fn seat(&mut self, config: SeatedConfig) {
self.yaw = config.base_yaw;
self.pitch = config.base_pitch;
self.mode = FreelookMode::Seated(config);
}
pub fn unseat(&mut self) {
if let FreelookMode::Seated(config) = &self.mode {
self.position = config.position;
}
self.mode = FreelookMode::Unseated;
}
pub fn is_seated(&self) -> bool {
matches!(self.mode, FreelookMode::Seated(_))
}
pub fn is_unseated(&self) -> bool {
matches!(self.mode, FreelookMode::Unseated)
}
pub fn effective_position(&self) -> Vec3 {
match &self.mode {
FreelookMode::Unseated => self.position,
FreelookMode::Seated(config) => config.position,
}
}
fn forward_direction(&self) -> Vec3 {
Vec3::new(
self.yaw.sin() * self.pitch.cos(),
self.pitch.sin(),
-self.yaw.cos() * self.pitch.cos(),
)
.normalize_or_zero()
}
fn right_direction(&self) -> Vec3 {
Vec3::new(self.yaw.cos(), 0.0, self.yaw.sin()).normalize_or_zero()
}
pub fn update(&mut self, input: &Input, dt: f32) {
let delta = input.mouse_delta();
self.yaw += delta.x * self.sensitivity;
self.pitch -= delta.y * self.sensitivity;
match &self.mode {
FreelookMode::Unseated => {
self.pitch = self.pitch.clamp(
-std::f32::consts::FRAC_PI_2 + 0.01,
std::f32::consts::FRAC_PI_2 - 0.01,
);
let forward = self.forward_direction();
let right = self.right_direction();
let mut velocity = Vec3::ZERO;
if input.key_down(KeyCode::KeyW) {
velocity += forward;
}
if input.key_down(KeyCode::KeyS) {
velocity -= forward;
}
if input.key_down(KeyCode::KeyA) {
velocity -= right;
}
if input.key_down(KeyCode::KeyD) {
velocity += right;
}
if input.key_down(KeyCode::Space) {
velocity += Vec3::Y;
}
if input.key_down(KeyCode::ShiftLeft) {
velocity -= Vec3::Y;
}
if velocity.length_squared() > 0.0 {
self.position += velocity.normalize() * self.speed * dt;
}
}
FreelookMode::Seated(config) => {
let yaw_offset = self.yaw - config.base_yaw;
let clamped_yaw_offset =
yaw_offset.clamp(config.min_yaw_offset, config.max_yaw_offset);
self.yaw = config.base_yaw + clamped_yaw_offset;
let pitch_offset = self.pitch - config.base_pitch;
let clamped_pitch_offset =
pitch_offset.clamp(config.min_pitch_offset, config.max_pitch_offset);
self.pitch = config.base_pitch + clamped_pitch_offset;
}
}
}
pub fn camera(&self) -> Camera {
let position = self.effective_position();
let forward = self.forward_direction();
Camera {
position,
forward,
up: Vec3::Y,
fov: self.fov,
near: self.near,
far: self.far,
}
}
}