1use std::sync::atomic::{AtomicU32, Ordering};
2
3use crate::*;
4
5pub static MAIN_CAMERA: Lazy<AtomicRefCell<MainCamera>> =
6 Lazy::new(|| AtomicRefCell::new(MainCamera::default()));
7
8pub const LINE_W: f32 = 2.0;
9
10pub static CAMERA_BOUNDS: AtomicCell<Rect> = AtomicCell::new(Rect {
11 center: Vec2::ZERO,
12 size: Vec2 { x: 30.0, y: 30.0 },
13});
14
15static PX: AtomicU32 =
16 AtomicU32::new(unsafe { std::mem::transmute(0.0347f32) });
17
18pub fn set_px(value: f32) {
19 PX.store(value.to_bits(), Ordering::SeqCst);
20}
21
22pub fn px() -> f32 {
23 f32::from_bits(PX.load(Ordering::SeqCst))
24}
25
26pub fn mouse_screen() -> Vec2 {
27 let pos = GLOBAL_STATE.borrow().mouse_position;
28 Vec2::new(pos.x, pos.y)
29}
30
31pub fn mouse_world() -> Vec2 {
32 GLOBAL_STATE.borrow().mouse_world
33}
34
35pub fn screen_width() -> f32 {
36 GLOBAL_STATE.borrow().screen_size.x
37}
38
39pub fn aspect_ratio() -> f32 {
40 MAIN_CAMERA.borrow().aspect_ratio
41}
42
43pub fn screen_height() -> f32 {
44 GLOBAL_STATE.borrow().screen_size.y
45}
46
47pub fn mouse_input_this_frame() -> bool {
49 GLOBAL_STATE.borrow().mouse_input_this_frame
50}
51
52pub fn mouse_moved_this_frame() -> bool {
54 GLOBAL_STATE.borrow().mouse_moved_this_frame
55}
56
57pub fn screenshake(timer: f32, amount: f32) {
58 let mut camera = main_camera_mut();
59
60 camera.shake_timer = timer;
61 camera.shake_amount = amount;
62}
63
64pub fn main_camera() -> AtomicRef<'static, MainCamera> {
65 MAIN_CAMERA.borrow()
66}
67
68pub fn main_camera_mut() -> AtomicRefMut<'static, MainCamera> {
69 MAIN_CAMERA.borrow_mut()
70}
71
72pub fn set_main_camera_zoom(zoom: f32) {
73 MAIN_CAMERA.borrow_mut().zoom = zoom;
74}
75
76pub fn egui_scale_factor() -> f32 {
77 GLOBAL_STATE.borrow().egui_scale_factor
78}
79
80pub fn world_to_gl_screen(position: Vec2) -> Vec2 {
81 let mut screen = world_to_screen(position);
82 screen.y = screen_height() - screen.y;
83 screen
84}
85
86pub fn world_to_screen(position: Vec2) -> Vec2 {
87 main_camera().world_to_screen(position)
88}
89
90pub fn screen_to_world(position: Vec2) -> Vec2 {
91 main_camera().screen_to_world(position)
92}
93
94pub struct DampedSpring {
96 pub value: f32,
97 pub target: f32,
98 pub velocity: f32,
99 pub damping: f32,
100}
101
102impl DampedSpring {
103 pub fn new(target: f32, damping: f32) -> DampedSpring {
104 DampedSpring { value: target, target, velocity: 0.0, damping }
105 }
106
107 pub fn update(&mut self) {
108 let diff = self.target - self.value;
109 self.velocity += diff * self.damping;
110 self.value += self.velocity;
111 self.velocity *= 0.9; }
113}
114
115#[derive(Copy, Clone)]
116struct HistoryStackValue {
117 pub center: Vec2,
118 pub desired_zoom: f32,
119 pub zoom: f32,
120}
121
122pub type CameraMatrixFn =
123 Box<dyn (Fn(&MainCamera, Vec2) -> Mat4) + Send + Sync + 'static>;
124
125pub struct MainCamera {
126 pub shake_timer: f32,
128 pub shake_amount: f32,
130
131 pub recoil: f32,
132
133 pub center: Vec2,
136 pub target: Option<Vec2>,
139
140 pub smoothing_speed: f32,
142 pub deadzone_width: f32,
144 pub deadzone_height: f32,
146
147 pub aspect_ratio: f32,
148
149 pub zoom: f32,
150 pub desired_zoom: f32,
151
152 pub matrix_fn: Option<CameraMatrixFn>,
158 pub use_matrix_fn: bool,
161
162 history_stack: Vec<HistoryStackValue>,
163}
164
165impl Default for MainCamera {
166 fn default() -> Self {
167 Self::new(Vec2::new(0.0, 0.0), 30.0)
168 }
169}
170
171impl MainCamera {
172 pub fn new(center: Vec2, zoom: f32) -> Self {
173 Self {
174 shake_timer: 0.0,
175 shake_amount: 0.0,
176
177 recoil: 0.0,
178
179 center,
180 target: None,
181
182 deadzone_width: 3.0,
183 deadzone_height: 2.0,
184
185 smoothing_speed: 4.0,
186
187 aspect_ratio: 1.0,
188
189 zoom,
190 desired_zoom: zoom,
191
192 history_stack: Vec::new(),
193
194 matrix_fn: None,
195 use_matrix_fn: true,
196 }
197 }
198
199 pub fn update(&mut self, delta: f32) {
200 self.shake_timer -= delta;
201 self.shake_timer = self.shake_timer.max(0.0);
202 self.recoil = (self.recoil - delta).max(0.0);
203
204 set_px(self.zoom / screen_width());
205
206 if let Some(player_position) = self.target {
207 let deadzone_hw = self.deadzone_width / 2.0;
208 let deadzone_hh = self.deadzone_height / 2.0;
209
210 let ox = player_position.x - self.center.x;
211 let oy = player_position.y - self.center.y;
212
213 let dx = ox.abs() - deadzone_hw;
214 let dy = oy.abs() - deadzone_hh;
215
216 let mut offset = Vec2::ZERO;
217
218 if dx > 0.0 {
219 offset.x = dx * ox.signum();
220 }
221
222 if dy > 0.0 {
223 offset.y = dy * oy.signum();
224 }
225
226 self.center += offset * delta * self.smoothing_speed;
227 self.center = player_position;
228 }
229
230 CAMERA_BOUNDS
231 .store(Rect { center: self.center, size: self.world_viewport() });
232 }
233
234 pub fn push_center(&mut self, new_center: Vec2, new_zoom: f32) {
235 self.history_stack.push(HistoryStackValue {
236 center: self.center,
237 desired_zoom: self.desired_zoom,
238 zoom: self.zoom,
239 });
240
241 self.center = new_center;
242
243 self.desired_zoom = new_zoom;
244 self.zoom = new_zoom;
245 }
246
247 pub fn pop_center(&mut self) {
248 if let Some(item) = self.history_stack.pop() {
249 self.center = item.center;
250 self.zoom = item.zoom;
251 self.desired_zoom = item.desired_zoom;
252 }
253 }
254
255 pub fn bump_recoil(&mut self, amount: f32) {
256 self.recoil = (self.recoil + amount).clamp(0.0, 1.0);
257 }
258
259 pub fn world_viewport(&self) -> Vec2 {
260 vec2(self.zoom, self.zoom / self.aspect_ratio)
261 }
262
263 pub fn screen_top_left(&self) -> Vec2 {
264 let world_viewport = self.world_viewport();
265 self.center + vec2(-world_viewport.x, world_viewport.y) / 2.0
266 }
267
268 pub fn screen_top_right(&self) -> Vec2 {
269 let world_viewport = self.world_viewport();
270 self.center + vec2(world_viewport.x, world_viewport.y) / 2.0
271 }
272
273 pub fn shake(&mut self, amount: f32, time: f32) {
274 self.shake_amount = amount;
275 self.shake_timer = time;
276 }
277
278 pub fn current_shake(&self) -> f32 {
279 self.shake_amount * self.shake_timer.clamp(0.0, 1.0)
280 }
281
282 pub fn build_view_projection_matrix(&self) -> Mat4 {
283 let hx = self.zoom / 2.0;
284 let hy = self.zoom / 2.0 / self.aspect_ratio;
285
286 let range = 1000.0;
287
288 const SHAKE: f32 = 0.2;
289
290 let (sx, sy) = if self.shake_timer > 0.0 {
291 let off = random_around(Vec2::ZERO, 0.0, SHAKE * self.shake_amount);
292 (off.x, off.y)
293 } else {
294 (0.0, 0.0)
295 };
296
297 let center = self.center + vec2(sx, sy);
298
299 let ortho_camera = Mat4::orthographic_rh(
300 center.x - hx,
301 center.x + hx,
302 center.y - hy,
303 center.y + hy,
304 -range,
305 range,
306 );
307
308 if let Some(matrix_fn) = self.matrix_fn.as_ref() {
309 if self.use_matrix_fn {
310 matrix_fn(self, center)
311 } else {
312 ortho_camera
313 }
314 } else {
315 ortho_camera
316 }
317 }
318
319 pub fn screen_to_world(&self, position: Vec2) -> Vec2 {
320 let viewport = self.world_viewport();
321 let camera_center = self.center;
322
323 let state = GLOBAL_STATE.borrow();
324
325 let normalized = position / state.screen_size;
326 let normalized = vec2(normalized.x, 1.0 - normalized.y);
327
328 let world_unoffset = (normalized - 0.5) * viewport;
329
330 world_unoffset + camera_center
331 }
332
333 pub fn world_to_screen(&self, position: Vec2) -> Vec2 {
334 let viewport = self.world_viewport();
335
336 let position = position - self.center;
337
338 let state = GLOBAL_STATE.borrow();
339
340 let moved = position + viewport / 2.0;
341 let normalized = moved / viewport;
342 let normalized = vec2(normalized.x, 1.0 - normalized.y);
343
344 normalized * state.screen_size
345 }
350
351 pub fn world_to_render_px(
352 &self,
353 position: Vec2,
354 render_scale: f32,
355 ) -> IVec2 {
356 let px = self.world_to_screen(position).as_ivec2();
357 (px.as_vec2() * render_scale).as_ivec2()
358 }
359}
360
361#[derive(Copy, Clone, Debug)]
362pub enum Position {
363 World { x: f32, y: f32 },
364 Screen { x: ScreenVal, y: ScreenVal },
365}
366
367impl Position {
368 pub fn world(x: f32, y: f32) -> Self {
369 Self::World { x, y }
370 }
371
372 pub fn screen(x: ScreenVal, y: ScreenVal) -> Self {
373 Self::Screen { x, y }
374 }
375
376 pub fn screen_px(x: f32, y: f32) -> Self {
377 Self::Screen { x: ScreenVal::Px(x), y: ScreenVal::Px(y) }
378 }
379
380 pub fn screen_percent(x: f32, y: f32) -> Self {
382 Self::Screen { x: ScreenVal::Percent(x), y: ScreenVal::Percent(y) }
383 }
384
385 pub fn vec2(self) -> Vec2 {
386 match self {
387 Position::World { x, y } => vec2(x, y),
388 Position::Screen { x, y } => vec2(x.to_f32(), y.to_f32()),
389 }
390 }
391
392 pub fn to_world(self) -> Vec2 {
393 match self {
394 Position::World { x, y } => vec2(x, y),
395 Position::Screen { x, y } => {
396 screen_to_world(vec2(
397 x.to_px(screen_width()),
398 y.to_px(screen_height()),
399 ))
400 }
401 }
402 }
403
404 pub fn to_screen(self) -> Vec2 {
405 match self {
406 Position::World { x, y } => world_to_screen(vec2(x, y)),
407 Position::Screen { x, y } => {
408 vec2(x.to_px(screen_width()), y.to_px(screen_height()))
409 }
410 }
411 }
412}
413
414#[derive(Copy, Clone, Debug)]
415pub enum ScreenVal {
416 Px(f32),
417 Percent(f32),
418}
419
420impl ScreenVal {
421 pub fn to_f32(self) -> f32 {
422 match self {
423 ScreenVal::Px(val) => val,
424 ScreenVal::Percent(val) => val,
425 }
426 }
427
428 pub fn to_px(self, screen_dim: f32) -> f32 {
429 match self {
430 ScreenVal::Px(val) => val,
431 ScreenVal::Percent(val) => screen_dim * val,
432 }
433 }
434}
435
436#[derive(Copy, Clone, Debug)]
438pub enum Value {
439 World(f32),
440 Px(f32),
441 Percent(f32),
442}
443
444#[derive(Copy, Clone, Debug)]
445pub enum Axis {
446 X,
447 Y,
448}
449
450impl Value {
451 pub fn to_world(self, axis: Axis) -> f32 {
452 let viewport = main_camera().world_viewport();
453
454 match self {
455 Value::World(val) => val,
456 Value::Px(val) => {
457 match axis {
458 Axis::X => val / screen_width() * viewport.x,
459 Axis::Y => val / screen_height() * viewport.y,
460 }
461 }
462 Value::Percent(percent) => {
464 match axis {
465 Axis::X => percent * viewport.x,
466 Axis::Y => percent * viewport.y,
467 }
468 }
469 }
470 }
471}
472
473#[derive(Copy, Clone, Debug)]
474pub struct Size {
475 pub width: Value,
476 pub height: Value,
477}
478
479impl Size {
480 pub fn world(width: f32, height: f32) -> Self {
481 Size { width: Value::World(width), height: Value::World(height) }
482 }
483
484 pub fn screen(width: f32, height: f32) -> Self {
485 Size { width: Value::Px(width), height: Value::Px(height) }
486 }
487
488 pub fn percent(width: f32, height: f32) -> Self {
489 Size { width: Value::Percent(width), height: Value::Percent(height) }
490 }
491
492 pub fn to_world(self) -> Vec2 {
493 vec2(self.width.to_world(Axis::X), self.height.to_world(Axis::Y))
494 }
495}