1use glam::{Mat4, Vec3};
6
7#[derive(Debug, Clone)]
9pub struct Camera3D {
10 position: Vec3,
12 target: Vec3,
14 up: Vec3,
16 fov: f32,
18 aspect: f32,
20 near: f32,
22 far: f32,
24}
25
26impl Default for Camera3D {
27 fn default() -> Self {
28 Self {
29 position: Vec3::new(5.0, 3.0, 5.0),
30 target: Vec3::ZERO,
31 up: Vec3::Y,
32 fov: std::f32::consts::FRAC_PI_4, aspect: 16.0 / 9.0,
34 near: 0.1,
35 far: 1000.0,
36 }
37 }
38}
39
40impl Camera3D {
41 pub fn new(position: Vec3, target: Vec3) -> Self {
43 Self {
44 position,
45 target,
46 ..Default::default()
47 }
48 }
49
50 pub fn for_grid(grid_size: (f32, f32, f32)) -> Self {
52 let center = Vec3::new(grid_size.0 / 2.0, grid_size.1 / 2.0, grid_size.2 / 2.0);
53 let distance = (grid_size.0.max(grid_size.2) * 1.5).max(5.0);
54
55 Self {
56 position: center + Vec3::new(distance, distance * 0.7, distance),
57 target: center,
58 ..Default::default()
59 }
60 }
61
62 pub fn set_aspect(&mut self, aspect: f32) {
64 self.aspect = aspect;
65 }
66
67 pub fn set_fov_degrees(&mut self, degrees: f32) {
69 self.fov = degrees.to_radians();
70 }
71
72 pub fn position(&self) -> Vec3 {
74 self.position
75 }
76
77 pub fn target(&self) -> Vec3 {
79 self.target
80 }
81
82 pub fn set_position(&mut self, position: Vec3) {
84 self.position = position;
85 }
86
87 pub fn set_target(&mut self, target: Vec3) {
89 self.target = target;
90 }
91
92 pub fn translate(&mut self, delta: Vec3) {
94 self.position += delta;
95 self.target += delta;
96 }
97
98 pub fn view_matrix(&self) -> Mat4 {
100 Mat4::look_at_rh(self.position, self.target, self.up)
101 }
102
103 pub fn projection_matrix(&self) -> Mat4 {
105 Mat4::perspective_rh(self.fov, self.aspect, self.near, self.far)
106 }
107
108 pub fn view_projection_matrix(&self) -> Mat4 {
110 self.projection_matrix() * self.view_matrix()
111 }
112
113 pub fn forward(&self) -> Vec3 {
115 (self.target - self.position).normalize()
116 }
117
118 pub fn right(&self) -> Vec3 {
120 self.forward().cross(self.up).normalize()
121 }
122
123 pub fn distance_to_target(&self) -> f32 {
125 (self.position - self.target).length()
126 }
127}
128
129pub struct CameraController {
131 pub rotate_speed: f32,
133 pub pan_speed: f32,
135 pub zoom_speed: f32,
137 pub min_distance: f32,
139 pub max_distance: f32,
141 theta: f32,
143 phi: f32,
144 distance: f32,
146 is_rotating: bool,
148 is_panning: bool,
149 last_mouse: (f32, f32),
150}
151
152impl Default for CameraController {
153 fn default() -> Self {
154 Self {
155 rotate_speed: 0.005,
156 pan_speed: 0.01,
157 zoom_speed: 0.1,
158 min_distance: 0.5,
159 max_distance: 500.0,
160 theta: std::f32::consts::FRAC_PI_4,
161 phi: std::f32::consts::FRAC_PI_4,
162 distance: 10.0,
163 is_rotating: false,
164 is_panning: false,
165 last_mouse: (0.0, 0.0),
166 }
167 }
168}
169
170impl CameraController {
171 pub fn new() -> Self {
173 Self::default()
174 }
175
176 pub fn from_camera(camera: &Camera3D) -> Self {
178 let dir = camera.position - camera.target;
179 let distance = dir.length();
180
181 let theta = dir.x.atan2(dir.z);
182 let phi = (dir.y / distance).asin();
183
184 Self {
185 distance,
186 theta,
187 phi,
188 ..Default::default()
189 }
190 }
191
192 pub fn on_mouse_down(&mut self, button: MouseButton, x: f32, y: f32) {
194 self.last_mouse = (x, y);
195 match button {
196 MouseButton::Left => self.is_rotating = true,
197 MouseButton::Middle => self.is_panning = true,
198 MouseButton::Right => self.is_panning = true,
199 }
200 }
201
202 pub fn on_mouse_up(&mut self, button: MouseButton) {
204 match button {
205 MouseButton::Left => self.is_rotating = false,
206 MouseButton::Middle => self.is_panning = false,
207 MouseButton::Right => self.is_panning = false,
208 }
209 }
210
211 pub fn on_mouse_move(&mut self, x: f32, y: f32, camera: &mut Camera3D) {
213 let dx = x - self.last_mouse.0;
214 let dy = y - self.last_mouse.1;
215 self.last_mouse = (x, y);
216
217 if self.is_rotating {
218 self.theta -= dx * self.rotate_speed;
219 self.phi = (self.phi + dy * self.rotate_speed).clamp(
220 -std::f32::consts::FRAC_PI_2 + 0.01,
221 std::f32::consts::FRAC_PI_2 - 0.01,
222 );
223 self.update_camera(camera);
224 }
225
226 if self.is_panning {
227 let right = camera.right();
228 let up = camera.up;
229 let pan = (right * -dx + up * dy) * self.pan_speed * self.distance * 0.01;
230 camera.translate(pan);
231 }
232 }
233
234 pub fn on_scroll(&mut self, delta: f32, camera: &mut Camera3D) {
236 self.distance *= 1.0 - delta * self.zoom_speed;
237 self.distance = self.distance.clamp(self.min_distance, self.max_distance);
238 self.update_camera(camera);
239 }
240
241 pub fn update_camera(&self, camera: &mut Camera3D) {
243 let x = self.distance * self.phi.cos() * self.theta.sin();
244 let y = self.distance * self.phi.sin();
245 let z = self.distance * self.phi.cos() * self.theta.cos();
246
247 camera.position = camera.target + Vec3::new(x, y, z);
248 }
249
250 pub fn reset(&mut self, camera: &mut Camera3D, grid_size: (f32, f32, f32)) {
252 let center = Vec3::new(grid_size.0 / 2.0, grid_size.1 / 2.0, grid_size.2 / 2.0);
253 camera.target = center;
254
255 self.theta = std::f32::consts::FRAC_PI_4;
256 self.phi = std::f32::consts::FRAC_PI_4 * 0.5;
257 self.distance = (grid_size.0.max(grid_size.2) * 1.5).max(5.0);
258
259 self.update_camera(camera);
260 }
261
262 pub fn set_orbit(&mut self, theta: f32, phi: f32, camera: &mut Camera3D) {
264 self.theta = theta;
265 self.phi = phi.clamp(
266 -std::f32::consts::FRAC_PI_2 + 0.01,
267 std::f32::consts::FRAC_PI_2 - 0.01,
268 );
269 self.update_camera(camera);
270 }
271
272 pub fn set_distance(&mut self, distance: f32, camera: &mut Camera3D) {
274 self.distance = distance.clamp(self.min_distance, self.max_distance);
275 self.update_camera(camera);
276 }
277
278 pub fn orbit_params(&self) -> (f32, f32, f32) {
280 (self.theta, self.phi, self.distance)
281 }
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
286pub enum MouseButton {
287 Left,
288 Middle,
289 Right,
290}
291
292pub struct FirstPersonController {
294 pub move_speed: f32,
296 pub sensitivity: f32,
298 pitch: f32,
300 yaw: f32,
302 forward: bool,
304 backward: bool,
305 left: bool,
306 right: bool,
307 up: bool,
308 down: bool,
309}
310
311impl Default for FirstPersonController {
312 fn default() -> Self {
313 Self {
314 move_speed: 5.0,
315 sensitivity: 0.003,
316 pitch: 0.0,
317 yaw: 0.0,
318 forward: false,
319 backward: false,
320 left: false,
321 right: false,
322 up: false,
323 down: false,
324 }
325 }
326}
327
328impl FirstPersonController {
329 pub fn new() -> Self {
330 Self::default()
331 }
332
333 pub fn on_key_down(&mut self, key: Key) {
335 match key {
336 Key::W => self.forward = true,
337 Key::S => self.backward = true,
338 Key::A => self.left = true,
339 Key::D => self.right = true,
340 Key::Space => self.up = true,
341 Key::Shift => self.down = true,
342 _ => {}
343 }
344 }
345
346 pub fn on_key_up(&mut self, key: Key) {
348 match key {
349 Key::W => self.forward = false,
350 Key::S => self.backward = false,
351 Key::A => self.left = false,
352 Key::D => self.right = false,
353 Key::Space => self.up = false,
354 Key::Shift => self.down = false,
355 _ => {}
356 }
357 }
358
359 pub fn on_mouse_move(&mut self, dx: f32, dy: f32, camera: &mut Camera3D) {
361 self.yaw -= dx * self.sensitivity;
362 self.pitch = (self.pitch - dy * self.sensitivity).clamp(
363 -std::f32::consts::FRAC_PI_2 + 0.01,
364 std::f32::consts::FRAC_PI_2 - 0.01,
365 );
366
367 self.update_camera_direction(camera);
368 }
369
370 pub fn update(&self, dt: f32, camera: &mut Camera3D) {
372 let forward = camera.forward();
373 let right = camera.right();
374
375 let mut movement = Vec3::ZERO;
376
377 if self.forward {
378 movement += forward;
379 }
380 if self.backward {
381 movement -= forward;
382 }
383 if self.right {
384 movement += right;
385 }
386 if self.left {
387 movement -= right;
388 }
389 if self.up {
390 movement += Vec3::Y;
391 }
392 if self.down {
393 movement -= Vec3::Y;
394 }
395
396 if movement.length_squared() > 0.0 {
397 movement = movement.normalize() * self.move_speed * dt;
398 camera.position += movement;
399 camera.target += movement;
400 }
401 }
402
403 fn update_camera_direction(&self, camera: &mut Camera3D) {
404 let direction = Vec3::new(
405 self.yaw.cos() * self.pitch.cos(),
406 self.pitch.sin(),
407 self.yaw.sin() * self.pitch.cos(),
408 )
409 .normalize();
410
411 camera.target = camera.position + direction;
412 }
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
417pub enum Key {
418 W,
419 A,
420 S,
421 D,
422 Space,
423 Shift,
424 Other,
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn test_camera_creation() {
433 let camera = Camera3D::default();
434 assert!(camera.position.length() > 0.0);
435 }
436
437 #[test]
438 fn test_camera_matrices() {
439 let camera = Camera3D::default();
440
441 let view = camera.view_matrix();
442 let proj = camera.projection_matrix();
443 let view_proj = camera.view_projection_matrix();
444
445 let expected = proj * view;
447 for i in 0..4 {
448 for j in 0..4 {
449 assert!(
450 (view_proj.col(i)[j] - expected.col(i)[j]).abs() < 0.001,
451 "Mismatch at [{}, {}]",
452 i,
453 j
454 );
455 }
456 }
457 }
458
459 #[test]
460 fn test_camera_controller() {
461 let mut camera = Camera3D::default();
462 let mut controller = CameraController::from_camera(&camera);
463
464 let initial_distance = camera.distance_to_target();
465
466 controller.on_scroll(1.0, &mut camera);
468 assert!(camera.distance_to_target() < initial_distance);
469 }
470
471 #[test]
472 fn test_for_grid() {
473 let camera = Camera3D::for_grid((10.0, 5.0, 10.0));
474
475 let center = Vec3::new(5.0, 2.5, 5.0);
477 let diff = (camera.target - center).length();
478 assert!(diff < 0.1);
479 }
480}