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
// Camera-shot preset schema.
use alloc::string::String;
/// A reusable [Camera3D](#camera3d) preset: reference it from a
/// [Scene](#scene)'s `camera_shot`, or use it standalone.
///
/// Used standalone, it expands into a [Camera3D](#camera3d) with the same
/// parameters.
///
/// **Examples**
///
/// With Scenes: camera switches per scene (declared on each Scene):
///
/// From library preset (standalone, replaces Camera3D):
///
/// ```rust
/// # use concinnity_asset::CameraShot;
/// CameraShot {
/// fov_y_degrees: 80.0,
/// position: [0.0, 1.75, 8.0],
/// yaw: 3.14,
/// ..Default::default()
/// };
/// ```
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct CameraShot {
/// Name of a built-in or file-backed preset (e.g. "shot_eye_level").
/// Preset values are used as defaults; any inline fields override them.
pub preset: String,
/// Vertical field of view in degrees.
pub fov_y_degrees: f32,
/// Near clip plane distance in world units.
pub near: f32,
/// Far clip plane distance in world units.
pub far: f32,
/// World-space camera position.
pub position: [f32; 3],
/// Yaw rotation in radians (Y-axis, applied first).
pub yaw: f32,
/// Pitch rotation in radians (X-axis, applied second).
pub pitch: f32,
}
impl Default for CameraShot {
fn default() -> Self {
Self {
preset: String::new(),
fov_y_degrees: 75.0,
near: 0.05,
far: 200.0,
position: [0.0, 0.0, 0.0],
yaw: 0.0,
pitch: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_the_camera_they_configure() {
// A shot overwrites a Camera3D's framing, so an unset field has to mean
// the same thing on both.
let s = CameraShot::default();
assert!(s.preset.is_empty());
assert_eq!(s.fov_y_degrees, 75.0);
assert_eq!((s.near, s.far), (0.05, 200.0));
assert_eq!(s.position, [0.0, 0.0, 0.0]);
assert_eq!((s.yaw, s.pitch), (0.0, 0.0));
}
#[test]
fn a_named_preset_parses_and_round_trips_through_postcard() {
let s: CameraShot = serde_json::from_str(
r#"{"preset":"establishing","fov_y_degrees":40,"position":[0,3,8],"yaw":1.5}"#,
)
.unwrap();
assert_eq!(s.preset, "establishing");
assert_eq!(s.fov_y_degrees, 40.0);
assert_eq!(s.position, [0.0, 3.0, 8.0]);
assert_eq!(s.yaw, 1.5);
let bytes = postcard::to_allocvec(&s).unwrap();
let back: CameraShot = postcard::from_bytes(&bytes).unwrap();
assert_eq!(back.preset, "establishing");
assert_eq!(back.far, 200.0);
}
}