1use bytemuck::{Pod, Zeroable};
5use glam::{Mat4, Vec2};
6
7const SHAKE_Y_FREQUENCY_RATIO: f32 = 1.3;
10
11#[derive(Debug, Clone)]
13pub struct ScreenShake {
14 pub intensity: f32,
15 pub duration: f32,
16 pub frequency: f32,
17 pub decay: f32,
18 pub elapsed: f32,
19}
20
21impl ScreenShake {
22 pub fn new(intensity: f32, duration: f32) -> Self {
23 Self {
24 intensity,
25 duration,
26 frequency: 40.0,
27 decay: 8.0,
28 elapsed: 0.0,
29 }
30 }
31
32 pub fn sample(&self) -> Vec2 {
34 if self.elapsed >= self.duration {
35 return Vec2::ZERO;
36 }
37 let t = self.elapsed;
38 let envelope = self.intensity * (-self.decay * t).exp();
39 Vec2::new(
40 envelope * (self.frequency * t).sin(),
41 envelope * (self.frequency * t * SHAKE_Y_FREQUENCY_RATIO).cos(),
42 )
43 }
44
45 pub fn update(&mut self, dt: f32) -> bool {
47 self.elapsed += dt;
48 self.elapsed < self.duration
49 }
50
51 pub fn is_active(&self) -> bool {
52 self.elapsed < self.duration
53 }
54}
55
56#[repr(C)]
59#[derive(Copy, Clone, Debug, Pod, Zeroable)]
60pub struct CameraUniform {
61 pub view_proj: [[f32; 4]; 4],
62}
63
64impl CameraUniform {
66 pub fn new(view_proj: Mat4) -> Self {
67 Self {
68 view_proj: view_proj.to_cols_array_2d(),
69 }
70 }
71}
72
73pub struct Camera {
76 pub width: f32,
78 pub height: f32,
79
80 pub offset_x: f32,
82 pub offset_y: f32,
83
84 pub shakes: Vec<ScreenShake>,
86
87 view_proj: Mat4,
89 uniform: CameraUniform,
90}
91
92impl Camera {
93 pub fn new(width: f32, height: f32) -> Self {
96 let view_proj = Self::build_projection(width, height, 0.0, 0.0);
97 let uniform = CameraUniform::new(view_proj);
98
99 Self {
100 width,
101 height,
102 offset_x: 0.0,
103 offset_y: 0.0,
104 shakes: Vec::new(),
105 view_proj,
106 uniform,
107 }
108 }
109
110 pub fn resize(&mut self, width: f32, height: f32) {
113 self.width = width;
114 self.height = height;
115 let shake = self.total_shake();
116 self.view_proj = Self::build_projection(
117 width,
118 height,
119 self.offset_x + shake.x,
120 self.offset_y + shake.y,
121 );
122 self.uniform = CameraUniform::new(self.view_proj);
123 }
124
125 pub fn set_offset(&mut self, offset_x: f32, offset_y: f32) {
128 self.offset_x = offset_x;
129 self.offset_y = offset_y;
130 let shake = self.total_shake();
131 self.view_proj = Self::build_projection(
132 self.width,
133 self.height,
134 self.offset_x + shake.x,
135 self.offset_y + shake.y,
136 );
137 self.uniform = CameraUniform::new(self.view_proj);
138 }
139
140 pub fn add_shake(&mut self, intensity: f32, duration: f32) {
142 self.shakes.push(ScreenShake::new(intensity, duration));
143 }
144
145 pub fn update_shakes(&mut self, dt: f32) {
147 for shake in &mut self.shakes {
148 shake.update(dt);
149 }
150 self.shakes.retain(|s| s.is_active());
151 }
152
153 fn total_shake(&self) -> Vec2 {
155 self.shakes.iter().map(|s| s.sample()).sum()
156 }
157
158 pub fn uniform(&self) -> &CameraUniform {
161 &self.uniform
162 }
163
164 fn build_projection(width: f32, height: f32, offset_x: f32, offset_y: f32) -> Mat4 {
168 Mat4::orthographic_rh(
169 offset_x,
170 width + offset_x,
171 height + offset_y,
172 offset_y,
173 -1.0,
174 1.0,
175 )
176 }
177}