Skip to main content

engvis_core/
camera.rs

1use glam::{Mat4, Vec3};
2use crate::aabb::Aabb;
3
4#[derive(Debug, Clone)]
5pub struct OrbitCamera {
6    /// Point the camera orbits around
7    pub target: Vec3,
8    /// Azimuth angle (radians, around Y axis)
9    pub yaw: f32,
10    /// Elevation angle (radians, clamped)
11    pub pitch: f32,
12    /// Distance from target
13    pub distance: f32,
14    /// Vertical field of view (radians)
15    pub fov_y: f32,
16    /// Near clipping plane
17    pub near: f32,
18    /// Far clipping plane
19    pub far: f32,
20    /// Aspect ratio (width / height)
21    pub aspect_ratio: f32,
22}
23
24impl OrbitCamera {
25    pub fn new(target: Vec3, distance: f32) -> Self {
26        Self {
27            target,
28            yaw: -0.785, // -45 degrees
29            pitch: 0.615, // ~35 degrees
30            distance,
31            fov_y: std::f32::consts::FRAC_PI_4, // 45 degrees
32            near: 0.01,
33            far: 1000.0,
34            aspect_ratio: 16.0 / 9.0,
35        }
36    }
37
38    /// Camera world-space position
39    pub fn position(&self) -> Vec3 {
40        let cos_pitch = self.pitch.cos();
41        let offset = Vec3::new(
42            self.distance * cos_pitch * self.yaw.sin(),
43            self.distance * self.pitch.sin(),
44            self.distance * cos_pitch * self.yaw.cos(),
45        );
46        self.target + offset
47    }
48
49    /// Right direction vector
50    pub fn right(&self) -> Vec3 {
51        let forward = (self.target - self.position()).normalize();
52        forward.cross(Vec3::Y).normalize()
53    }
54
55    /// Up direction vector
56    pub fn up(&self) -> Vec3 {
57        let forward = (self.target - self.position()).normalize();
58        let right = forward.cross(Vec3::Y).normalize();
59        right.cross(forward).normalize()
60    }
61
62    /// View matrix (world -> camera)
63    pub fn view_matrix(&self) -> Mat4 {
64        Mat4::look_at_rh(self.position(), self.target, Vec3::Y)
65    }
66
67    /// Projection matrix
68    pub fn projection_matrix(&self) -> Mat4 {
69        Mat4::perspective_rh(self.fov_y, self.aspect_ratio, self.near, self.far)
70    }
71
72    /// Combined view-projection matrix
73    pub fn view_projection(&self) -> Mat4 {
74        self.projection_matrix() * self.view_matrix()
75    }
76
77    /// Orbit by delta yaw/pitch
78    pub fn orbit(&mut self, delta_yaw: f32, delta_pitch: f32) {
79        self.yaw += delta_yaw;
80        self.pitch = (self.pitch + delta_pitch).clamp(
81            -std::f32::consts::FRAC_PI_2 + 0.01,
82            std::f32::consts::FRAC_PI_2 - 0.01,
83        );
84    }
85
86    /// Pan the target point in camera-local XY
87    pub fn pan(&mut self, delta_x: f32, delta_y: f32) {
88        let right = self.right();
89        let up = self.up();
90        self.target += right * delta_x + up * delta_y;
91    }
92
93    /// Zoom (change distance)
94    pub fn zoom(&mut self, delta: f32) {
95        self.distance *= 1.0 - delta;
96        self.distance = self.distance.clamp(0.1, 500.0);
97    }
98
99    /// Fit camera to show a bounding box
100    pub fn fit_to_aabb(&mut self, aabb: Aabb) {
101        if !aabb.is_valid() {
102            return;
103        }
104        self.target = aabb.center();
105        let radius = aabb.diagonal() * 0.5;
106        self.distance = (radius / (self.fov_y * 0.5).sin()).max(0.5);
107    }
108
109    /// Preset: front view
110    pub fn view_front(&mut self) {
111        self.yaw = 0.0;
112        self.pitch = 0.0;
113    }
114
115    /// Preset: top view
116    pub fn view_top(&mut self) {
117        self.yaw = 0.0;
118        self.pitch = std::f32::consts::FRAC_PI_2 - 0.01;
119    }
120
121    /// Preset: right view
122    pub fn view_right(&mut self) {
123        self.yaw = -std::f32::consts::FRAC_PI_2;
124        self.pitch = 0.0;
125    }
126
127    /// Preset: isometric view
128    pub fn view_iso(&mut self) {
129        self.yaw = -0.785;
130        self.pitch = 0.615;
131    }
132}
133
134impl Default for OrbitCamera {
135    fn default() -> Self {
136        Self::new(Vec3::ZERO, 5.0)
137    }
138}