1use crate::camera::Camera;
7use core::f32::consts::FRAC_PI_2;
8use nalgebra::{Point3, Vector3};
9
10#[cfg(not(feature = "std"))]
11#[allow(unused_imports)]
12use micromath::F32Ext;
13
14#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct OrbitCameraController {
20 pub target: Point3<f32>,
22 pub distance: f32,
24 pub yaw: f32,
26 pub pitch: f32,
28 pub min_distance: f32,
30 pub max_distance: f32,
32 pub min_pitch: f32,
34 pub max_pitch: f32,
36}
37
38impl OrbitCameraController {
39 pub fn new(target: Point3<f32>, distance: f32) -> Self {
41 Self {
42 target,
43 distance: distance.max(0.1),
44 yaw: 0.0,
45 pitch: 0.0,
46 min_distance: 0.5,
47 max_distance: 100.0,
48 min_pitch: -FRAC_PI_2 + 0.01,
49 max_pitch: FRAC_PI_2 - 0.01,
50 }
51 }
52
53 pub fn with_distance_limits(mut self, min: f32, max: f32) -> Self {
55 self.min_distance = min;
56 self.max_distance = max;
57 self.distance = self.distance.clamp(min, max);
58 self
59 }
60
61 pub fn with_pitch_limits(mut self, min: f32, max: f32) -> Self {
63 self.min_pitch = min;
64 self.max_pitch = max;
65 self.pitch = self.pitch.clamp(min, max);
66 self
67 }
68
69 pub fn orbit(&mut self, delta_yaw: f32, delta_pitch: f32) {
71 self.yaw += delta_yaw;
72 self.pitch = (self.pitch + delta_pitch).clamp(self.min_pitch, self.max_pitch);
73 }
74
75 pub fn zoom(&mut self, delta_distance: f32) {
77 self.distance =
78 (self.distance + delta_distance).clamp(self.min_distance, self.max_distance);
79 }
80
81 pub fn pan(&mut self, delta_right: f32, delta_up: f32) {
83 let cos_yaw = self.yaw.cos();
84 let sin_yaw = self.yaw.sin();
85 let right = Vector3::new(cos_yaw, 0.0, -sin_yaw);
86 let up = Vector3::new(0.0, 1.0, 0.0);
87
88 let offset = right * delta_right + up * delta_up;
89 self.target += offset;
90 }
91
92 #[inline]
94 pub fn eye_position(&self) -> Point3<f32> {
95 let cos_pitch = self.pitch.cos();
96 let sin_pitch = self.pitch.sin();
97 let cos_yaw = self.yaw.cos();
98 let sin_yaw = self.yaw.sin();
99
100 let x = self.target.x + self.distance * cos_pitch * sin_yaw;
101 let y = self.target.y + self.distance * sin_pitch;
102 let z = self.target.z + self.distance * cos_pitch * cos_yaw;
103
104 Point3::new(x, y, z)
105 }
106
107 pub fn update_camera(&self, camera: &mut Camera) {
109 camera.set_target(self.target);
110 camera.set_position(self.eye_position());
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq)]
118pub struct FpsCameraController {
119 pub position: Point3<f32>,
121 pub yaw: f32,
123 pub pitch: f32,
125 pub move_speed: f32,
127 pub min_pitch: f32,
129 pub max_pitch: f32,
131}
132
133impl FpsCameraController {
134 pub fn new(position: Point3<f32>) -> Self {
136 Self {
137 position,
138 yaw: 0.0,
139 pitch: 0.0,
140 move_speed: 5.0,
141 min_pitch: -FRAC_PI_2 + 0.01,
142 max_pitch: FRAC_PI_2 - 0.01,
143 }
144 }
145
146 pub fn rotate(&mut self, delta_yaw: f32, delta_pitch: f32) {
148 self.yaw += delta_yaw;
149 self.pitch = (self.pitch + delta_pitch).clamp(self.min_pitch, self.max_pitch);
150 }
151
152 #[inline]
154 pub fn horizontal_forward(&self) -> Vector3<f32> {
155 Vector3::new(self.yaw.sin(), 0.0, -self.yaw.cos())
156 }
157
158 #[inline]
160 pub fn horizontal_right(&self) -> Vector3<f32> {
161 Vector3::new(self.yaw.cos(), 0.0, self.yaw.sin())
162 }
163
164 #[inline]
166 pub fn look_direction(&self) -> Vector3<f32> {
167 let cos_pitch = self.pitch.cos();
168 let sin_pitch = self.pitch.sin();
169 let cos_yaw = self.yaw.cos();
170 let sin_yaw = self.yaw.sin();
171
172 Vector3::new(cos_pitch * sin_yaw, sin_pitch, -cos_pitch * cos_yaw)
173 }
174
175 pub fn move_relative(&mut self, forward: f32, strafe: f32, vertical: f32, dt: f32) {
181 let fwd = self.horizontal_forward() * (forward * self.move_speed * dt);
182 let right = self.horizontal_right() * (strafe * self.move_speed * dt);
183 let up = Vector3::new(0.0, vertical * self.move_speed * dt, 0.0);
184
185 self.position += fwd + right + up;
186 }
187
188 pub fn update_camera(&self, camera: &mut Camera) {
190 let target = self.position + self.look_direction();
191 camera.set_target(target);
192 camera.set_position(self.position);
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_orbit_camera_eye_position() {
202 let mut orbit = OrbitCameraController::new(Point3::new(0.0, 0.0, 0.0), 5.0);
203 let eye = orbit.eye_position();
204 assert!((eye.z - 5.0).abs() < 1e-4);
205 assert!(eye.x.abs() < 1e-4);
206 assert!(eye.y.abs() < 1e-4);
207
208 orbit.orbit(core::f32::consts::FRAC_PI_2, 0.0);
210 let eye90 = orbit.eye_position();
211 assert!((eye90.x - 5.0).abs() < 1e-4);
212 assert!(eye90.z.abs() < 1e-4);
213 }
214
215 #[test]
216 fn test_orbit_camera_pitch_clamping() {
217 let mut orbit = OrbitCameraController::new(Point3::new(0.0, 0.0, 0.0), 5.0);
218 orbit.orbit(0.0, 10.0); assert!(orbit.pitch <= orbit.max_pitch);
220
221 orbit.orbit(0.0, -20.0); assert!(orbit.pitch >= orbit.min_pitch);
223 }
224
225 #[test]
226 fn test_fps_camera_movement() {
227 let mut fps = FpsCameraController::new(Point3::new(0.0, 0.0, 0.0));
228 fps.move_relative(1.0, 0.0, 0.0, 1.0);
230 assert!((fps.position.z - (-5.0)).abs() < 1e-4);
231
232 fps.move_relative(0.0, 1.0, 0.0, 1.0);
234 assert!((fps.position.x - 5.0).abs() < 1e-4);
235 }
236
237 #[test]
238 fn test_orbit_zoom_pan_limits_and_camera_sync() {
239 let mut orbit = OrbitCameraController::new(Point3::new(1.0, 2.0, 3.0), 5.0)
240 .with_distance_limits(1.0, 10.0)
241 .with_pitch_limits(-1.0, 1.0);
242 orbit.zoom(100.0);
243 assert_eq!(orbit.distance, 10.0);
244 orbit.zoom(-100.0);
245 assert_eq!(orbit.distance, 1.0);
246
247 orbit.orbit(0.0, 2.0);
248 assert_eq!(orbit.pitch, 1.0);
249 orbit.pan(1.0, 0.5);
250 assert!((orbit.target.x - 1.0).abs() > 1e-5);
251 assert!(orbit.target.y > 2.0);
252
253 let mut camera = Camera::new(1.0);
254 orbit.update_camera(&mut camera);
255 let eye = orbit.eye_position();
256 assert!((camera.position.x - eye.x).abs() < 1e-5);
257 assert_eq!(camera.position.z, eye.z);
258 }
259
260 #[test]
261 fn test_fps_rotate_vectors_and_camera_sync() {
262 let mut fps = FpsCameraController::new(Point3::new(0.0, 0.0, 0.0));
263 let fwd = fps.horizontal_forward();
264 assert!((fwd.z - (-1.0)).abs() < 1e-5);
265 let right = fps.horizontal_right();
266 assert!((right.x - 1.0).abs() < 1e-5);
267 let look = fps.look_direction();
268 assert!((look.z - (-1.0)).abs() < 1e-5);
269
270 fps.rotate(core::f32::consts::FRAC_PI_2, 0.0);
271 assert!((fps.horizontal_forward().x - 1.0).abs() < 1e-4);
272 fps.rotate(0.0, 5.0);
273 assert_eq!(fps.pitch, fps.max_pitch);
274
275 fps.move_relative(0.0, 0.0, 1.0, 1.0);
276 assert!((fps.position.y - 5.0).abs() < 1e-4);
277
278 let mut camera = Camera::new(1.0);
279 fps.update_camera(&mut camera);
280 assert_eq!(camera.position.x, fps.position.x);
281 }
282}