veloren_voxygen/scene/camera.rs
1use common::{terrain::TerrainGrid, vol::ReadVol};
2use common_base::span;
3use core::{f32::consts::PI, fmt::Debug, ops::Range};
4use num::traits::{FloatConst, real::Real};
5use treeculler::Frustum;
6use vek::*;
7
8pub const NEAR_PLANE: f32 = 0.0625;
9pub const FAR_PLANE: f32 = 524288.06; // excessive precision: 524288.0625
10
11const FIRST_PERSON_INTERP_TIME: f32 = 0.1;
12const THIRD_PERSON_INTERP_TIME: f32 = 0.1;
13const FREEFLY_INTERP_TIME: f32 = 0.0;
14const LERP_ORI_RATE: f32 = 15.0;
15const CLIPPING_MODE_RANGE: Range<f32> = 2.0..20.0;
16pub const MIN_ZOOM: f32 = 0.1;
17
18// Possible TODO: Add more modes
19#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash, Default)]
20pub enum CameraMode {
21 FirstPerson = 0,
22 #[default]
23 ThirdPerson = 1,
24 Freefly = 2,
25}
26
27#[derive(Clone, Copy)]
28pub struct Dependents {
29 pub view_mat: Mat4<f32>,
30 pub view_mat_inv: Mat4<f32>,
31 pub proj_mat: Mat4<f32>,
32 pub proj_mat_inv: Mat4<f32>,
33 /// Specifically there for satisfying our treeculler dependency, which can't
34 /// handle inverted depth planes.
35 pub proj_mat_treeculler: Mat4<f32>,
36 pub cam_pos: Vec3<f32>,
37 pub cam_dir: Vec3<f32>,
38}
39
40pub struct Camera {
41 tgt_focus: Vec3<f32>,
42 focus: Vec3<f32>,
43 tgt_ori: Vec3<f32>,
44 ori: Vec3<f32>,
45 tgt_dist: f32,
46 dist: f32,
47 tgt_fov: f32,
48 fov: f32,
49 tgt_fixate: f32,
50 fixate: f32,
51 aspect: f32,
52 mode: CameraMode,
53
54 last_time: Option<f64>,
55
56 dependents: Dependents,
57 frustum: Frustum<f32>,
58}
59
60fn clamp_and_modulate(ori: Vec3<f32>) -> Vec3<f32> {
61 Vec3 {
62 // Wrap camera yaw
63 x: ori.x.rem_euclid(2.0 * PI),
64 // Clamp camera pitch to the vertical limits
65 y: ori.y.clamp(-PI / 2.0 + 0.0001, PI / 2.0 - 0.0001),
66 // Wrap camera roll
67 z: ori.z.rem_euclid(2.0 * PI),
68 }
69}
70
71/// Generalized method to construct a perspective projection with `x ∈ [-1,1], y
72/// ∈ [-1,1], z ∈ [0,1]` given fov_y_radians, aspect_ratio, 1/n, and 1/f. Note
73/// that you pass in *1/n* and *1/f*, not n and f like you normally would for a
74/// perspective projection; this is done to enable uniform handling of both
75/// finite and infinite far planes.
76///
77/// The only requirements on n and f are: 1/n ≠ 1/f, and 0 ≤ 1/n * 1/f.
78///
79/// This ensures that the near and far plane are not identical (or else your
80/// projection would not cover any distance), and that they have the same sign
81/// (or else we cannot rely on clipping to properly fix your scene). This also
82/// ensures that at least one of 1/n and 1/f is not 0, and by construction it
83/// guarantees that neither n nor f are 0; these are required in order to make
84/// sense of the definition of near and far planes, and avoid collapsing all
85/// depths to a single point.
86///
87/// For "typical" projections (matching perspective_lh_no), you would satisfy
88/// the stronger requirements. We give the typical conditions for each bullet
89/// point, and then explain the consequences of not satisfying these conditions:
90///
91/// * 1/n < 1/f (0 to 1 depth planes, meaning n = near and f = far; if f < n,
92/// depth planes go from 1 to 0, meaning f = near and n = far, aka "reverse
93/// depth").
94///
95/// This is by far the most likely thing to want to change; inverted depth
96/// coordinates have *far* better accuracy for DirectX / Metal / WGPU-like
97/// APIs, when using floating point depth, while not being *worse* than the
98/// alternative (OpenGL-like depth, or when using fixed-point / integer depth).
99/// For maximum benefit, make sure you are using Depth32F, as on most
100/// platforms this is the only depth buffer size where floating point can be
101/// used.
102///
103/// It is a bit unintuitive to prove this, but it turns out that when using
104/// 1 to 0 depth planes, the point where the depth buffer has its worst
105/// precision is not at the far plane (as with 0 to 1 depth planes) nor at
106/// the near plane, as you might expect, but exactly at far/2 (the
107/// near plane setting does not affect the point of minimum accuracy at
108/// all!). However, don't let this fool you into believing the point of
109/// worst precision has simply been moved around--for *any* fixed Δz that is
110/// the minimum amount of depth precision you want over the whole range, and
111/// any near plane, you can set the far plane farther (generally much much
112/// farther!) with reversed clip space than you can with standard clip space
113/// while still getting at least that much depth precision in the worst
114/// case. Nor is this a small worst-case; for many desirable near and far
115/// plane combinations, more than half the visible space will have
116/// completely unusable precision under 0 to 1 depth, while having much better
117/// than needed precision under 1 to 0 depth.
118///
119/// To compute the exact (at least "roughly exact") worst-case accuracy for
120/// floating point depth and a given precision target Δz, for reverse clip
121/// planes (this can be computed for the non-reversed case too, but it's
122/// painful and the values are horrible, so don't bother), we compute
123/// (assuming a finite far plane--see below for details on the infinite
124/// case) the change in the integer representation of the mantissa at z=n/2:
125///
126/// ```text
127/// e = floor(ln(near/(far - near))/ln(2))
128/// db/dz = 2^(2-e) / ((1 / far - 1 / near) * (far)^2)
129/// ```
130///
131/// Then the maximum precision you can safely use to get a change in the
132/// integer representation of the mantissa (assuming 32-bit floating points)
133/// is around:
134///
135/// ```text
136/// abs(2^(-23) / (db/dz)).
137/// ```
138///
139/// In particular, if your worst-case target accuracy over the depth range
140/// is Δz, you should be okay if:
141///
142/// ```text
143/// abs(Δz * (db/dz)) * 2^(23) ≥ 1.
144/// ```
145///
146/// This only accounts for precision of the final floating-point value, so
147/// it's possible that artifacts may be introduced elsewhere during the
148/// computation that reduce precision further; the most famous example of
149/// this is that OpenGL wipes out most of the precision gains by going from
150/// \[-1,1\] to \[0,1\] by letting
151///
152/// ```text
153/// clip space depth = depth * 0.5 + 0.5
154/// ```
155///
156/// which results in huge precision errors by removing nearly all the
157/// floating point values with the most precision (those close to 0).
158/// Fortunately, most such artifacts are absent under the wgpu/DirectX/Metal
159/// depth clip space model, so with any luck remaining depth errors due to
160/// the perspective warp itself should be minimal.
161///
162/// * 0 ≠ 1/far (finite far plane). When this is false, the far plane is at
163/// infinity; this removes the restriction of having a far plane at all, often
164/// with minimal reduction in accuracy for most values in the scene. In fact,
165/// in almost all cases with non-reversed depth planes, it *improves* accuracy
166/// over the finite case for the vast majority of the range; however, you
167/// should be using reversed depth planes, and if you are then there is a
168/// quite natural accuracy vs. distance tradeoff in the infinite case.
169///
170/// When using an infinite far plane, the worst-case accuracy is *always* at
171/// infinity, and gets progressively worse as you get farther away from the
172/// near plane. However, there is a second advantage that may not be
173/// immediately apparent: the perspective warp becomes much simpler,
174/// potentially removing artifacts! Specifically, in the 0 to 1 depth plane
175/// case, the assigned depth value (after perspective division) becomes:
176///
177/// ```text
178/// depth = 1 - near/z
179/// ```
180///
181/// while in the 1 to 0 depth plane case (which you should be using), the
182/// equation is even simpler:
183///
184/// ```text
185/// depth = near/z
186/// ```
187///
188/// In the 1 to 0 case, in particular, you can see that the depth value is
189/// *linear in z in log space.* This lets us compute, for any given target
190/// precision, a *very* simple worst-case upper bound on the maximum
191/// absolute z value for which that precision can be achieved (the upper
192/// bound is tight in some cases, but in others may be conservative):
193///
194/// ```text
195/// db/dz ≥ 1/z
196/// ```
197///
198/// Plugging that into our old formula, we find that we attain the required
199/// precision at least in the range (again, this is for the 1 to 0 infinite
200/// case only!):
201///
202/// ```text
203/// abs(z) ≤ Δz * 2^23
204/// ```
205///
206/// One thing you may notice is that this worst-case bound *does not depend
207/// on the near plane.*This means that (within reason) you can put the near
208/// plane as close as you like and still attain this bound. Of course, the
209/// bound is not completely tight, but it should not be off by more than a
210/// factor of 2 or so (informally proven, not made rigorous yet), so for most
211/// practical purposes you can set the near plane as low as you like in this
212/// case.
213///
214/// * 0 < 1/near (positive near plane--best used when moving *to* left-handed
215/// spaces, as we normally do in OpenGL and DirectX). A use case for *not*
216/// doing this is that it allows moving *from* a left-handed space *to* a
217/// right-handed space in WGPU / DirectX / Metal coordinates; this means that
218/// if matrices were already set up for OpenGL using functions like look_at_rh
219/// that assume right-handed coordinates, we can simply switch these to
220/// look_at_lh and use a right-handed perspective projection with a negative
221/// near plane, to get correct rendering behavior. Details are out of scope
222/// for this comment.
223///
224/// Note that there is one final, very important thing that affects possible
225/// precision--the actual underlying precision of the floating point format at a
226/// particular value! As your z values go up, their precision will shrink, so
227/// if at all possible try to shrink your z values down to the lowest range in
228/// which they can be. Unfortunately, this cannot be part of the perspective
229/// projection itself, because by the time z gets to the projection it is
230/// usually too late for values to still be integers (or coarse-grained powers
231/// of 2). Instead, try to scale down x, y, and z as soon as possible before
232/// submitting them to the GPU, ideally by as large as possible of a power of 2
233/// that works for your use case. Not only will this improve depth precision
234/// and recall, it will also help address other artifacts caused by values far
235/// from z (such as improperly rounded rotations, or improper line equations due
236/// to greedy meshing).
237///
238/// TODO: Consider passing fractions rather than 1/n and 1/f directly, even
239/// though the logic for why it should be okay to pass them directly is probably
240/// sound (they are both valid z values in the range, so gl_FragCoord.w will be
241/// assigned to this, meaning if they are imprecise enough then the whole
242/// calculation will be similarly imprecise).
243///
244/// TODO: Since it's a bit confusing that n and f are not always near and far,
245/// and a negative near plane can (probably) be emulated with simple actions on
246/// the perspective matrix, consider removing this functionality and replacing
247/// our assertion with a single condition: `(1/far) * (1/near) < (1/near)²`.
248pub fn perspective_lh_zo_general<T>(
249 fov_y_radians: T,
250 aspect_ratio: T,
251 inv_n: T,
252 inv_f: T,
253) -> Mat4<T>
254where
255 T: Real + FloatConst + Debug,
256{
257 // Per comments, we only need these two assertions to make sure our calculations
258 // make sense.
259 debug_assert_ne!(
260 inv_n, inv_f,
261 "The near and far plane distances cannot be equal, found: {:?} = {:?}",
262 inv_n, inv_f
263 );
264 debug_assert!(
265 T::zero() <= inv_n * inv_f,
266 "The near and far plane distances must have the same sign, found: {:?} * {:?} < 0",
267 inv_n,
268 inv_f
269 );
270
271 // TODO: Would be nice to separate out the aspect ratio computations.
272 let two = T::one() + T::one();
273 let tan_half_fovy = (fov_y_radians / two).tan();
274 let m00 = T::one() / (aspect_ratio * tan_half_fovy);
275 let m11 = T::one() / tan_half_fovy;
276 let m23 = -T::one() / (inv_n - inv_f);
277 let m22 = inv_n * (-m23);
278 Mat4::new(
279 m00,
280 T::zero(),
281 T::zero(),
282 T::zero(),
283 T::zero(),
284 m11,
285 T::zero(),
286 T::zero(),
287 T::zero(),
288 T::zero(),
289 m22,
290 m23,
291 T::zero(),
292 T::zero(),
293 T::one(),
294 T::zero(),
295 )
296}
297
298/// Same as perspective_lh_zo_general, but for right-handed source spaces.
299pub fn perspective_rh_zo_general<T>(
300 fov_y_radians: T,
301 aspect_ratio: T,
302 inv_n: T,
303 inv_f: T,
304) -> Mat4<T>
305where
306 T: Real + FloatConst + Debug,
307{
308 let mut m = perspective_lh_zo_general(fov_y_radians, aspect_ratio, inv_n, inv_f);
309 m[(2, 2)] = -m[(2, 2)];
310 m[(3, 2)] = -m[(3, 2)];
311 m
312}
313
314impl Camera {
315 /// Create a new `Camera` with default parameters.
316 pub fn new(aspect: f32, mode: CameraMode) -> Self {
317 // Make sure aspect is valid
318 let aspect = if aspect.is_normal() { aspect } else { 1.0 };
319
320 let dist = match mode {
321 CameraMode::ThirdPerson => 10.0,
322 CameraMode::FirstPerson | CameraMode::Freefly => MIN_ZOOM,
323 };
324
325 Self {
326 tgt_focus: Vec3::unit_z() * 10.0,
327 focus: Vec3::unit_z() * 10.0,
328 tgt_ori: Vec3::zero(),
329 ori: Vec3::zero(),
330 tgt_dist: dist,
331 dist,
332 tgt_fov: 1.1,
333 fov: 1.1,
334 tgt_fixate: 1.0,
335 fixate: 1.0,
336 aspect,
337 mode,
338
339 last_time: None,
340
341 dependents: Dependents {
342 view_mat: Mat4::identity(),
343 view_mat_inv: Mat4::identity(),
344 proj_mat: Mat4::identity(),
345 proj_mat_inv: Mat4::identity(),
346 proj_mat_treeculler: Mat4::identity(),
347 cam_pos: Vec3::zero(),
348 cam_dir: Vec3::unit_y(),
349 },
350 frustum: Frustum::from_modelview_projection(Mat4::identity().into_col_arrays()),
351 }
352 }
353
354 /// Compute the transformation matrices (view matrix and projection matrix)
355 /// and position of the camera.
356 pub fn compute_dependents(&mut self, terrain: &TerrainGrid) {
357 self.compute_dependents_full(terrain, |block| block.is_opaque())
358 }
359
360 /// The is_fluid argument should return true for transparent voxels.
361 pub fn compute_dependents_full<V: ReadVol>(
362 &mut self,
363 terrain: &V,
364 is_transparent: fn(&V::Vox) -> bool,
365 ) {
366 span!(_guard, "compute_dependents", "Camera::compute_dependents");
367 // TODO: More intelligent function to decide on which strategy to use
368 if self.tgt_dist < CLIPPING_MODE_RANGE.end {
369 self.compute_dependents_near(terrain, is_transparent)
370 } else {
371 self.compute_dependents_far(terrain, is_transparent)
372 }
373 }
374
375 fn compute_dependents_near<V: ReadVol>(
376 &mut self,
377 terrain: &V,
378 is_transparent: fn(&V::Vox) -> bool,
379 ) {
380 const FRUSTUM_PADDING: [Vec3<f32>; 4] = [
381 Vec3::new(0.0, 0.0, -1.0),
382 Vec3::new(0.0, 0.0, 1.0),
383 Vec3::new(0.0, 0.0, -1.0),
384 Vec3::new(0.0, 0.0, 1.0),
385 ];
386 // Calculate new frustum location as there may have been lerp towards tgt_dist
387 // Without this, there will be camera jumping back and forth in some scenarios
388 // TODO: Optimize and fix clipping still happening if self.dist << self.tgt_dist
389
390 // Use tgt_dist, as otherwise we end up in loop due to dist depending on frustum
391 // and vice versa
392 let local_dependents = self.compute_dependents_helper(self.tgt_dist);
393 let frustum = self.compute_frustum(&local_dependents);
394 let dist = {
395 frustum
396 .points
397 .iter()
398 .take(4)
399 .zip(FRUSTUM_PADDING.iter())
400 .map(|(pos, padding)| {
401 let fwd = self.forward();
402 // TODO: undo once treeculler is vek15.7
403 let transformed = Vec3::new(pos.x, pos.y, pos.z);
404 transformed + 0.6 * (fwd.cross(*padding) + fwd.cross(*padding).cross(fwd))
405 })
406 .chain([(self.focus - self.forward() * (self.dist + 0.5))]) // Padding to behind
407 .map(|pos| {
408 match terrain
409 .ray(self.focus, pos)
410 .ignore_error()
411 .max_iter(500)
412 .until(is_transparent)
413 .cast()
414 {
415 (d, Ok(Some(_))) => f32::min(d, self.tgt_dist),
416 (_, Ok(None)) => self.dist,
417 (_, Err(_)) => self.dist,
418 }
419 .max(0.0)
420 })
421 .reduce(f32::min)
422 .unwrap_or(0.0)
423 };
424
425 // If the camera ends up being too close to the focus point, switch policies.
426 if dist < CLIPPING_MODE_RANGE.start {
427 self.compute_dependents_far(terrain, is_transparent);
428 } else {
429 if self.dist >= dist {
430 self.dist = dist;
431 }
432
433 // Recompute only if needed
434 if (dist - self.tgt_dist).abs() > f32::EPSILON {
435 let dependents = self.compute_dependents_helper(dist);
436 self.frustum = self.compute_frustum(&dependents);
437 self.dependents = dependents;
438 } else {
439 self.dependents = local_dependents;
440 self.frustum = frustum;
441 }
442 }
443 }
444
445 fn compute_dependents_far<V: ReadVol>(
446 &mut self,
447 terrain: &V,
448 is_transparent: fn(&V::Vox) -> bool,
449 ) {
450 let dist = {
451 let (start, end) = (self.focus - self.forward() * self.dist, self.focus);
452
453 match terrain
454 .ray(start, end)
455 .ignore_error()
456 .max_iter(500)
457 .until(|b| !is_transparent(b))
458 .cast()
459 {
460 (d, Ok(Some(_))) => f32::min(self.dist - d - 0.03, self.dist),
461 (_, Ok(None)) => self.dist,
462 (_, Err(_)) => self.dist,
463 }
464 .max(0.0)
465 };
466
467 let dependents = self.compute_dependents_helper(dist);
468 self.frustum = self.compute_frustum(&dependents);
469 self.dependents = dependents;
470 }
471
472 fn compute_dependents_helper(&self, dist: f32) -> Dependents {
473 let view_mat = Mat4::<f32>::identity()
474 * Mat4::translation_3d(-Vec3::unit_z() * dist)
475 * Mat4::rotation_z(self.ori.z)
476 * Mat4::rotation_x(self.ori.y)
477 * Mat4::rotation_y(self.ori.x)
478 * Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x())
479 * Mat4::translation_3d(-self.focus.map(|e| e.fract()));
480 let view_mat_inv: Mat4<f32> = view_mat.inverted();
481
482 let fov = self.get_effective_fov();
483 // NOTE: We reverse the far and near planes to produce an inverted depth
484 // buffer (1 to 0 z planes).
485 let proj_mat =
486 perspective_rh_zo_general(fov, self.aspect, 1.0 / FAR_PLANE, 1.0 / NEAR_PLANE);
487 // For treeculler, we also produce a version without inverted depth.
488 let proj_mat_treeculler =
489 perspective_rh_zo_general(fov, self.aspect, 1.0 / NEAR_PLANE, 1.0 / FAR_PLANE);
490
491 Dependents {
492 view_mat,
493 view_mat_inv,
494 proj_mat,
495 proj_mat_inv: proj_mat.inverted(),
496 proj_mat_treeculler,
497 cam_pos: Vec3::from(view_mat_inv * Vec4::unit_w()),
498 cam_dir: Vec3::from(view_mat_inv * -Vec4::unit_z()),
499 }
500 }
501
502 fn compute_frustum(&mut self, dependents: &Dependents) -> Frustum<f32> {
503 Frustum::from_modelview_projection(
504 (dependents.proj_mat_treeculler
505 * dependents.view_mat
506 * Mat4::translation_3d(-self.focus.map(|e| e.trunc())))
507 .into_col_arrays(),
508 )
509 }
510
511 pub fn frustum(&self) -> &Frustum<f32> { &self.frustum }
512
513 pub fn dependents(&self) -> Dependents { self.dependents }
514
515 /// Rotate the camera about its focus by the given delta, limiting the input
516 /// accordingly.
517 pub fn rotate_by(&mut self, delta: Vec3<f32>) {
518 let delta = delta * self.fixate;
519 // Wrap camera yaw
520 self.tgt_ori.x = (self.tgt_ori.x + delta.x).rem_euclid(2.0 * PI);
521 // Clamp camera pitch to the vertical limits
522 self.tgt_ori.y = (self.tgt_ori.y + delta.y).clamp(-PI / 2.0 + 0.001, PI / 2.0 - 0.001);
523 // Wrap camera roll
524 self.tgt_ori.z = (self.tgt_ori.z + delta.z).rem_euclid(2.0 * PI);
525 }
526
527 /// Set the orientation of the camera about its focus.
528 pub fn set_orientation(&mut self, ori: Vec3<f32>) { self.tgt_ori = clamp_and_modulate(ori); }
529
530 /// Set the orientation of the camera about its focus without lerping.
531 pub fn set_orientation_instant(&mut self, ori: Vec3<f32>) {
532 self.set_orientation(ori);
533 self.ori = self.tgt_ori;
534 }
535
536 /// Zoom the camera by the given delta, limiting the input accordingly.
537 pub fn zoom_by(&mut self, delta: f32, cap: Option<f32>) {
538 if self.mode == CameraMode::ThirdPerson {
539 // Clamp camera dist to the 2 <= x <= infinity range
540 self.tgt_dist = (self.tgt_dist + delta).max(2.0);
541 }
542
543 if let Some(cap) = cap {
544 self.tgt_dist = self.tgt_dist.min(cap);
545 }
546 }
547
548 /// Zoom with the ability to switch between first and third-person mode.
549 ///
550 /// Note that cap > 18237958000000.0 can cause panic due to float overflow
551 pub fn zoom_switch(&mut self, delta: f32, cap: f32, scale: f32) {
552 if delta > 0_f32 || self.mode != CameraMode::FirstPerson {
553 let t = self.tgt_dist + delta;
554 const MIN_THIRD_PERSON: f32 = 2.35;
555 match self.mode {
556 CameraMode::ThirdPerson => {
557 if t < MIN_THIRD_PERSON * scale {
558 self.set_mode(CameraMode::FirstPerson);
559 } else {
560 self.tgt_dist = t;
561 }
562 },
563 CameraMode::FirstPerson => {
564 self.set_mode(CameraMode::ThirdPerson);
565 self.tgt_dist = MIN_THIRD_PERSON * scale;
566 },
567 _ => {},
568 }
569 }
570
571 self.tgt_dist = self.tgt_dist.min(cap);
572 }
573
574 /// Get the distance of the camera from the focus
575 pub fn get_distance(&self) -> f32 { self.dist }
576
577 /// Set the distance of the camera from the focus (i.e., zoom).
578 pub fn set_distance(&mut self, dist: f32) { self.tgt_dist = dist; }
579
580 pub fn update(&mut self, time: f64, dt: f32, smoothing_enabled: bool) {
581 // This is horribly frame time dependent, but so is most of the game
582 let delta = self.last_time.replace(time).map_or(0.0, |t| time - t);
583 if (self.dist - self.tgt_dist).abs() > 0.01 {
584 self.dist = Lerp::lerp(
585 self.dist,
586 self.tgt_dist,
587 0.65 * (delta as f32) / self.interp_time(),
588 );
589 }
590
591 if (self.fov - self.tgt_fov).abs() > 0.01 {
592 self.fov = Lerp::lerp(
593 self.fov,
594 self.tgt_fov,
595 0.65 * (delta as f32) / self.interp_time(),
596 );
597 }
598
599 if (self.fixate - self.tgt_fixate).abs() > 0.01 {
600 self.fixate = Lerp::lerp(
601 self.fixate,
602 self.tgt_fixate,
603 0.65 * (delta as f32) / self.interp_time(),
604 );
605 }
606
607 if (self.focus - self.tgt_focus).magnitude_squared() > 0.001 {
608 let lerped_focus = Lerp::lerp(
609 self.focus,
610 self.tgt_focus,
611 (delta as f32) / self.interp_time()
612 * if matches!(self.mode, CameraMode::FirstPerson) {
613 2.0
614 } else {
615 1.0
616 },
617 );
618
619 self.focus.x = lerped_focus.x;
620 self.focus.y = lerped_focus.y;
621
622 // Always lerp in z
623 self.focus.z = lerped_focus.z;
624 }
625
626 let ori = if smoothing_enabled {
627 Vec3::new(
628 lerp_angle(self.ori.x, self.tgt_ori.x, LERP_ORI_RATE * dt),
629 Lerp::lerp(self.ori.y, self.tgt_ori.y, LERP_ORI_RATE * dt),
630 lerp_angle(self.ori.z, self.tgt_ori.z, LERP_ORI_RATE * dt),
631 )
632 } else {
633 self.tgt_ori
634 };
635 self.ori = clamp_and_modulate(ori);
636 }
637
638 pub fn lerp_toward(&mut self, tgt_ori: Vec3<f32>, dt: f32, rate: f32) {
639 self.ori = Vec3::new(
640 lerp_angle(self.ori.x, tgt_ori.x, rate * dt),
641 Lerp::lerp(self.ori.y, tgt_ori.y, rate * dt),
642 lerp_angle(self.ori.z, tgt_ori.z, rate * dt),
643 );
644 self.tgt_ori = self.ori;
645 }
646
647 pub fn interp_time(&self) -> f32 {
648 match self.mode {
649 CameraMode::FirstPerson => FIRST_PERSON_INTERP_TIME,
650 CameraMode::ThirdPerson => THIRD_PERSON_INTERP_TIME,
651 CameraMode::Freefly => FREEFLY_INTERP_TIME,
652 }
653 }
654
655 /// Get the focus position of the camera.
656 pub fn get_focus_pos(&self) -> Vec3<f32> { self.focus }
657
658 /// Set the focus position of the camera.
659 pub fn set_focus_pos(&mut self, focus: Vec3<f32>) { self.tgt_focus = focus; }
660
661 /// Set the focus position of the camera, without lerping.
662 pub fn force_focus_pos(&mut self, focus: Vec3<f32>) {
663 self.tgt_focus = focus;
664 self.focus = focus;
665 }
666
667 /// Set the focus position of the camera, without lerping.
668 pub fn force_xy_focus_pos(&mut self, focus: Vec3<f32>) {
669 self.tgt_focus = focus;
670 self.focus = focus.xy().with_z(self.focus.z);
671 }
672
673 /// Get the aspect ratio of the camera.
674 pub fn get_aspect_ratio(&self) -> f32 { self.aspect }
675
676 /// Set the aspect ratio of the camera.
677 pub fn set_aspect_ratio(&mut self, aspect: f32) {
678 self.aspect = if aspect.is_normal() { aspect } else { 1.0 };
679 }
680
681 /// Get the orientation of the camera.
682 pub fn get_orientation(&self) -> Vec3<f32> { self.ori }
683
684 /// Get the orientation that the camera is moving toward.
685 pub fn get_tgt_orientation(&self) -> Vec3<f32> { self.tgt_ori }
686
687 /// Get the field of view of the camera in radians, taking into account
688 /// fixation.
689 pub fn get_effective_fov(&self) -> f32 { self.fov * self.fixate }
690
691 // /// Get the field of view of the camera in radians.
692 // pub fn get_fov(&self) -> f32 { self.fov }
693
694 /// Set the field of view of the camera in radians.
695 pub fn set_fov(&mut self, fov: f32) { self.tgt_fov = fov; }
696
697 /// Set the 'fixation' proportion, allowing the camera to focus in with
698 /// precise aiming. Fixation is applied on top of the regular FoV.
699 pub fn set_fixate(&mut self, fixate: f32) { self.tgt_fixate = fixate; }
700
701 /// Set the FOV in degrees
702 pub fn set_fov_deg(&mut self, fov: u16) {
703 //Magic value comes from pi/180; no use recalculating.
704 self.set_fov((fov as f32) * 0.01745329)
705 }
706
707 /// Set the mode of the camera.
708 pub fn set_mode(&mut self, mode: CameraMode) {
709 if self.mode != mode {
710 self.mode = mode;
711 match self.mode {
712 CameraMode::ThirdPerson => {
713 self.zoom_by(5.0, None);
714 },
715 CameraMode::FirstPerson => {
716 self.set_distance(MIN_ZOOM);
717 },
718 CameraMode::Freefly => {
719 self.set_distance(MIN_ZOOM);
720 },
721 }
722 }
723 }
724
725 /// Get the mode of the camera
726 pub fn get_mode(&self) -> CameraMode {
727 // Perform a bit of a trick... don't report first-person until the camera has
728 // lerped close enough to the player.
729 match self.mode {
730 CameraMode::FirstPerson if self.dist < 0.5 => CameraMode::FirstPerson,
731 CameraMode::FirstPerson => CameraMode::ThirdPerson,
732 mode => mode,
733 }
734 }
735
736 /// Cycle the camera to its next valid mode. If is_admin is false then only
737 /// modes which are accessible without admin access will be cycled to.
738 pub fn next_mode(&mut self, is_admin: bool, has_target: bool) {
739 if has_target {
740 self.set_mode(match self.mode {
741 CameraMode::ThirdPerson => CameraMode::FirstPerson,
742 CameraMode::FirstPerson => {
743 if is_admin {
744 CameraMode::Freefly
745 } else {
746 CameraMode::ThirdPerson
747 }
748 },
749 CameraMode::Freefly => CameraMode::ThirdPerson,
750 });
751 } else {
752 self.set_mode(CameraMode::Freefly);
753 }
754 }
755
756 /// Return a unit vector in the forward direction for the current camera
757 /// orientation
758 pub fn forward(&self) -> Vec3<f32> {
759 Vec3::new(
760 f32::sin(self.ori.x) * f32::cos(self.ori.y),
761 f32::cos(self.ori.x) * f32::cos(self.ori.y),
762 -f32::sin(self.ori.y),
763 )
764 }
765
766 /// Return a unit vector in the right direction for the current camera
767 /// orientation
768 pub fn right(&self) -> Vec3<f32> {
769 const UP: Vec3<f32> = Vec3::new(0.0, 0.0, 1.0);
770 self.forward().cross(UP).normalized()
771 }
772
773 /// Return a unit vector in the forward direction on the XY plane for
774 /// the current camera orientation
775 pub fn forward_xy(&self) -> Vec2<f32> { Vec2::new(f32::sin(self.ori.x), f32::cos(self.ori.x)) }
776
777 /// Return a unit vector in the right direction on the XY plane for
778 /// the current camera orientation
779 pub fn right_xy(&self) -> Vec2<f32> { Vec2::new(f32::cos(self.ori.x), -f32::sin(self.ori.x)) }
780
781 pub fn get_pos_with_focus(&self) -> Vec3<f32> {
782 let focus_off = self.get_focus_pos().map(f32::trunc);
783 self.dependents().cam_pos + focus_off
784 }
785}
786
787fn lerp_angle(a: f32, b: f32, rate: f32) -> f32 {
788 let offs = [-2.0 * PI, 0.0, 2.0 * PI]
789 .iter()
790 .min_by_key(|offs: &&f32| ((a - (b + *offs)).abs() * 1000.0) as i32)
791 .unwrap();
792 Lerp::lerp(a, b + *offs, rate)
793}