use crate::ecs::Component;
use crate::math::{Mat4, Vec2, Vec3};
#[derive(Clone, Debug)]
pub struct Camera {
pub position: Vec2,
pub zoom: f32,
pub rotation: f32,
pub viewport_width: f32,
pub viewport_height: f32,
}
impl Camera {
pub fn new(viewport_width: f32, viewport_height: f32) -> Self {
Self {
position: Vec2::ZERO,
zoom: 1.0,
rotation: 0.0,
viewport_width,
viewport_height,
}
}
pub fn with_position(mut self, position: Vec2) -> Self {
self.position = position;
self
}
pub fn with_zoom(mut self, zoom: f32) -> Self {
self.zoom = zoom;
self
}
pub fn resize(&mut self, width: f32, height: f32) {
self.viewport_width = width;
self.viewport_height = height;
}
pub fn view_matrix(&self) -> Mat4 {
let left = -self.viewport_width / 2.0 / self.zoom;
let right = self.viewport_width / 2.0 / self.zoom;
let bottom = self.viewport_height / 2.0 / self.zoom;
let top = -self.viewport_height / 2.0 / self.zoom;
let ortho = Mat4::orthographic_rh(left, right, bottom, top, -1.0, 1.0);
let translation = Mat4::from_translation(Vec3::new(-self.position.x, -self.position.y, 0.0));
let rotation = Mat4::from_rotation_z(self.rotation);
ortho * rotation * translation
}
pub fn screen_to_world(&self, screen_pos: Vec2) -> Vec2 {
let normalized_x = (screen_pos.x / self.viewport_width - 0.5) * 2.0;
let normalized_y = (screen_pos.y / self.viewport_height - 0.5) * 2.0;
let world_x = normalized_x * (self.viewport_width / 2.0 / self.zoom) + self.position.x;
let world_y = normalized_y * (self.viewport_height / 2.0 / self.zoom) + self.position.y;
Vec2::new(world_x, world_y)
}
pub fn world_to_screen(&self, world_pos: Vec2) -> Vec2 {
let relative_x = (world_pos.x - self.position.x) * self.zoom / (self.viewport_width / 2.0);
let relative_y = (world_pos.y - self.position.y) * self.zoom / (self.viewport_height / 2.0);
let screen_x = (relative_x / 2.0 + 0.5) * self.viewport_width;
let screen_y = (relative_y / 2.0 + 0.5) * self.viewport_height;
Vec2::new(screen_x, screen_y)
}
pub fn follow(&mut self, target: Vec2, smoothness: f32, dt: f32) {
let diff = target - self.position;
self.position += diff * smoothness * dt;
}
pub fn follow_bounded(&mut self, target: Vec2, bounds: CameraBounds, smoothness: f32, dt: f32) {
let diff = target - self.position;
let mut new_pos = self.position + diff * smoothness * dt;
let half_width = self.viewport_width / 2.0 / self.zoom;
let half_height = self.viewport_height / 2.0 / self.zoom;
new_pos.x = new_pos.x.clamp(bounds.min.x + half_width, bounds.max.x - half_width);
new_pos.y = new_pos.y.clamp(bounds.min.y + half_height, bounds.max.y - half_height);
self.position = new_pos;
}
pub fn shake(&mut self, intensity: f32) {
use rand::Rng;
let mut rng = rand::thread_rng();
let offset_x = rng.gen_range(-intensity..intensity);
let offset_y = rng.gen_range(-intensity..intensity);
self.position += Vec2::new(offset_x, offset_y);
}
}
impl Component for Camera {}
#[derive(Clone, Copy, Debug)]
pub struct CameraBounds {
pub min: Vec2,
pub max: Vec2,
}
impl CameraBounds {
pub fn new(min: Vec2, max: Vec2) -> Self {
Self { min, max }
}
pub fn from_size(width: f32, height: f32) -> Self {
Self {
min: Vec2::new(-width / 2.0, -height / 2.0),
max: Vec2::new(width / 2.0, height / 2.0),
}
}
}
pub struct CameraController {
pub target_entity: Option<u64>,
pub follow_smoothness: f32,
pub bounds: Option<CameraBounds>,
pub zoom_speed: f32,
pub min_zoom: f32,
pub max_zoom: f32,
}
impl CameraController {
pub fn new() -> Self {
Self {
target_entity: None,
follow_smoothness: 5.0,
bounds: None,
zoom_speed: 1.0,
min_zoom: 0.1,
max_zoom: 10.0,
}
}
pub fn follow_entity(mut self, entity_id: u64) -> Self {
self.target_entity = Some(entity_id);
self
}
pub fn with_bounds(mut self, bounds: CameraBounds) -> Self {
self.bounds = Some(bounds);
self
}
pub fn with_smoothness(mut self, smoothness: f32) -> Self {
self.follow_smoothness = smoothness;
self
}
pub fn zoom_in(&self, camera: &mut Camera, amount: f32) {
camera.zoom = (camera.zoom + amount * self.zoom_speed).clamp(self.min_zoom, self.max_zoom);
}
pub fn zoom_out(&self, camera: &mut Camera, amount: f32) {
camera.zoom = (camera.zoom - amount * self.zoom_speed).clamp(self.min_zoom, self.max_zoom);
}
}
impl Default for CameraController {
fn default() -> Self {
Self::new()
}
}