use crate::core::engine::glfw::{GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS};
use crate::core::engine::opengl::Vec2;
pub trait Projection {
fn world_to_screen(&self, world: Vec2) -> Vec2;
fn screen_to_world(&self, screen: Vec2) -> Vec2;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct IdentityProjection;
impl Projection for IdentityProjection {
fn world_to_screen(&self, world: Vec2) -> Vec2 {
world
}
fn screen_to_world(&self, screen: Vec2) -> Vec2 {
screen
}
}
#[derive(Debug, Clone, Copy)]
pub struct Camera2D {
center: Vec2,
scale: f32,
screen_size: Vec2,
}
impl Camera2D {
pub fn new(center: Vec2, scale: f32, screen_size: Vec2) -> Self {
Self {
center,
scale,
screen_size,
}
}
pub fn center(&self) -> Vec2 {
self.center
}
pub fn set_center(&mut self, center: Vec2) {
self.center = center;
}
pub fn scale(&self) -> f32 {
self.scale
}
pub fn set_scale(&mut self, scale: f32) {
self.scale = scale;
}
pub fn screen_size(&self) -> Vec2 {
self.screen_size
}
pub fn set_screen_size(&mut self, screen_size: Vec2) {
self.screen_size = screen_size;
}
pub fn pan(&mut self, delta: Vec2) {
self.center.x += delta.x;
self.center.y += delta.y;
}
pub fn pan_screen(&mut self, delta_pixels: Vec2) {
self.center.x -= delta_pixels.x / self.scale;
self.center.y -= delta_pixels.y / self.scale;
}
pub fn zoom(&mut self, factor: f32) {
self.scale *= factor;
}
pub fn zoom_at(&mut self, factor: f32, screen_point: Vec2) {
let world_before = self.screen_to_world(screen_point);
self.scale *= factor;
let world_after = self.screen_to_world(screen_point);
self.center.x += world_before.x - world_after.x;
self.center.y += world_before.y - world_after.y;
}
pub fn world_bounds(&self) -> (f32, f32, f32, f32) {
let half_width = self.screen_size.x / (2.0 * self.scale);
let half_height = self.screen_size.y / (2.0 * self.scale);
(
self.center.x - half_width,
self.center.y - half_height,
self.center.x + half_width,
self.center.y + half_height,
)
}
}
impl Projection for Camera2D {
fn world_to_screen(&self, world: Vec2) -> Vec2 {
Vec2 {
x: (world.x - self.center.x) * self.scale + self.screen_size.x * 0.5,
y: (world.y - self.center.y) * self.scale + self.screen_size.y * 0.5,
}
}
fn screen_to_world(&self, screen: Vec2) -> Vec2 {
Vec2 {
x: (screen.x - self.screen_size.x * 0.5) / self.scale + self.center.x,
y: (screen.y - self.screen_size.y * 0.5) / self.scale + self.center.y,
}
}
}
pub struct CameraController {
camera: Camera2D,
target_scale: f32,
target_center: Vec2,
smoothness: f32,
is_dragging: bool,
last_cursor_pos: Vec2,
zoom_sensitivity: f32,
}
impl CameraController {
pub fn new(camera: Camera2D) -> Self {
let scale = camera.scale();
let center = camera.center();
Self {
camera,
target_scale: scale,
target_center: center,
smoothness: 0.0,
is_dragging: false,
last_cursor_pos: Vec2::new(0.0, 0.0),
zoom_sensitivity: 1.1,
}
}
pub fn set_smoothness(&mut self, value: f32) {
self.smoothness = value.max(0.0);
if self.smoothness == 0.0 {
self.camera.set_scale(self.target_scale);
self.camera.set_center(self.target_center);
}
}
pub fn update(&mut self, dt: f32) {
if self.smoothness == 0.0 {
return;
}
let t = 1.0 - (-self.smoothness * dt).exp();
let current_scale = self.camera.scale();
let new_scale = current_scale + (self.target_scale - current_scale) * t;
self.camera.set_scale(new_scale);
let current_center = self.camera.center();
let new_center = Vec2::new(
current_center.x + (self.target_center.x - current_center.x) * t,
current_center.y + (self.target_center.y - current_center.y) * t,
);
self.camera.set_center(new_center);
}
pub fn set_zoom_sensitivity(&mut self, sensitivity: f32) {
self.zoom_sensitivity = sensitivity;
}
pub fn on_mouse_button(&mut self, button: i32, action: i32) {
if button == GLFW_MOUSE_BUTTON_LEFT {
self.is_dragging = action == GLFW_PRESS;
}
}
pub fn on_cursor_move(&mut self, x: f64, y: f64) {
let cursor = Vec2::new(x as f32, y as f32);
if self.is_dragging {
let delta = Vec2::new(
cursor.x - self.last_cursor_pos.x,
cursor.y - self.last_cursor_pos.y,
);
if self.smoothness > 0.0 {
self.target_center.x -= delta.x / self.target_scale;
self.target_center.y -= delta.y / self.target_scale;
} else {
self.camera.pan_screen(delta);
}
}
self.last_cursor_pos = cursor;
}
pub fn on_scroll(&mut self, y_offset: f64) {
let factor = if y_offset > 0.0 {
self.zoom_sensitivity
} else {
1.0 / self.zoom_sensitivity
};
if self.smoothness > 0.0 {
let screen_size = self.camera.screen_size();
let world_before = Vec2 {
x: (self.last_cursor_pos.x - screen_size.x * 0.5) / self.target_scale
+ self.target_center.x,
y: (self.last_cursor_pos.y - screen_size.y * 0.5) / self.target_scale
+ self.target_center.y,
};
self.target_scale *= factor;
let world_after = Vec2 {
x: (self.last_cursor_pos.x - screen_size.x * 0.5) / self.target_scale
+ self.target_center.x,
y: (self.last_cursor_pos.y - screen_size.y * 0.5) / self.target_scale
+ self.target_center.y,
};
self.target_center.x += world_before.x - world_after.x;
self.target_center.y += world_before.y - world_after.y;
} else {
self.camera.zoom_at(factor, self.last_cursor_pos);
}
}
pub fn camera(&self) -> &Camera2D {
&self.camera
}
pub fn camera_mut(&mut self) -> &mut Camera2D {
&mut self.camera
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_projection() {
let proj = IdentityProjection;
let world = Vec2::new(100.0, 200.0);
assert_eq!(proj.world_to_screen(world), world);
assert_eq!(proj.screen_to_world(world), world);
}
#[test]
fn test_camera_center_at_origin() {
let camera = Camera2D::new(
Vec2::new(0.0, 0.0),
1.0,
Vec2::new(800.0, 600.0),
);
let screen = camera.world_to_screen(Vec2::new(0.0, 0.0));
assert_eq!(screen.x, 400.0);
assert_eq!(screen.y, 300.0);
let world = camera.screen_to_world(Vec2::new(400.0, 300.0));
assert_eq!(world.x, 0.0);
assert_eq!(world.y, 0.0);
}
#[test]
fn test_camera_with_offset_center() {
let camera = Camera2D::new(
Vec2::new(100.0, 50.0), 1.0,
Vec2::new(800.0, 600.0),
);
let screen = camera.world_to_screen(Vec2::new(100.0, 50.0));
assert_eq!(screen.x, 400.0);
assert_eq!(screen.y, 300.0);
let screen = camera.world_to_screen(Vec2::new(0.0, 0.0));
assert_eq!(screen.x, 300.0); assert_eq!(screen.y, 250.0); }
#[test]
fn test_camera_with_scale() {
let camera = Camera2D::new(
Vec2::new(0.0, 0.0),
2.0, Vec2::new(800.0, 600.0),
);
let screen = camera.world_to_screen(Vec2::new(10.0, 10.0));
assert_eq!(screen.x, 420.0); assert_eq!(screen.y, 320.0);
let world = camera.screen_to_world(screen);
assert!((world.x - 10.0).abs() < 0.001);
assert!((world.y - 10.0).abs() < 0.001);
}
#[test]
fn test_camera_world_bounds() {
let camera = Camera2D::new(
Vec2::new(0.0, 0.0),
1.0,
Vec2::new(800.0, 600.0),
);
let (min_x, min_y, max_x, max_y) = camera.world_bounds();
assert_eq!(min_x, -400.0);
assert_eq!(min_y, -300.0);
assert_eq!(max_x, 400.0);
assert_eq!(max_y, 300.0);
}
#[test]
fn test_camera_zoom_at_center() {
let mut camera = Camera2D::new(
Vec2::new(0.0, 0.0),
1.0,
Vec2::new(800.0, 600.0),
);
camera.zoom_at(2.0, Vec2::new(400.0, 300.0));
assert_eq!(camera.center().x, 0.0);
assert_eq!(camera.center().y, 0.0);
assert_eq!(camera.scale(), 2.0);
}
#[test]
fn test_camera_zoom_at_corner() {
let mut camera = Camera2D::new(
Vec2::new(0.0, 0.0),
1.0,
Vec2::new(800.0, 600.0),
);
let corner_world_before = camera.screen_to_world(Vec2::new(0.0, 0.0));
camera.zoom_at(2.0, Vec2::new(0.0, 0.0));
let corner_world_after = camera.screen_to_world(Vec2::new(0.0, 0.0));
assert!((corner_world_before.x - corner_world_after.x).abs() < 0.001);
assert!((corner_world_before.y - corner_world_after.y).abs() < 0.001);
}
}