use crate::ecs::{Component, EntityId, World};
use crate::math::{Matrix4, Point3, Transform, Vector3};
use cgmath::{Deg, EuclideanSpace, perspective};
use winit::event::{ElementState, MouseButton};
#[derive(Debug, Clone)]
pub struct Camera {
pub is_active: bool,
pub fov: f32,
pub near: f32,
pub far: f32,
}
impl Component for Camera {}
impl Default for Camera {
fn default() -> Self {
Self {
is_active: true,
fov: 45.0,
near: 0.1,
far: 100.0,
}
}
}
impl Camera {
pub fn new(fov: f32, near: f32, far: f32) -> Self {
Self {
is_active: true,
fov,
near,
far,
}
}
pub fn projection_matrix(&self, aspect_ratio: f32) -> Matrix4<f32> {
perspective(Deg(self.fov), aspect_ratio, self.near, self.far)
}
}
pub struct CameraController {
radius: f32,
theta: f32,
phi: f32,
center: Point3<f32>,
is_dragging: bool,
last_mouse_pos: (f32, f32),
cursor_pos: (f32, f32),
camera_entity: Option<EntityId>,
pub sensitivity: f32,
pub zoom_sensitivity: f32,
pub zoom_range: (f32, f32),
}
impl CameraController {
pub fn new() -> Self {
Self {
radius: 10.0,
theta: 0.0,
phi: std::f32::consts::PI * 0.3, center: Point3::new(0.0, 0.0, 0.0),
is_dragging: false,
last_mouse_pos: (0.0, 0.0),
cursor_pos: (0.0, 0.0),
camera_entity: None,
sensitivity: 0.01,
zoom_sensitivity: 0.1,
zoom_range: (2.0, 50.0),
}
}
pub fn set_camera_entity(&mut self, entity: EntityId) {
self.camera_entity = Some(entity);
}
pub fn set_center(&mut self, center: Point3<f32>) {
self.center = center;
}
pub fn set_radius(&mut self, radius: f32) {
self.radius = radius.clamp(self.zoom_range.0, self.zoom_range.1);
}
pub fn position(&self) -> Point3<f32> {
let x = self.center.x + self.radius * self.phi.sin() * self.theta.cos();
let y = self.center.y + self.radius * self.phi.cos();
let z = self.center.z + self.radius * self.phi.sin() * self.theta.sin();
Point3::new(x, y, z)
}
pub fn view_matrix(&self) -> Matrix4<f32> {
let position = self.position();
let target = self.center;
let up = Vector3::new(0.0, 1.0, 0.0);
Matrix4::look_at_rh(position, target, up)
}
pub fn mouse_button(&mut self, button: MouseButton, state: ElementState) {
if button == MouseButton::Left {
match state {
ElementState::Pressed => {
self.is_dragging = true;
self.last_mouse_pos = self.cursor_pos;
}
ElementState::Released => {
self.is_dragging = false;
}
}
}
}
pub fn mouse_motion(&mut self, x: f32, y: f32) -> bool {
self.cursor_pos = (x, y);
if !self.is_dragging {
return false;
}
let dx = x - self.last_mouse_pos.0;
let dy = y - self.last_mouse_pos.1;
self.theta += dx * self.sensitivity; self.phi -= dy * self.sensitivity;
self.phi = self.phi.clamp(0.1, std::f32::consts::PI - 0.1);
self.last_mouse_pos = (x, y);
true
}
pub fn mouse_wheel(&mut self, delta: f32) -> bool {
self.radius -= delta * self.zoom_sensitivity;
self.radius = self.radius.clamp(self.zoom_range.0, self.zoom_range.1);
true
}
pub fn update_camera_transform(&self, world: &mut World) {
if let Some(entity) = self.camera_entity
&& let Some(transform) = world.get_component_mut::<Transform>(entity)
{
transform.position = self.position().to_vec();
}
}
pub fn camera_entity(&self) -> Option<EntityId> {
self.camera_entity
}
}
impl Default for CameraController {
fn default() -> Self {
Self::new()
}
}
pub mod utils {
use super::*;
pub fn find_active_camera(world: &World) -> Option<(EntityId, &Camera, &Transform)> {
for (entity, camera) in world.query::<Camera>() {
if camera.is_active
&& let Some(transform) = world.get_component::<Transform>(entity)
{
return Some((entity, camera, transform));
}
}
None
}
pub fn view_matrix_from_transform(transform: &Transform) -> Matrix4<f32> {
let position = Point3::from_vec(transform.position);
let forward = Vector3::new(
transform.rotation.y.cos() * transform.rotation.x.cos(),
transform.rotation.x.sin(),
transform.rotation.y.sin() * transform.rotation.x.cos(),
);
let target = position + forward;
let up = Vector3::new(0.0, 1.0, 0.0);
Matrix4::look_at_rh(position, target, up)
}
}