use crate::camera::Camera;
use core::f32::consts::FRAC_PI_2;
use nalgebra::{Point3, Vector3};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use micromath::F32Ext;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OrbitCameraController {
pub target: Point3<f32>,
pub distance: f32,
pub yaw: f32,
pub pitch: f32,
pub min_distance: f32,
pub max_distance: f32,
pub min_pitch: f32,
pub max_pitch: f32,
}
impl OrbitCameraController {
pub fn new(target: Point3<f32>, distance: f32) -> Self {
Self {
target,
distance: distance.max(0.1),
yaw: 0.0,
pitch: 0.0,
min_distance: 0.5,
max_distance: 100.0,
min_pitch: -FRAC_PI_2 + 0.01,
max_pitch: FRAC_PI_2 - 0.01,
}
}
pub fn with_distance_limits(mut self, min: f32, max: f32) -> Self {
self.min_distance = min;
self.max_distance = max;
self.distance = self.distance.clamp(min, max);
self
}
pub fn with_pitch_limits(mut self, min: f32, max: f32) -> Self {
self.min_pitch = min;
self.max_pitch = max;
self.pitch = self.pitch.clamp(min, max);
self
}
pub fn orbit(&mut self, delta_yaw: f32, delta_pitch: f32) {
self.yaw += delta_yaw;
self.pitch = (self.pitch + delta_pitch).clamp(self.min_pitch, self.max_pitch);
}
pub fn zoom(&mut self, delta_distance: f32) {
self.distance =
(self.distance + delta_distance).clamp(self.min_distance, self.max_distance);
}
pub fn pan(&mut self, delta_right: f32, delta_up: f32) {
let cos_yaw = self.yaw.cos();
let sin_yaw = self.yaw.sin();
let right = Vector3::new(cos_yaw, 0.0, -sin_yaw);
let up = Vector3::new(0.0, 1.0, 0.0);
let offset = right * delta_right + up * delta_up;
self.target += offset;
}
#[inline]
pub fn eye_position(&self) -> Point3<f32> {
let cos_pitch = self.pitch.cos();
let sin_pitch = self.pitch.sin();
let cos_yaw = self.yaw.cos();
let sin_yaw = self.yaw.sin();
let x = self.target.x + self.distance * cos_pitch * sin_yaw;
let y = self.target.y + self.distance * sin_pitch;
let z = self.target.z + self.distance * cos_pitch * cos_yaw;
Point3::new(x, y, z)
}
pub fn update_camera(&self, camera: &mut Camera) {
camera.set_target(self.target);
camera.set_position(self.eye_position());
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FpsCameraController {
pub position: Point3<f32>,
pub yaw: f32,
pub pitch: f32,
pub move_speed: f32,
pub min_pitch: f32,
pub max_pitch: f32,
}
impl FpsCameraController {
pub fn new(position: Point3<f32>) -> Self {
Self {
position,
yaw: 0.0,
pitch: 0.0,
move_speed: 5.0,
min_pitch: -FRAC_PI_2 + 0.01,
max_pitch: FRAC_PI_2 - 0.01,
}
}
pub fn rotate(&mut self, delta_yaw: f32, delta_pitch: f32) {
self.yaw += delta_yaw;
self.pitch = (self.pitch + delta_pitch).clamp(self.min_pitch, self.max_pitch);
}
#[inline]
pub fn horizontal_forward(&self) -> Vector3<f32> {
Vector3::new(self.yaw.sin(), 0.0, -self.yaw.cos())
}
#[inline]
pub fn horizontal_right(&self) -> Vector3<f32> {
Vector3::new(self.yaw.cos(), 0.0, self.yaw.sin())
}
#[inline]
pub fn look_direction(&self) -> Vector3<f32> {
let cos_pitch = self.pitch.cos();
let sin_pitch = self.pitch.sin();
let cos_yaw = self.yaw.cos();
let sin_yaw = self.yaw.sin();
Vector3::new(cos_pitch * sin_yaw, sin_pitch, -cos_pitch * cos_yaw)
}
pub fn move_relative(&mut self, forward: f32, strafe: f32, vertical: f32, dt: f32) {
let fwd = self.horizontal_forward() * (forward * self.move_speed * dt);
let right = self.horizontal_right() * (strafe * self.move_speed * dt);
let up = Vector3::new(0.0, vertical * self.move_speed * dt, 0.0);
self.position += fwd + right + up;
}
pub fn update_camera(&self, camera: &mut Camera) {
let target = self.position + self.look_direction();
camera.set_target(target);
camera.set_position(self.position);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_orbit_camera_eye_position() {
let mut orbit = OrbitCameraController::new(Point3::new(0.0, 0.0, 0.0), 5.0);
let eye = orbit.eye_position();
assert!((eye.z - 5.0).abs() < 1e-4);
assert!(eye.x.abs() < 1e-4);
assert!(eye.y.abs() < 1e-4);
orbit.orbit(core::f32::consts::FRAC_PI_2, 0.0);
let eye90 = orbit.eye_position();
assert!((eye90.x - 5.0).abs() < 1e-4);
assert!(eye90.z.abs() < 1e-4);
}
#[test]
fn test_orbit_camera_pitch_clamping() {
let mut orbit = OrbitCameraController::new(Point3::new(0.0, 0.0, 0.0), 5.0);
orbit.orbit(0.0, 10.0); assert!(orbit.pitch <= orbit.max_pitch);
orbit.orbit(0.0, -20.0); assert!(orbit.pitch >= orbit.min_pitch);
}
#[test]
fn test_fps_camera_movement() {
let mut fps = FpsCameraController::new(Point3::new(0.0, 0.0, 0.0));
fps.move_relative(1.0, 0.0, 0.0, 1.0);
assert!((fps.position.z - (-5.0)).abs() < 1e-4);
fps.move_relative(0.0, 1.0, 0.0, 1.0);
assert!((fps.position.x - 5.0).abs() < 1e-4);
}
#[test]
fn test_orbit_zoom_pan_limits_and_camera_sync() {
let mut orbit = OrbitCameraController::new(Point3::new(1.0, 2.0, 3.0), 5.0)
.with_distance_limits(1.0, 10.0)
.with_pitch_limits(-1.0, 1.0);
orbit.zoom(100.0);
assert_eq!(orbit.distance, 10.0);
orbit.zoom(-100.0);
assert_eq!(orbit.distance, 1.0);
orbit.orbit(0.0, 2.0);
assert_eq!(orbit.pitch, 1.0);
orbit.pan(1.0, 0.5);
assert!((orbit.target.x - 1.0).abs() > 1e-5);
assert!(orbit.target.y > 2.0);
let mut camera = Camera::new(1.0);
orbit.update_camera(&mut camera);
let eye = orbit.eye_position();
assert!((camera.position.x - eye.x).abs() < 1e-5);
assert_eq!(camera.position.z, eye.z);
}
#[test]
fn test_fps_rotate_vectors_and_camera_sync() {
let mut fps = FpsCameraController::new(Point3::new(0.0, 0.0, 0.0));
let fwd = fps.horizontal_forward();
assert!((fwd.z - (-1.0)).abs() < 1e-5);
let right = fps.horizontal_right();
assert!((right.x - 1.0).abs() < 1e-5);
let look = fps.look_direction();
assert!((look.z - (-1.0)).abs() < 1e-5);
fps.rotate(core::f32::consts::FRAC_PI_2, 0.0);
assert!((fps.horizontal_forward().x - 1.0).abs() < 1e-4);
fps.rotate(0.0, 5.0);
assert_eq!(fps.pitch, fps.max_pitch);
fps.move_relative(0.0, 0.0, 1.0, 1.0);
assert!((fps.position.y - 5.0).abs() < 1e-4);
let mut camera = Camera::new(1.0);
fps.update_camera(&mut camera);
assert_eq!(camera.position.x, fps.position.x);
}
}