1#![allow(clippy::uninlined_format_args)]
2#![allow(clippy::new_without_default)]
3
4mod asset_loader;
5mod assets;
6mod audio;
7mod blood_canvas;
8mod camera;
9mod config;
10#[cfg(not(target_arch = "wasm32"))]
11mod desktop;
12mod errors;
13mod events;
14mod fast_sprite;
15mod global_state;
16mod input;
17mod lighting;
18mod math;
19mod perf_counters;
20mod quad;
21pub mod random;
22mod render_queues;
23mod shaders;
24pub mod spatial_hash;
25mod task_timer;
26mod text;
27mod timer;
28mod tween;
29
30pub use crate::asset_loader::*;
31pub use crate::assets::*;
32pub use crate::audio::*;
33pub use crate::blood_canvas::*;
34pub use crate::camera::*;
35pub use crate::config::*;
36#[cfg(not(target_arch = "wasm32"))]
37pub use crate::desktop::*;
38pub use crate::errors::*;
39pub use crate::events::*;
40pub use crate::fast_sprite::*;
41pub use crate::global_state::*;
42pub use crate::input::*;
43pub use crate::lighting::*;
44pub use crate::math::*;
45pub use crate::perf_counters::*;
46pub use crate::quad::*;
47pub use crate::random::*;
48pub use crate::render_queues::*;
49pub use crate::shaders::*;
50pub use crate::task_timer::*;
51pub use crate::text::*;
52pub use crate::timer::*;
53pub use crate::tween::*;
54
55pub use std::any::Any;
56pub use std::collections::VecDeque;
57pub use std::hash::{Hash, Hasher};
58pub use std::num::NonZeroU32;
59
60pub use std::ops::DerefMut;
61
62use std::ops::Add;
63pub use std::{
64 borrow::Cow,
65 cell::RefCell,
66 collections::{hash_map::DefaultHasher, HashMap, HashSet},
67 f32::consts::PI,
68 ops::{Mul, Range},
69 rc::Rc,
70 sync::Arc,
71};
72
73use num_traits::NumCast;
74pub use rand::seq::SliceRandom;
75
76pub use smallvec::{self, SmallVec};
77
78pub use anyhow;
79pub use anyhow::{bail, Result};
80
81pub use bimap::BiHashMap;
82pub use fxhash;
83pub use num_traits;
84
85#[cfg(target_arch = "wasm32")]
86pub use instant::{Duration, Instant};
87#[cfg(not(target_arch = "wasm32"))]
88pub use notify;
89#[cfg(not(target_arch = "wasm32"))]
90pub use std::time::{Duration, Instant};
91
92pub use inline_tweak;
93pub use inline_tweak::tweak;
94
95pub use std::future::Future;
96pub use std::path::Path;
97pub use std::pin::Pin;
98pub use std::task::Poll;
99
100pub use hecs;
101pub use hecs::{CommandBuffer, DynamicBundle, Entity, World};
102pub use simple_easing::*;
103
104pub use backtrace;
105pub use backtrace::Backtrace;
106
107pub use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
108pub use bytemuck;
109pub use cfg_if::cfg_if;
110pub use chrono;
111pub use crossbeam::atomic::AtomicCell;
112pub use egui;
113pub use egui_plot;
114pub use egui_winit;
115pub use env_logger;
116pub use epaint;
117pub use glam::{
118 ivec2, uvec2, vec2, vec3, vec4, Affine2, IVec2, Mat3, Mat4, UVec2, Vec2,
119 Vec2Swizzles, Vec3, Vec4,
120};
121#[cfg(feature = "exr")]
122pub use half;
123pub use image;
124pub use image::DynamicImage;
125pub use itertools::Itertools;
126pub use log;
127pub use log::{debug, error, info, trace, warn};
128pub use once_cell::{
129 self,
130 sync::{Lazy, OnceCell},
131};
132pub use parking_lot::Mutex;
133pub use rand::{distributions::uniform::SampleUniform, Rng, RngCore};
134
135#[cfg(feature = "blobs")]
136pub use blobs;
137
138#[cfg(all(feature = "memory-stats", not(target_arch = "wasm32")))]
139pub use memory_stats;
140
141pub use num_complex::Complex;
142
143pub use etagere;
144pub use fontdue;
145
146#[cfg(feature = "tracy")]
147pub use tracy_client;
148pub use winit::{
149 self,
150 event::{ElementState, Event, MouseScrollDelta, WindowEvent},
151 window::Window,
152};
153
154pub use thunderdome::{Arena, Index};
155
156pub use maplit::hashmap;
157
158pub const FHD_RATIO: f32 = 1920.0 / 1080.0;
159
160pub static GLOBAL_PARAMS: Lazy<AtomicRefCell<GlobalParams>> =
161 Lazy::new(|| AtomicRefCell::new(GlobalParams::new()));
162
163#[cfg(all(feature = "memprof", feature = "tracy"))]
164#[global_allocator]
165static GLOBAL: tracy_client::ProfiledAllocator<std::alloc::System> =
166 tracy_client::ProfiledAllocator::new(std::alloc::System, 100);
167
168#[cfg(feature = "jemalloc")]
169#[global_allocator]
170static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
171
172#[cfg(feature = "jemalloc")]
173pub use jemalloc_ctl;
174
175pub use ::rand::{
176 distributions::Distribution, distributions::WeightedIndex,
177 seq::IteratorRandom, thread_rng,
178};
179
180#[cfg(target_arch = "wasm32")]
181pub use wasm_bindgen;
182#[cfg(target_arch = "wasm32")]
183pub use wasm_bindgen_futures;
184#[cfg(target_arch = "wasm32")]
185pub use web_sys;
186
187#[cfg(target_arch = "wasm32")]
188pub use console_error_panic_hook;
189#[cfg(target_arch = "wasm32")]
190pub use console_log;
191
192pub use anymap::AnyMap;
193pub use bitflags::bitflags;
194pub use crossbeam;
195pub use lazy_static::lazy_static;
196pub use ordered_float::OrderedFloat;
197
198pub use comfy_color_backtrace as color_backtrace;
199#[cfg(feature = "git-version")]
200pub use comfy_git_version as git_version;
201pub use comfy_include_dir as include_dir;
202
203pub use kira;
204pub use kira::manager::{AudioManager, AudioManagerSettings};
205pub use kira::sound::static_sound::{
206 StaticSoundData, StaticSoundHandle, StaticSoundSettings,
207};
208pub use kira::{
209 track::{
210 effect::{
211 filter::{FilterBuilder, FilterHandle},
212 reverb::ReverbBuilder,
213 },
214 TrackBuilder, TrackHandle,
215 },
216 Volume,
217};
218
219pub fn constant(_t: f32) -> f32 {
220 0.0
221}
222
223#[derive(Clone, Debug)]
224pub struct Name {
225 pub name: Cow<'static, str>,
226 }
229
230impl Name {
231 pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
232 Self {
233 name: name.into(),
234 }
236 }
237}
238
239pub fn default_hash(value: &impl std::hash::Hash) -> u64 {
240 let mut hasher = DefaultHasher::new();
241 value.hash(&mut hasher);
242 hasher.finish()
243}
244
245pub trait ComplexExt {
246 fn lerp(self, other: Self, t: f32) -> Self;
247}
248
249impl ComplexExt for Complex<f32> {
250 fn lerp(self, other: Self, t: f32) -> Self {
251 let real = self.re + t * (other.re - self.re);
252 let imag = self.im + t * (other.im - self.im);
253 Complex::new(real, imag)
254 }
255}
256
257#[derive(Copy, Clone, Debug)]
258pub struct ValueRange<T> {
259 pub min: T,
260 pub max: T,
261 pub value: T,
262 pub speed: T,
263}
264
265impl<T> ValueRange<T> {
266 pub fn new(value: T, min: T, max: T, speed: T) -> Self {
267 Self { min, max, value, speed }
268 }
269}
270
271pub trait OptionExtensions {
272 fn log_none(self, message: impl Into<Cow<'static, str>>) -> Self;
273 fn log_none_f(self, f: impl FnOnce()) -> Self;
274}
275
276impl<T> OptionExtensions for Option<T> {
277 fn log_none(self, message: impl Into<Cow<'static, str>>) -> Self {
278 if self.is_none() {
279 error!("{}", message.into());
280 }
281
282 self
283 }
284
285 fn log_none_f(self, f: impl FnOnce()) -> Self {
286 if self.is_none() {
287 f();
288 }
289
290 self
291 }
292}
293
294pub struct GlobalParams {
295 pub floats: HashMap<&'static str, ValueRange<f32>>,
296 pub ints: HashMap<&'static str, ValueRange<i32>>,
297 pub flags: HashMap<String, bool>,
298}
299
300impl GlobalParams {
301 pub fn new() -> Self {
302 let mut floats = HashMap::default();
303
304 floats.insert(
305 "filter-cutoff",
306 ValueRange::new(100.0, 0.0, 20000.0, 10.0),
307 );
308 floats.insert("filter-resonance", ValueRange::new(0.0, 0.0, 1.0, 0.01));
309
310 floats.insert("colorScale", ValueRange::new(1.0, 0.001, 20.0, 0.01));
311 floats
312 .insert("bloomThreshold", ValueRange::new(1.0, 0.001, 50.0, 0.05));
313 floats.insert("bloom-lerp", ValueRange::new(0.5, 0.0, 1.0, 0.005));
314 floats.insert("exposure", ValueRange::new(1.0, 0.001, 10.0, 0.01));
315 floats.insert("bloomGamma", ValueRange::new(0.8, 0.001, 3.0, 0.01));
316
317 floats.insert(
318 "chromatic_aberration",
319 ValueRange::new(0.0, 0.0, 50.0, 0.1),
320 );
321
322 floats.insert("contrast", ValueRange::new(1.0, 0.00, 10.0, 0.01));
323 floats.insert("brightness", ValueRange::new(0.0, 0.00, 10.0, 0.01));
324 floats.insert("saturation", ValueRange::new(1.0, 0.00, 10.0, 0.01));
325 floats.insert("gamma", ValueRange::new(1.0, 0.001, 10.0, 0.01));
326 floats.insert("shake_amount", ValueRange::new(0.0, 0.0, 10.0, 0.01));
327
328 let mut ints = HashMap::default();
329
330 ints.insert("bloom_alg", ValueRange::new(1, 0, 2, 1));
331 ints.insert("physics_substeps", ValueRange::new(8, 1, 64, 1));
332 ints.insert("tonemapping_alg", ValueRange::new(3, 0, 4, 1));
333
334 let mut flags = HashMap::default();
335
336 flags.insert("additive-blending".to_string(), true);
337
338 Self { floats, ints, flags }
339 }
340
341 pub fn set(name: &'static str, value: f32) {
342 GLOBAL_PARAMS
343 .borrow_mut()
344 .floats
345 .entry(name)
346 .and_modify(|e| e.value = value);
347 }
348
349 pub fn get(name: &str) -> f32 {
350 GLOBAL_PARAMS
351 .borrow()
352 .floats
353 .get(name)
354 .cloned()
355 .unwrap_or_else(|| {
356 error!("Missing param {name}");
357 ValueRange::new(0.0, 0.0, 0.0, 0.1)
358 })
359 .value
360 }
361
362 pub fn set_int(name: &'static str, value: i32) {
363 GLOBAL_PARAMS
364 .borrow_mut()
365 .ints
366 .entry(name)
367 .and_modify(|e| e.value = value);
368 }
369
370 pub fn get_int(name: &str) -> i32 {
371 GLOBAL_PARAMS
372 .borrow()
373 .ints
374 .get(name)
375 .cloned()
376 .unwrap_or_else(|| {
377 error!("Missing param {name}");
378 ValueRange::new(0, 0, 0, 0)
379 })
380 .value
381 }
382
383 pub fn flag(name: &str) -> bool {
384 *GLOBAL_PARAMS.borrow().flags.get(name).unwrap_or(&false)
385 }
386
387 pub fn toggle_flag(name: &str) {
388 let flags = &mut GLOBAL_PARAMS.borrow_mut().flags;
389
390 let entry = flags.entry(name.to_string()).or_insert(false);
391
392 *entry = !*entry;
393 }
394
395 pub fn flag_set(name: &str, value: bool) {
396 GLOBAL_PARAMS.borrow_mut().flags.insert(name.to_string(), value);
397 }
398}
399
400pub trait ErrorHandlingExtensions {
416 fn log_err(&self);
417}
418
419impl<T> ErrorHandlingExtensions for Option<T> {
420 fn log_err(&self) {
421 if self.is_none() {
422 error!("Unexpected None");
423 }
424 }
425}
426
427impl<T, E> ErrorHandlingExtensions for std::result::Result<T, E>
428where E: std::fmt::Debug
429{
430 fn log_err(&self) {
431 if let Err(err) = self {
432 error!("Unexpected {err:?}");
433 }
434 }
435}
436
437pub trait ResultExtensions<T> {
438 fn log_err_ok(self) -> Option<T>;
439}
440
441impl<T, E> ResultExtensions<T> for std::result::Result<T, E>
442where E: std::fmt::Debug
443{
444 fn log_err_ok(self) -> Option<T> {
445 match self {
446 Ok(val) => Some(val),
447 Err(err) => {
448 error!("Unexpected {err:?}");
449 None
450 }
451 }
452 }
453}
454
455#[derive(Copy, Clone, Debug)]
456pub struct FollowPlayer;
457
458#[derive(Copy, Clone, Debug)]
459pub struct PlayerTag;
460
461pub fn rect_contains(center: Vec2, size: Vec2, point: Vec2) -> bool {
462 let hx = size.x / 2.0;
463 let hy = size.y / 2.0;
464
465 point.x >= center.x - hx &&
466 point.x <= center.x + hx &&
467 point.y >= center.y - hy &&
468 point.y <= center.y + hy
469}
470
471#[derive(Copy, Clone, Debug)]
472pub struct IRect {
473 pub offset: IVec2,
474 pub size: IVec2,
475}
476
477impl IRect {
478 pub fn new(offset: IVec2, size: IVec2) -> Self {
479 IRect { offset, size }
480 }
481}
482
483#[derive(Copy, Clone, Debug, Default)]
484pub struct Rect {
485 pub center: Vec2,
486 pub size: Vec2,
487}
488
489impl Rect {
490 pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Self {
491 Self { center: vec2(x + w / 2.0, y + h / 2.0), size: vec2(w, h) }
493 }
494
495 pub fn new(center: Vec2, size: Vec2) -> Self {
496 Self { center, size }
497 }
498
499 pub fn from_min_max(min: Vec2, max: Vec2) -> Self {
500 Self { center: (min + max) / 2.0, size: max - min }
501 }
502
503 pub fn top_left(&self) -> Vec2 {
504 self.center - self.size / 2.0
505 }
506
507 pub fn x(&self) -> f32 {
508 self.center.x - self.size.x / 2.0
509 }
510
511 pub fn y(&self) -> f32 {
512 self.center.y - self.size.y / 2.0
513 }
514
515 pub fn w(&self) -> f32 {
516 self.size.x
517 }
518
519 pub fn h(&self) -> f32 {
520 self.size.y
521 }
522
523 pub fn expand(self, size: Vec2) -> Self {
524 Self { center: self.center, size: self.size + size }
525 }
526
527 pub fn contains(&self, point: Vec2) -> bool {
528 rect_contains(self.center, self.size, point)
529 }
530
531 pub fn contains_rect_safe(&self, point: Vec2, size: Vec2) -> bool {
532 self.expand(size).contains(point)
533 }
534}
535
536#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
537#[repr(C)]
538pub struct FrameDataUniform {
539 pub projection: [f32; 16],
540 pub mouse_world: [f32; 2],
541 pub mouse_screen: [f32; 2],
542 pub time: f32,
543 pub delta: f32,
544 pub frame: i32,
545 pub fps: f32,
546 pub aspect_ratio: f32,
547 pub _padding: [f32; 3],
548}
549
550#[derive(Clone, Debug)]
551pub struct FrameParams {
552 pub frame: u32,
553 pub delta: f32,
554 pub time: f32,
555}
556
557pub struct SpriteDraw {
558 pub texture: TextureHandle,
559 pub position: Vec2,
560 pub color: Color,
561 pub z_index: i32,
562 pub raw_draw: RawDrawParams,
563}
564
565pub type TextureLoadQueue = Vec<LoadedImage>;
566
567#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash)]
568pub enum BlendMode {
569 #[default]
570 None,
571 Additive,
573 Alpha,
574}
575
576pub struct DrawParams<'a> {
577 pub aspect_ratio: f32,
578 pub projection: Mat4,
579 pub white_px: TextureHandle,
580
581 pub clear_color: Color,
582 pub lights: Vec<Light>,
583
584 pub config: &'a mut GameConfig,
585
586 pub frame: FrameParams,
587
588 pub particle_queues: HashMap<MeshGroupKey, Vec<ParticleDraw>>,
589}
590
591#[derive(Copy, Clone, Debug)]
592pub struct ParticleDraw {
593 pub position: Vec3,
594 pub rotation: f32,
595 pub texture: TextureHandle,
596 pub color: Color,
597 pub size: Vec2,
598 pub source_rect: Option<IRect>,
599 pub blend_mode: BlendMode,
600}
601
602#[derive(Copy, Clone, Debug, Default)]
603pub struct RawDrawParams {
604 pub dest_size: Option<Vec2>,
605 pub source_rect: Option<IRect>,
606 pub rotation: f32,
607 pub flip_x: bool,
608 pub flip_y: bool,
609 pub pivot: Option<Vec2>,
610}
611
612const WHITE_ARRAY: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
613
614pub const QUAD_VERTICES: &[SpriteVertex] = &[
615 SpriteVertex {
616 position: [-0.5, -0.5, 0.0],
617 tex_coords: [1.0, 1.0],
618 color: WHITE_ARRAY,
619 },
620 SpriteVertex {
621 position: [-0.5, 0.5, 0.0],
622 tex_coords: [1.0, 0.0],
623 color: WHITE_ARRAY,
624 },
625 SpriteVertex {
626 position: [0.5, 0.5, 0.0],
627 tex_coords: [0.0, 0.0],
628 color: WHITE_ARRAY,
629 },
630 SpriteVertex {
631 position: [0.5, -0.5, 0.0],
632 tex_coords: [0.0, 1.0],
633 color: WHITE_ARRAY,
634 },
635];
636
637#[derive(Clone, Debug, Default)]
638pub struct Mesh {
639 pub origin: Vec3,
640 pub vertices: SmallVec<[SpriteVertex; 4]>,
641 pub indices: SmallVec<[u32; 6]>,
642 pub z_index: i32,
643 pub texture: Option<TextureHandle>,
644 pub y_sort_offset: f32,
645}
646
647#[repr(C)]
648#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
649pub struct SpriteVertex {
650 pub position: [f32; 3],
651 pub tex_coords: [f32; 2],
652 pub color: [f32; 4],
653}
654
655impl SpriteVertex {
656 pub fn new(position: Vec3, tex_coords: Vec2, color: Color) -> Self {
657 Self {
658 position: [position.x, position.y, position.z],
659 tex_coords: [tex_coords.x, tex_coords.y],
660 color: [color.r, color.g, color.b, color.a],
661 }
662 }
663}
664
665pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
666 a + (b - a) * t
667}
668
669#[macro_export]
697macro_rules! span_with_timing {
698 ($name: expr) => {
699 let (_s1, _s2) = (span!($name), timing_start($name));
700 };
701}
702
703#[cfg(feature = "tracy")]
704#[macro_export]
705macro_rules! span {
706 ($name: expr) => {
707 Some(tracy_client::span!($name, 0))
708 };
709}
710
711#[cfg(not(feature = "tracy"))]
712#[macro_export]
713macro_rules! span {
714 ($name: expr) => {
715 None::<()>
716 };
717}
718
719#[derive(
720 Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable, PartialEq,
721)]
722#[repr(C)]
723pub struct Color {
724 pub r: f32,
725 pub g: f32,
726 pub b: f32,
727 pub a: f32,
728}
729
730impl Color {
731 pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
732 Self { r, g, b, a }
733 }
734
735 pub const fn gray(value: f32) -> Self {
736 Self { r: value, g: value, b: value, a: 1.0 }
737 }
738
739 pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
740 Self { r, g, b, a: 1.0 }
741 }
742
743 pub fn rgb8(r: u8, g: u8, b: u8) -> Self {
744 Self {
745 r: r as f32 / 255.0,
746 g: g as f32 / 255.0,
747 b: b as f32 / 255.0,
748 a: 1.0,
749 }
750 }
751
752 pub fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
753 Self {
754 r: r as f32 / 255.0,
755 g: g as f32 / 255.0,
756 b: b as f32 / 255.0,
757 a: a as f32 / 255.0,
758 }
759 }
760
761 pub fn egui(self) -> egui::Color32 {
762 self.into()
763 }
764
765 pub fn to_vec4(&self) -> Vec4 {
766 Vec4::new(self.r, self.g, self.b, self.a)
767 }
768
769 pub fn gamma_space_tint(self, tint: Color) -> Color {
770 let gamma_a = self.to_srgb();
771 let gamma_b = tint.to_srgb();
772
773 let result = gamma_a * gamma_b;
774
775 result.to_linear()
776 }
777
778 pub fn linear_space_tint(self, tint: Color) -> Color {
779 let lin_a = self.to_linear();
780 let lin_b = tint.to_linear();
781
782 let result = lin_a * lin_b;
783
784 result.to_srgb()
785 }
786
787 pub fn to_linear(self) -> Color {
788 Color::new(self.r.powf(2.2), self.g.powf(2.2), self.b.powf(2.2), self.a)
789 }
790
791 pub fn to_srgb(self) -> Color {
792 Color::new(
793 self.r.powf(1.0 / 2.2),
794 self.g.powf(1.0 / 2.2),
795 self.b.powf(1.0 / 2.2),
796 self.a,
797 )
798 }
799
800 pub fn to_array(self) -> [u8; 4] {
801 [
802 (self.r * 255.0) as u8,
803 (self.g * 255.0) as u8,
804 (self.b * 255.0) as u8,
805 (self.a * 255.0) as u8,
806 ]
807 }
808
809 pub fn to_array_f32(self) -> [f32; 4] {
810 [self.r, self.g, self.b, self.a]
811 }
812
813 pub fn alpha(&self, value: f32) -> Color {
814 Color::new(self.r, self.g, self.b, value)
815 }
816
817 pub fn mix(&self, other: Color, value: f32) -> Color {
818 let a = 1.0 - value;
819 let b = value;
820
821 Color::new(
822 self.r * a + other.r * b,
823 self.g * a + other.g * b,
824 self.b * a + other.b * b,
825 self.a * a + other.a * b,
826 )
827 }
828
829 pub fn to_image_rgba(self) -> image::Rgba<u8> {
830 image::Rgba([
831 (self.r * 255.0) as u8,
832 (self.g * 255.0) as u8,
833 (self.b * 255.0) as u8,
834 (self.a * 255.0) as u8,
835 ])
836 }
837
838 pub fn darken(&self, amount: f32) -> Color {
839 let amount = 1.0 - amount;
840 Color::new(self.r * amount, self.g * amount, self.b * amount, self.a)
841 }
842
843 pub fn lighten(&self, amount: f32) -> Color {
849 let r = (self.r + amount).min(1.0);
850 let g = (self.g + amount).min(1.0);
851 let b = (self.b + amount).min(1.0);
852 Color::new(r, g, b, self.a)
853 }
854
855 pub fn boost(&self, amount: f32) -> Color {
856 Color::new(self.r * amount, self.g * amount, self.b * amount, self.a)
857 }
858}
859
860impl From<Color> for image::Rgba<u8> {
861 fn from(value: Color) -> Self {
862 image::Rgba(value.to_array())
863 }
864}
865
866impl From<image::Rgba<u8>> for Color {
867 fn from(value: image::Rgba<u8>) -> Self {
868 Self::rgba8(value.0[0], value.0[1], value.0[2], value.0[3])
869 }
870}
871
872impl From<Color> for egui::Color32 {
873 fn from(value: Color) -> Self {
874 egui::Color32::from_rgba_unmultiplied(
875 (value.r * 255.0) as u8,
876 (value.g * 255.0) as u8,
877 (value.b * 255.0) as u8,
878 (value.a * 255.0) as u8,
879 )
880 }
881}
882
883impl Add<Color> for Color {
884 type Output = Color;
885
886 fn add(self, val: Color) -> Self::Output {
887 Color::new(
888 self.r + val.r,
889 self.g + val.g,
890 self.b + val.b,
891 (self.a + val.a).clamp(0.0, 1.0),
892 )
893 }
894}
895
896impl Mul<Color> for Color {
897 type Output = Color;
898
899 fn mul(self, val: Color) -> Self::Output {
900 Color::new(
901 self.r * val.r,
902 self.g * val.g,
903 self.b * val.b,
904 self.a * val.a,
905 )
906 }
907}
908
909impl Mul<f32> for Color {
910 type Output = Color;
911
912 fn mul(self, val: f32) -> Self::Output {
913 Color::new(self.r * val, self.g * val, self.b * val, self.a)
914 }
915}
916
917pub fn font_family(name: &str, size: f32) -> egui::FontId {
929 egui::FontId::new(size, egui::FontFamily::Name(name.into()))
930}
931
932#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
933pub struct Sound {
934 pub id: u64,
935}
936
937impl Sound {
938 pub fn from_path(path: &str) -> Sound {
939 Sound { id: simple_hash(path) }
940 }
941}
942
943#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
944pub enum TextureHandle {
945 Path(u64),
946 Raw(u64),
947 RenderTarget(RenderTargetId),
948}
949
950pub fn simple_hash(value: impl std::hash::Hash) -> u64 {
951 ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(value)
952}
953
954impl TextureHandle {
955 pub fn from_path(path: &str) -> Self {
957 TextureHandle::Path(simple_hash(path))
958 }
959
960 pub fn key_unchecked(key: &str) -> Self {
961 TextureHandle::Path(simple_hash(key))
962 }
963}
964
965pub const LIGHTGRAY: Color = Color::new(0.78, 0.78, 0.78, 1.00);
966pub const GRAY: Color = Color::new(0.51, 0.51, 0.51, 1.00);
967pub const DARKGRAY: Color = Color::new(0.31, 0.31, 0.31, 1.00);
968pub const YELLOW: Color = Color::new(0.99, 0.98, 0.00, 1.00);
969pub const GOLD: Color = Color::new(1.00, 0.80, 0.00, 1.00);
970pub const ORANGE: Color = Color::new(1.00, 0.63, 0.00, 1.00);
971pub const PINK: Color = Color::new(1.00, 0.43, 0.76, 1.00);
972pub const RED: Color = Color::new(0.90, 0.16, 0.22, 1.00);
973pub const MAROON: Color = Color::new(0.75, 0.13, 0.22, 1.00);
974pub const GREEN: Color = Color::new(0.00, 0.89, 0.19, 1.00);
975pub const LIME: Color = Color::new(0.00, 0.62, 0.18, 1.00);
976pub const DARKGREEN: Color = Color::new(0.00, 0.46, 0.17, 1.00);
977pub const SKYBLUE: Color = Color::new(0.40, 0.75, 1.00, 1.00);
978pub const BLUE: Color = Color::new(0.00, 0.47, 0.95, 1.00);
979pub const DARKBLUE: Color = Color::new(0.00, 0.32, 0.67, 1.00);
980pub const PURPLE: Color = Color::new(0.78, 0.48, 1.00, 1.00);
981pub const VIOLET: Color = Color::new(0.53, 0.24, 0.75, 1.00);
982pub const DARKPURPLE: Color = Color::new(0.44, 0.12, 0.49, 1.00);
983pub const BEIGE: Color = Color::new(0.83, 0.69, 0.51, 1.00);
984pub const BROWN: Color = Color::new(0.50, 0.42, 0.31, 1.00);
985pub const DARKBROWN: Color = Color::new(0.30, 0.25, 0.18, 1.00);
986pub const WHITE: Color = Color::new(1.00, 1.00, 1.00, 1.00);
987pub const BLACK: Color = Color::new(0.00, 0.00, 0.00, 1.00);
988pub const BLANK: Color = Color::new(0.00, 0.00, 0.00, 0.00);
989pub const MAGENTA: Color = Color::new(1.00, 0.00, 1.00, 1.00);
990pub const DARKRED: Color = Color::new(0.46, 0.08, 0.12, 1.00);
991pub const TRANSPARENT: Color = Color::new(0.0, 0.0, 0.0, 0.0);
992pub const ALICE_BLUE: Color = Color::rgb(0.94, 0.97, 1.0);
993pub const ANTIQUE_WHITE: Color = Color::rgb(0.98, 0.92, 0.84);
994pub const AQUAMARINE: Color = Color::rgb(0.49, 1.0, 0.83);
995pub const AZURE: Color = Color::rgb(0.94, 1.0, 1.0);
996pub const BISQUE: Color = Color::rgb(1.0, 0.89, 0.77);
997pub const CRIMSON: Color = Color::rgb(0.86, 0.08, 0.24);
998pub const CYAN: Color = Color::rgb(0.0, 1.0, 1.0);
999pub const DARK_GRAY: Color = Color::rgb(0.25, 0.25, 0.25);
1000pub const DARK_GREEN: Color = Color::rgb(0.0, 0.5, 0.0);
1001pub const FUCHSIA: Color = Color::rgb(1.0, 0.0, 1.0);
1002pub const INDIGO: Color = Color::rgb(0.29, 0.0, 0.51);
1003pub const LIME_GREEN: Color = Color::rgb(0.2, 0.8, 0.2);
1004pub const MIDNIGHT_BLUE: Color = Color::rgb(0.1, 0.1, 0.44);
1005pub const NAVY: Color = Color::rgb(0.0, 0.0, 0.5);
1006pub const OLIVE: Color = Color::rgb(0.5, 0.5, 0.0);
1007pub const ORANGE_RED: Color = Color::rgb(1.0, 0.27, 0.0);
1008pub const SALMON: Color = Color::rgb(0.98, 0.5, 0.45);
1009pub const SEA_GREEN: Color = Color::rgb(0.18, 0.55, 0.34);
1010pub const SILVER: Color = Color::rgb(0.75, 0.75, 0.75);
1011pub const TEAL: Color = Color::rgb(0.0, 0.5, 0.5);
1012pub const TOMATO: Color = Color::rgb(1.0, 0.39, 0.28);
1013pub const TURQUOISE: Color = Color::rgb(0.25, 0.88, 0.82);
1014pub const YELLOW_GREEN: Color = Color::rgb(0.6, 0.8, 0.2);
1015
1016pub const COMFY_BLUE: Color = Color::rgb(0.74, 0.86, 0.88);
1020pub const COMFY_PINK: Color = Color::rgb(1.0, 0.76, 0.86);
1021pub const COMFY_GREEN: Color = Color::rgb(0.67, 0.92, 0.72);
1022pub const COMFY_DARK_BLUE: Color = Color::rgb(0.73, 0.93, 0.97);
1023
1024pub trait UVec2Extensions {
1041 fn fit_width(self, width: u32) -> egui::Vec2;
1042 fn fit_height(self, height: u32) -> egui::Vec2;
1043 fn fit_rect(self, width: u32, height: u32) -> egui::Vec2;
1044 fn fit_square(self, size: u32) -> egui::Vec2;
1045}
1046
1047impl UVec2Extensions for UVec2 {
1048 fn fit_width(self, width: u32) -> egui::Vec2 {
1049 let ratio = self.y as f32 / self.x as f32;
1050 egui::vec2(width as f32, width as f32 * ratio)
1051 }
1052
1053 fn fit_height(self, height: u32) -> egui::Vec2 {
1054 let ratio = self.x as f32 / self.y as f32;
1055 egui::vec2(height as f32 * ratio, height as f32)
1056 }
1057
1058 fn fit_square(self, size: u32) -> egui::Vec2 {
1059 self.fit_rect(size, size)
1060 }
1061
1062 fn fit_rect(self, width: u32, height: u32) -> egui::Vec2 {
1063 let size = vec2(width as f32, height as f32);
1064 let self_ratio = self.x as f32 / self.y as f32;
1065 let rect_ratio = size.x / size.y;
1066
1067 if self_ratio > rect_ratio {
1068 self.fit_width(size.x as u32)
1070 } else {
1071 self.fit_height(size.y as u32)
1073 }
1074 }
1075}
1076
1077pub trait Vec2Extensions {
1078 fn normalize_or_right(self) -> Vec2;
1079 fn tuple(self) -> (f32, f32);
1080 fn wiggle(self, angle: f32) -> Vec2;
1081 fn angle(self) -> f32;
1082
1083 fn as_array(&self) -> [f32; 2];
1084 fn as_transform(&self) -> Transform;
1085 fn egui(&self) -> egui::Vec2;
1086 fn egui_pos(&self) -> egui::Pos2;
1087}
1088
1089impl Vec2Extensions for Vec2 {
1090 fn normalize_or_right(self) -> Vec2 {
1091 let rcp = self.length_recip();
1092
1093 if rcp.is_finite() && rcp > 0.0 {
1094 self * rcp
1095 } else {
1096 Self::X
1097 }
1098 }
1099
1100 fn tuple(self) -> (f32, f32) {
1101 (self.x, self.y)
1102 }
1103
1104 fn wiggle(self, angle: f32) -> Vec2 {
1105 self.rotate(Vec2::from_angle(gen_range(-angle / 2.0, angle / 2.0)))
1106 }
1107
1108 fn angle(self) -> f32 {
1109 vec2(1.0, 0.0).angle_between(self)
1110 }
1111
1112 fn as_array(&self) -> [f32; 2] {
1113 [self.x, self.y]
1114 }
1115
1116 fn as_transform(&self) -> Transform {
1117 Transform::position(*self)
1118 }
1119
1120 fn egui(&self) -> egui::Vec2 {
1121 egui::vec2(self.x, self.y)
1122 }
1123
1124 fn egui_pos(&self) -> egui::Pos2 {
1125 egui::pos2(self.x, self.y)
1126 }
1127}
1128
1129#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
1138pub struct SemanticVer {
1139 pub major: u16,
1140 pub minor: u16,
1141 pub patch: u16,
1142}
1143
1144impl std::fmt::Display for SemanticVer {
1145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1146 write!(f, "v{}.{}.{}", self.major, self.minor, self.patch)
1147 }
1148}
1149
1150#[macro_export]
1151macro_rules! define_versions {
1152 () => {
1153 #[cfg(feature = "git-version")]
1154 pub const GIT_VERSION: &str = git_version::git_version!();
1155
1156 $crate::lazy_static! {
1157 pub static ref VERSION: $crate::SemanticVer = $crate::SemanticVer {
1158 major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
1159 minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
1160 patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
1161 };
1162 }
1163
1164 #[cfg(not(feature = "git-version"))]
1165 pub fn version_str() -> &'static str {
1166 concat!(
1167 "v",
1168 env!("CARGO_PKG_VERSION_MAJOR"),
1169 ".",
1170 env!("CARGO_PKG_VERSION_MINOR"),
1171 ".",
1172 env!("CARGO_PKG_VERSION_PATCH"),
1173 )
1174 }
1175
1176 #[cfg(feature = "git-version")]
1177 pub fn version_str() -> &'static str {
1178 concat!(
1179 "v",
1180 env!("CARGO_PKG_VERSION_MAJOR"),
1181 ".",
1182 env!("CARGO_PKG_VERSION_MINOR"),
1183 ".",
1184 env!("CARGO_PKG_VERSION_PATCH"),
1185 " (",
1186 git_version::git_version!(),
1187 ")"
1188 )
1189 }
1190 };
1191}
1192
1193#[derive(Copy, Clone, Debug)]
1194pub struct Transform {
1195 pub position: Vec2,
1196 pub rotation: f32,
1197 pub scale: f32,
1198 pub parent: Option<Entity>,
1199
1200 pub abs_position: Vec2,
1201 pub abs_rotation: f32,
1202 pub abs_scale: f32,
1203}
1204
1205impl Transform {
1206 pub fn position(position: Vec2) -> Self {
1207 Self {
1208 position,
1209 rotation: 0.0,
1210 scale: 1.0,
1211
1212 parent: None,
1213
1214 abs_position: position,
1215 abs_rotation: 0.0,
1216 abs_scale: 1.0,
1217 }
1218 }
1219
1220 pub fn rotation(self, rotation: f32) -> Self {
1221 Self { rotation, ..self }
1222 }
1223
1224 pub fn scale(self, scale: f32) -> Self {
1225 Self { scale, ..self }
1226 }
1227
1228 pub fn distance(&self, other: &Transform) -> f32 {
1229 self.position.distance(other.position)
1230 }
1231
1232 pub fn parent(self, parent: Entity) -> Self {
1233 Self { parent: Some(parent), ..self }
1234 }
1235
1236 pub fn compose_with_parent(
1237 &self,
1238 parent_transform: &Transform,
1239 ) -> Transform {
1240 let parent_matrix = parent_transform.to_matrix();
1241 let self_matrix = self.to_matrix();
1242 let composed_matrix = parent_matrix * self_matrix;
1243
1244 let composed_transform = Transform::from_matrix(composed_matrix);
1245
1246 Transform {
1247 position: composed_transform.position,
1248 rotation: composed_transform.rotation,
1249 scale: composed_transform.scale,
1251
1252 parent: None,
1253
1254 abs_position: composed_transform.position,
1255 abs_rotation: composed_transform.rotation,
1256 abs_scale: composed_transform.scale,
1257 }
1258 }
1259
1260 pub fn to_matrix(&self) -> Mat3 {
1261 let translate_matrix = Mat3::from_translation(self.position);
1262 let rotate_matrix = Mat3::from_angle(self.rotation);
1263 let scale_matrix = Mat3::from_scale(splat(self.scale));
1264
1265 translate_matrix * rotate_matrix * scale_matrix
1266 }
1267
1268 pub fn from_matrix(matrix: Mat3) -> Self {
1269 let position = matrix.transform_point2(Vec2::ZERO);
1270 let rotation = f32::atan2(matrix.x_axis.y, matrix.x_axis.x);
1272 let scale = matrix.x_axis.length();
1273
1274 Transform {
1275 position,
1276 rotation,
1277 scale,
1278
1279 parent: None,
1280
1281 abs_position: position,
1282 abs_rotation: rotation,
1283 abs_scale: scale,
1284 }
1285 }
1286}
1287
1288pub fn initialize_logger() {
1289 #[cfg(all(feature = "file_logger", not(target_arch = "wasm32")))]
1290 {
1291 pub fn initialize_log4rs(
1292 log_root: &std::path::Path,
1293 ) -> Result<(), Box<dyn std::error::Error>> {
1294 use chrono::Timelike;
1295 use log4rs::{append::file::*, config::*};
1296
1297 let now = chrono::Utc::now();
1298 let (is_pm, hour) = now.hour12();
1299
1300 let log_file = format!(
1301 "{} {}-{}-{} {}.log",
1302 now.date_naive(),
1303 hour,
1304 now.minute(),
1305 now.second(),
1306 if is_pm { "pm" } else { "am" }
1307 );
1308
1309 let log_location = log_root.join(log_file);
1310
1311 let logfile = FileAppender::builder().build(&log_location)?;
1312 let config = Config::builder()
1313 .appender(
1314 Appender::builder().build("logfile", Box::new(logfile)),
1315 )
1316 .build(
1317 Root::builder()
1318 .appender("logfile")
1319 .build(log::LevelFilter::Info),
1320 )?;
1321
1322 log4rs::init_config(config)?;
1323
1324 std::fs::write(&log_location, "")?;
1326
1327 Ok(())
1328 }
1329
1330 initialize_log4rs(std::path::Path::new("logs")).unwrap_or_else(|err| {
1331 eprintln!("FAILED TO INITIALIZE LOG4RS: {}", err);
1332 });
1333
1334 println!("LOGGER: log4rs ");
1335 }
1336
1337 #[cfg(any(not(feature = "file_logger"), target_arch = "wasm32"))]
1338 {
1339 env_logger::builder().format_timestamp(None).init();
1340 println!("LOGGER: env_logger");
1343 }
1344}
1345
1346pub trait MathExtensions {
1347 fn lerp(self, other: Self, t: f32) -> Self;
1348}
1349
1350impl MathExtensions for f32 {
1351 fn lerp(self, other: Self, t: f32) -> Self {
1352 self * (1.0 - t) + other * t
1353 }
1354}
1355
1356impl MathExtensions for Color {
1357 fn lerp(self, other: Self, t: f32) -> Self {
1358 Color {
1359 r: self.r.lerp(other.r, t),
1360 g: self.g.lerp(other.g, t),
1361 b: self.b.lerp(other.b, t),
1362 a: self.a.lerp(other.a, t),
1363 }
1364 }
1365}
1366
1367pub trait RangeExtensions {
1374 fn lerp(self, t: f32) -> f32;
1375}
1376
1377impl RangeExtensions for Range<f32> {
1378 fn lerp(self, t: f32) -> f32 {
1379 self.start.lerp(self.end, t)
1380 }
1381}
1382
1383#[macro_export]
1384macro_rules! hash {
1385 ($a:expr) => {{
1386 let mut hasher = DefaultHasher::new();
1387 $a.hash(&mut hasher);
1388 hasher.finish()
1389 }};
1390 ($a:expr, $b:expr) => {{
1391 let mut hasher = DefaultHasher::new();
1392 $a.hash(&mut hasher);
1393 $b.hash(&mut hasher);
1394 hasher.finish()
1395 }};
1396}
1397
1398pub fn timed_frame(interval: f32, frames: u32) -> i32 {
1399 ((get_time() / interval as f64) % frames as f64) as i32
1400}
1401
1402pub fn timed_frame_from(start: f64, interval: f32, frames: u32) -> i32 {
1403 let time = (get_time() - start).max(0.0);
1404
1405 ((time / interval as f64) % frames as f64) as i32
1406}
1407
1408pub fn random_timed_frame(seed: f32, interval: f32, frames: u32) -> i32 {
1409 let off = (seed as f64).exp().sin() + 1.0;
1410
1411 (((off + get_time()) / interval as f64) % frames as f64) as i32
1412}
1413
1414pub fn random_entity_idx(entity: Entity, max: i32) -> usize {
1415 (entity.id() as i32 % max) as usize
1416}
1417
1418pub trait EntityExtensions {
1419 fn to_user_data(&self) -> u128;
1420}
1421
1422impl EntityExtensions for Entity {
1423 fn to_user_data(&self) -> u128 {
1424 self.to_bits().get().into()
1425 }
1426}
1427
1428#[macro_export]
1429macro_rules! define_asset_dir {
1430 ($name:literal) => {
1431 define_asset_dir!($name, "assets");
1432 };
1433
1434 ($name:literal, $dir:literal) => {
1435 cfg_if! {
1436 if #[cfg(feature = "ci-release")] {
1437 pub static ASSET_DIR: include_dir::Dir<'_> =
1438 include_dir::include_dir!("$CARGO_MANIFEST_DIR/../" $dir "/" $name "/assets");
1439 } else {
1440 pub static ASSET_DIR: include_dir::Dir<'_> =
1441 include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../" $dir "/" $name "/assets");
1442 }
1443 }
1444
1445 fn base_path(path: &str) -> String {
1446 if cfg!(feature = "ci-release") {
1447 path.to_string()
1448 } else {
1449 format!(concat!("../", $dir, "/", $name, "/assets/{}"), path)
1450 }
1451 }
1452 };
1453}
1454
1455pub fn triangle_wave(value: f32) -> f32 {
1456 triangle_wave_period(value, 2.0)
1457}
1458
1459pub fn triangle_time(offset: f32) -> f32 {
1460 triangle_wave(offset + get_time() as f32)
1461}
1462
1463pub fn triangle_wave_period(value: f32, period: f32) -> f32 {
1464 let t = (value % period) / period;
1465 if t < 0.5 {
1466 2.0 * t
1467 } else {
1468 2.0 * (1.0 - t)
1469 }
1470}
1471
1472pub struct MovingAverage {
1473 size: usize,
1474 queue: VecDeque<f32>,
1475 sum: f32,
1476}
1477
1478impl MovingAverage {
1479 pub fn new(size: usize) -> Self {
1480 MovingAverage { size, queue: VecDeque::with_capacity(size), sum: 0.0 }
1481 }
1482
1483 pub fn next(&mut self, val: f32) -> f32 {
1484 if self.queue.len() == self.size {
1485 self.sum -= self.queue.pop_front().unwrap();
1486 }
1487
1488 self.queue.push_back(val);
1489 self.sum += val;
1490
1491 self.sum / self.queue.len() as f32
1492 }
1493}
1494
1495pub struct MovingStats {
1496 size: usize,
1497 queue: VecDeque<f32>,
1498 sum: f32,
1499 sq_sum: f32,
1500}
1501
1502pub struct Stats {
1503 pub mean: f32,
1504 pub std_dev: f32,
1505 pub percentile_50: f32,
1506 pub percentile_75: f32,
1507 pub percentile_90: f32,
1508 pub percentile_95: f32,
1509 pub percentile_99: f32,
1510}
1511
1512impl MovingStats {
1513 pub fn new(size: usize) -> Self {
1514 MovingStats {
1515 size,
1516 queue: VecDeque::with_capacity(size),
1517 sum: 0.0,
1518 sq_sum: 0.0,
1519 }
1520 }
1521
1522 pub fn next(&mut self, val: f32) -> Stats {
1523 if self.queue.len() == self.size {
1524 let old_val = self.queue.pop_front().unwrap();
1525 self.sum -= old_val;
1526 self.sq_sum -= old_val * old_val;
1527 }
1528
1529 self.queue.push_back(val);
1530 self.sum += val;
1531 self.sq_sum += val * val;
1532
1533 let queue_len = self.queue.len() as f32;
1534 let mean = self.sum / queue_len;
1535 let variance = (self.sq_sum / queue_len) - (mean * mean);
1536 let std_dev = f32::sqrt(variance.max(0.0)); let mut sorted_window: Vec<_> = self.queue.iter().collect();
1539 sorted_window.sort_by(|a, b| b.partial_cmp(a).unwrap());
1540
1541 let get_percentile = |p: f32| {
1542 let idx = (p * queue_len).round() as usize;
1543 sorted_window[idx.min(sorted_window.len() - 1)]
1544 };
1545
1546 Stats {
1547 mean,
1548 std_dev,
1549 percentile_50: *get_percentile(0.5),
1550 percentile_75: *get_percentile(0.75),
1551 percentile_90: *get_percentile(0.9),
1552 percentile_95: *get_percentile(0.95),
1553 percentile_99: *get_percentile(0.99),
1554 }
1555 }
1556}
1557
1558pub struct ExponentialMovingAverage {
1594 alpha: f32,
1595 value: Option<f32>,
1596}
1597
1598impl ExponentialMovingAverage {
1599 pub fn new(alpha: f32) -> Self {
1600 ExponentialMovingAverage { alpha, value: None }
1601 }
1602
1603 pub fn next(&mut self, val: f32) -> f32 {
1604 match self.value {
1605 Some(prev_val) => {
1606 let new_val = self.alpha * val + (1.0 - self.alpha) * prev_val;
1607 self.value = Some(new_val);
1608 new_val
1609 }
1610 None => {
1611 self.value = Some(val);
1612 val
1613 }
1614 }
1615 }
1616}
1617
1618pub fn is_point_in_rotated_rect(
1619 point: Vec2,
1620 rect_center: Vec2,
1621 rect_size: Vec2,
1622 rect_rotation: f32,
1623) -> bool {
1624 let transform = Mat3::from_translation(rect_center) *
1626 Mat3::from_rotation_z(rect_rotation);
1627
1628 let inv_transform = transform.inverse();
1630
1631 let new_point = inv_transform.transform_point2(point);
1633
1634 (new_point.x.abs() <= rect_size.x / 2.0) &&
1636 (new_point.y.abs() <= rect_size.y / 2.0)
1637}
1638
1639pub fn rescale<T: NumCast>(value: T, from: Range<T>, to: Range<T>) -> f32 {
1653 let value: f32 = NumCast::from(value).unwrap_or(0.0);
1654 let from_start = NumCast::from(from.start).unwrap_or(0.0);
1655 let from_end = NumCast::from(from.end).unwrap_or(0.0);
1656 let to_start = NumCast::from(to.start).unwrap_or(0.0);
1657 let to_end = NumCast::from(to.end).unwrap_or(0.0);
1658
1659 let value = value.max(from_start).min(from_end);
1660 let from_range = from_end - from_start;
1661 let to_range = to_end - to_start;
1662
1663 to_start + (value - from_start) / from_range * to_range
1664}
1665
1666#[derive(Copy, Clone, Debug)]
1667pub struct Velocity(pub Vec2);
1668
1669#[derive(Debug, Clone, Copy)]
1670pub struct AABB {
1671 pub min: Vec2,
1672 pub max: Vec2,
1673}
1674
1675impl AABB {
1676 pub fn new(min: Vec2, max: Vec2) -> Self {
1677 Self { min, max }
1678 }
1679
1680 pub fn from_two_points(a: Vec2, b: Vec2) -> Self {
1681 Self { min: a.min(b), max: a.max(b) }
1682 }
1683
1684 pub fn from_top_left(top_left: Vec2, size: Vec2) -> Self {
1685 Self::from_center_size(
1686 vec2(top_left.x + size.x / 2.0, top_left.y - size.y / 2.0),
1687 size,
1688 )
1689 }
1690
1691 pub fn from_center_size(center: Vec2, size: Vec2) -> Self {
1692 let half_size = size * 0.5;
1693 Self { min: center - half_size, max: center + half_size }
1694 }
1695
1696 pub fn center(&self) -> Vec2 {
1697 (self.min + self.max) * 0.5
1698 }
1699
1700 pub fn size(&self) -> Vec2 {
1701 self.max - self.min
1702 }
1703
1704 pub fn contains(&self, point: Vec2) -> bool {
1705 self.min.x <= point.x &&
1706 self.min.y <= point.y &&
1707 self.max.x >= point.x &&
1708 self.max.y >= point.y
1709 }
1710
1711 pub fn intersects(&self, other: &AABB) -> bool {
1712 self.min.x <= other.max.x &&
1713 self.max.x >= other.min.x &&
1714 self.min.y <= other.max.y &&
1715 self.max.y >= other.min.y
1716 }
1717
1718 pub fn expand_to_include_point(&mut self, point: Vec2) {
1719 self.min = self.min.min(point);
1720 self.max = self.max.max(point);
1721 }
1722
1723 pub fn expand_to_include_aabb(&mut self, other: &AABB) {
1724 self.min = self.min.min(other.min);
1725 self.max = self.max.max(other.max);
1726 }
1727
1728 pub fn top_left(&self) -> Vec2 {
1729 vec2(self.min.x, self.max.y)
1730 }
1731}
1732
1733pub trait VecExtensions {
1734 fn flip(&self, width: usize) -> Self;
1735 fn flip_inplace(&mut self, width: usize);
1736}
1737
1738impl<T: Clone> VecExtensions for Vec<T> {
1739 fn flip(&self, width: usize) -> Self {
1740 let mut res = self.clone();
1741 res.flip_inplace(width);
1742 res
1743 }
1744
1745 fn flip_inplace(&mut self, width: usize) {
1746 assert!(self.len() % width == 0);
1747
1748 let height = self.len() / width;
1749
1750 for y in 0..(height / 2) {
1751 for x in 0..width {
1752 self.swap(y * width + x, (height - y - 1) * width + x)
1753 }
1754 }
1755 }
1756}
1757
1758#[test]
1759fn test_vec_flip_h() {
1760 assert_eq!(vec![0, 0, 1, 1].flip(2), vec![1, 1, 0, 0]);
1761 assert_eq!(vec![0, 0, 0, 1, 1, 2].flip(3), vec![1, 1, 2, 0, 0, 0]);
1762 assert_eq!(vec![0, 0, 0, 1, 1, 2].flip(2), vec![1, 2, 0, 1, 0, 0]);
1763
1764 assert_eq!(vec![0, 0, 0, 1, 1, 2, 3, 3].flip(2), vec![
1765 3, 3, 1, 2, 0, 1, 0, 0
1766 ]);
1767 assert_eq!(vec![0, 0, 0, 1, 1, 2, 3, 3].flip(4), vec![
1768 1, 2, 3, 3, 0, 0, 0, 1
1769 ]);
1770}