oxihuman_export/
camera_shake_export.rs1#![allow(dead_code)]
4
5use std::f32::consts::TAU;
8
9#[allow(dead_code)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ShakeChannel {
13 TranslateX,
14 TranslateY,
15 TranslateZ,
16 RotateX,
17 RotateY,
18 RotateZ,
19}
20
21#[allow(dead_code)]
23#[derive(Debug, Clone)]
24pub struct ShakeKeyframe {
25 pub time: f32,
26 pub amplitude: f32,
27 pub frequency: f32,
28 pub channel: ShakeChannel,
29}
30
31#[allow(dead_code)]
33#[derive(Debug, Clone, Default)]
34pub struct CameraShakeExport {
35 pub keyframes: Vec<ShakeKeyframe>,
36 pub duration: f32,
37 pub decay: f32,
38}
39
40#[allow(dead_code)]
42pub fn new_camera_shake(duration: f32, decay: f32) -> CameraShakeExport {
43 CameraShakeExport {
44 keyframes: vec![],
45 duration,
46 decay,
47 }
48}
49
50#[allow(dead_code)]
52pub fn add_shake_keyframe(
53 export: &mut CameraShakeExport,
54 time: f32,
55 amplitude: f32,
56 frequency: f32,
57 channel: ShakeChannel,
58) {
59 export.keyframes.push(ShakeKeyframe {
60 time,
61 amplitude,
62 frequency,
63 channel,
64 });
65}
66
67#[allow(dead_code)]
69pub fn evaluate_shake(export: &CameraShakeExport, time: f32, channel: ShakeChannel) -> f32 {
70 export
71 .keyframes
72 .iter()
73 .filter(|k| k.channel == channel)
74 .map(|k| {
75 let envelope = (-export.decay * (time - k.time).max(0.0)).exp();
76 k.amplitude * (TAU * k.frequency * time).sin() * envelope
77 })
78 .sum()
79}
80
81#[allow(dead_code)]
83pub fn channel_keyframe_count(export: &CameraShakeExport, channel: ShakeChannel) -> usize {
84 export
85 .keyframes
86 .iter()
87 .filter(|k| k.channel == channel)
88 .count()
89}
90
91#[allow(dead_code)]
93pub fn peak_amplitude(export: &CameraShakeExport) -> f32 {
94 export
95 .keyframes
96 .iter()
97 .map(|k| k.amplitude.abs())
98 .fold(0.0_f32, f32::max)
99}
100
101#[allow(dead_code)]
103pub fn serialise_shake(export: &CameraShakeExport) -> Vec<f32> {
104 let mut buf = vec![export.duration, export.decay];
105 for k in &export.keyframes {
106 buf.push(k.time);
107 buf.push(k.amplitude);
108 buf.push(k.frequency);
109 }
110 buf
111}
112
113#[allow(dead_code)]
115pub fn is_decayed(export: &CameraShakeExport, time: f32, threshold: f32) -> bool {
116 if export.keyframes.is_empty() {
117 return true;
118 }
119 let max_env = (-export.decay * time).exp();
120 max_env * peak_amplitude(export) < threshold
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 fn basic_shake() -> CameraShakeExport {
128 let mut s = new_camera_shake(2.0, 1.0);
129 add_shake_keyframe(&mut s, 0.0, 0.5, 10.0, ShakeChannel::TranslateX);
130 s
131 }
132
133 #[test]
134 fn test_new_camera_shake() {
135 let s = new_camera_shake(1.5, 0.5);
136 assert!((s.duration - 1.5).abs() < 1e-6);
137 }
138
139 #[test]
140 fn test_add_shake_keyframe() {
141 let s = basic_shake();
142 assert_eq!(s.keyframes.len(), 1);
143 }
144
145 #[test]
146 fn test_channel_count() {
147 let s = basic_shake();
148 assert_eq!(channel_keyframe_count(&s, ShakeChannel::TranslateX), 1);
149 assert_eq!(channel_keyframe_count(&s, ShakeChannel::TranslateY), 0);
150 }
151
152 #[test]
153 fn test_peak_amplitude() {
154 let s = basic_shake();
155 assert!((peak_amplitude(&s) - 0.5).abs() < 1e-6);
156 }
157
158 #[test]
159 fn test_evaluate_zero_time() {
160 let s = basic_shake();
161 let v = evaluate_shake(&s, 0.0, ShakeChannel::TranslateX);
162 assert!(v.abs() < 1e-5); }
164
165 #[test]
166 fn test_is_decayed_far_future() {
167 let s = basic_shake();
168 assert!(is_decayed(&s, 100.0, 0.01));
169 }
170
171 #[test]
172 fn test_is_not_decayed_at_start() {
173 let s = basic_shake();
174 assert!(!is_decayed(&s, 0.0, 0.1));
175 }
176
177 #[test]
178 fn test_serialise_length() {
179 let s = basic_shake();
180 let buf = serialise_shake(&s);
181 assert_eq!(buf.len(), 2 + 3); }
183
184 #[test]
185 fn test_serialise_empty() {
186 let s = new_camera_shake(1.0, 1.0);
187 assert_eq!(serialise_shake(&s).len(), 2);
188 }
189}