1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! Per-camera parameter store.
//!
//! A lights-shaped slotmap of authorable camera parameters (projection, clip
//! planes, depth-of-field). This is the animation/editor-facing store the
//! `AnimationTarget::Camera` channel drives — it holds the *parameters*, not
//! the per-frame view/projection matrices (those live in
//! [`crate::camera`]). Mirrors the shape of [`crate::lights::Lights`].
use slotmap::{new_key_type, SlotMap};
new_key_type! {
/// Opaque key for a camera in the [`Cameras`] store.
pub struct CameraKey;
}
/// Projection parameters for a camera. Mirrors the two projection kinds the
/// renderer supports; `AnimationTarget::Camera { param: FovY }` only touches
/// the perspective arm (it's a no-op on an orthographic camera).
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CameraProjectionParams {
/// Perspective projection driven by vertical field-of-view (radians).
Perspective { fov_y_rad: f32 },
/// Orthographic projection driven by half the view-volume height.
Orthographic { half_height: f32 },
}
/// Authorable per-camera parameters.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CameraParams {
/// Projection kind + its driving parameter.
pub projection: CameraProjectionParams,
/// Near clip plane.
pub near: f32,
/// Far clip plane.
pub far: f32,
/// Depth-of-field aperture (f-stop). Mirrors `CameraMatrices.aperture`;
/// default `5.6`.
pub aperture: f32,
/// Depth-of-field focus distance. Default `10.0`.
pub focus_distance: f32,
}
/// A slotmap of [`CameraParams`], keyed by [`CameraKey`].
#[derive(Debug, Clone, Default)]
pub struct Cameras {
store: SlotMap<CameraKey, CameraParams>,
}
impl Cameras {
/// Creates an empty camera store.
pub fn new() -> Self {
Self::default()
}
/// Inserts a camera and returns its key.
pub fn insert(&mut self, params: CameraParams) -> CameraKey {
self.store.insert(params)
}
/// Removes a camera, returning its parameters if it existed.
pub fn remove(&mut self, key: CameraKey) -> Option<CameraParams> {
self.store.remove(key)
}
/// Returns a camera's parameters, or `None` if the key is unknown.
pub fn get(&self, key: CameraKey) -> Option<&CameraParams> {
self.store.get(key)
}
/// Returns true if the store contains the given key.
pub fn contains(&self, key: CameraKey) -> bool {
self.store.contains_key(key)
}
/// Mutates a camera in place. Returns true if the key existed (and `f`
/// ran), false otherwise.
pub fn update(&mut self, key: CameraKey, f: impl FnOnce(&mut CameraParams)) -> bool {
if let Some(params) = self.store.get_mut(key) {
f(params);
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn params() -> CameraParams {
CameraParams {
projection: CameraProjectionParams::Perspective { fov_y_rad: 1.0 },
near: 0.1,
far: 100.0,
aperture: 5.6,
focus_distance: 10.0,
}
}
#[test]
fn insert_get_round_trip() {
let mut cameras = Cameras::new();
let key = cameras.insert(params());
assert!(cameras.contains(key));
assert_eq!(cameras.get(key), Some(¶ms()));
}
#[test]
fn update_mutates_and_reports_existence() {
let mut cameras = Cameras::new();
let key = cameras.insert(params());
let ran = cameras.update(key, |p| p.near = 0.5);
assert!(ran);
assert_eq!(cameras.get(key).unwrap().near, 0.5);
}
#[test]
fn update_returns_false_for_stale_key() {
let mut cameras = Cameras::new();
let key = cameras.insert(params());
let removed = cameras.remove(key);
assert_eq!(removed, Some(params()));
// key is now stale
let ran = cameras.update(key, |p| p.near = 0.5);
assert!(!ran);
assert!(!cameras.contains(key));
assert!(cameras.get(key).is_none());
}
#[test]
fn remove_returns_none_for_stale_key() {
let mut cameras = Cameras::new();
let key = cameras.insert(params());
assert!(cameras.remove(key).is_some());
assert!(cameras.remove(key).is_none());
}
}