anvilkit_camera/
controller.rs1use bevy_ecs::prelude::*;
2use glam::Vec3;
3
4pub enum CameraMode {
6 FirstPerson,
8 ThirdPerson {
10 target: Vec3,
11 distance: f32,
12 min_distance: f32,
13 max_distance: f32,
14 },
15 Free,
17}
18
19impl Default for CameraMode {
20 fn default() -> Self {
21 Self::FirstPerson
22 }
23}
24
25#[derive(Component)]
27pub struct CameraController {
28 pub mode: CameraMode,
29 pub yaw: f32,
30 pub pitch: f32,
31 pub pitch_limits: (f32, f32),
32 pub mouse_sensitivity: f32,
33 pub move_speed: f32,
34 pub zoom_speed: f32,
35 pub smoothing: f32,
36 pub base_fov: f32,
38 pub(crate) smooth_yaw: f32,
40 pub(crate) smooth_pitch: f32,
41 pub(crate) smooth_position: Vec3,
42}
43
44impl Default for CameraController {
45 fn default() -> Self {
46 Self {
47 mode: CameraMode::FirstPerson,
48 yaw: 0.0,
49 pitch: 0.0,
50 pitch_limits: (-1.48, 1.48), mouse_sensitivity: 0.003,
52 move_speed: 10.0,
53 zoom_speed: 2.0,
54 smoothing: 0.0,
55 base_fov: 70.0,
56 smooth_yaw: 0.0,
57 smooth_pitch: 0.0,
58 smooth_position: Vec3::ZERO,
59 }
60 }
61}
62
63impl CameraController {
64 pub fn effective_yaw(&self) -> f32 {
66 if self.smoothing > 0.0 {
67 self.smooth_yaw
68 } else {
69 self.yaw
70 }
71 }
72
73 pub fn effective_pitch(&self) -> f32 {
75 if self.smoothing > 0.0 {
76 self.smooth_pitch
77 } else {
78 self.pitch
79 }
80 }
81
82 pub fn rotation(&self) -> glam::Quat {
84 glam::Quat::from_rotation_y(self.effective_yaw())
85 * glam::Quat::from_rotation_x(self.effective_pitch())
86 }
87
88 pub fn forward_xz(&self) -> Vec3 {
90 glam::Quat::from_rotation_y(self.effective_yaw()) * Vec3::Z
91 }
92
93 pub fn right_xz(&self) -> Vec3 {
95 glam::Quat::from_rotation_y(self.effective_yaw()) * Vec3::X
96 }
97
98 pub fn toggle_perspective(&mut self, player_pos: Vec3) {
100 match &self.mode {
101 CameraMode::FirstPerson => {
102 self.mode = CameraMode::ThirdPerson {
103 target: player_pos,
104 distance: 5.0,
105 min_distance: 2.0,
106 max_distance: 20.0,
107 };
108 self.smoothing = 0.15;
109 }
110 CameraMode::ThirdPerson { .. } => {
111 self.mode = CameraMode::FirstPerson;
112 self.smoothing = 0.0;
113 }
114 CameraMode::Free => {
115 self.mode = CameraMode::FirstPerson;
116 self.smoothing = 0.0;
117 }
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_default_controller() {
128 let ctrl = CameraController::default();
129 assert_eq!(ctrl.yaw, 0.0);
130 assert_eq!(ctrl.pitch, 0.0);
131 assert_eq!(ctrl.base_fov, 70.0);
132 assert!(matches!(ctrl.mode, CameraMode::FirstPerson));
133 }
134
135 #[test]
136 fn test_rotation_identity_at_zero() {
137 let ctrl = CameraController::default();
138 let rot = ctrl.rotation();
139 let diff = rot.dot(glam::Quat::IDENTITY);
140 assert!((diff - 1.0).abs() < 1e-5);
141 }
142
143 #[test]
144 fn test_forward_xz_at_zero_yaw() {
145 let ctrl = CameraController::default();
146 let fwd = ctrl.forward_xz();
147 assert!(fwd.z.abs() > 0.99);
148 }
149
150 #[test]
151 fn test_toggle_perspective() {
152 let mut ctrl = CameraController::default();
153 assert!(matches!(ctrl.mode, CameraMode::FirstPerson));
154 ctrl.toggle_perspective(Vec3::ZERO);
155 assert!(matches!(ctrl.mode, CameraMode::ThirdPerson { .. }));
156 ctrl.toggle_perspective(Vec3::ZERO);
157 assert!(matches!(ctrl.mode, CameraMode::FirstPerson));
158 }
159
160 #[test]
161 fn test_pitch_limits() {
162 let ctrl = CameraController::default();
163 assert!(ctrl.pitch_limits.0 < 0.0);
164 assert!(ctrl.pitch_limits.1 > 0.0);
165 }
166}