comfy_core/
lib.rs

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    // TODO: maybe hide under a feature flag to optionally track entity creation location?
227    // pub backtrace: Backtrace,
228}
229
230impl Name {
231    pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
232        Self {
233            name: name.into(),
234            // backtrace: Backtrace::new()
235        }
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
400// pub trait EntityExtensions {
401//     fn u128(&self) -> u128;
402//     fn has<T: Send + Sync + 'static>(&self, world: &World) -> bool;
403// }
404//
405// impl EntityExtensions for Entity {
406//     fn u128(&self) -> u128 {
407//         self.to_bits().get() as u128
408//     }
409//
410//     fn has<T: Send + Sync + 'static>(&self, world: &World) -> bool {
411//         world.get::<&T>(*self).is_ok()
412//     }
413// }
414
415pub 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        // impl
492        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    // TODO: Rename to Add
572    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// #[cfg(feature = "tracy")]
670// type SpanType = tracy_client::Span;
671// #[cfg(not(feature = "tracy"))]
672// type SpanType = ();
673//
674// pub fn span(
675//     #[cfg(feature = "tracy")] name: &str,
676//     #[cfg(not(feature = "tracy"))] _name: &str,
677// ) -> Option<SpanType> {
678//     cfg_if::cfg_if! {
679//         if #[cfg(feature = "tracy")] {
680//             Some(tracy_client::span!(name, 0))
681//         } else {
682//             None
683//         }
684//     }
685// }
686
687// None::<()>
688// cfg_if::cfg_if! {
689//         if #[cfg(feature = "tracy")] {
690//         Some(tracy_client::span!($name, 0))
691//     } else {
692//         None
693//     }
694// }
695
696#[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 {
844    //     let amount = 1.0 - amount;
845    //     Color::new(self.r * amount, self.g * amount, self.b * amount, self.a)
846    // }
847
848    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
917// impl Into<egui::Color32> for Color {
918//     fn into(self) -> egui::Color32 {
919//         egui::Color32::from_rgba_unmultiplied(
920//             (self.r * 255.0) as u8,
921//             (self.g * 255.0) as u8,
922//             (self.b * 255.0) as u8,
923//             (self.a * 255.0) as u8,
924//         )
925//     }
926// }
927
928pub 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    // TODO: rename to something like "unchecked_id"
956    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
1016// Comfy colors
1017// pub const COMFY_BLUE: Color = Color::rgb(0.16, 0.04, 0.02);
1018// pub const COMFY_BLUE: Color = Color::rgb(0.84, 0.96, 0.98);
1019pub 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
1024// pub const PINK: Color = Color::rgb(1.0, 0.08, 0.58);
1025// pub const PURPLE: Color = Color::rgb(0.5, 0.0, 0.5);
1026// pub const RED: Color = Color::rgb(1.0, 0.0, 0.0);
1027// pub const VIOLET: Color = Color::rgb(0.93, 0.51, 0.93);
1028// pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
1029// pub const YELLOW: Color = Color::rgb(1.0, 1.0, 0.0);
1030// pub const BEIGE: Color = Color::rgb(0.96, 0.96, 0.86);
1031// pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0);
1032// pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0);
1033// pub const GOLD: Color = Color::rgb(1.0, 0.84, 0.0);
1034// pub const GRAY: Color = Color::rgb(0.5, 0.5, 0.5);
1035// pub const GREEN: Color = Color::rgb(0.0, 1.0, 0.0);
1036// pub const MAROON: Color = Color::rgb(0.5, 0.0, 0.0);
1037// pub const NONE: Color = Color::rgba(0.0, 0.0, 0.0, 0.0);
1038// pub const ORANGE: Color = Color::rgb(1.0, 0.65, 0.0);
1039
1040pub 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            // Image is wider than the rect, so fit to the rect's width
1069            self.fit_width(size.x as u32)
1070        } else {
1071            // Image is taller than the rect, so fit to the rect's height
1072            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// pub trait ColorExtensions {
1130//     fn with_alpha(&self, alpha: f32) -> Self;
1131//     fn to_egui(&self) -> egui::Color32;
1132//     fn is_similar(&self, other: Color, tolerance: f32) -> bool;
1133//     fn is_similar_no_alpha(&self, other: Color, tolerance: f32) -> bool;
1134//     fn from_u8(r: u8, g: u8, b: u8, a: u8) -> Self;
1135// }
1136
1137#[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            // rotation: self.rotation,
1250            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 = matrix.x_axis.angle_between(Vec3::X);
1271        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            // Clear the log
1325            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        // env_logger::builder().format_timestamp_millis().init();
1341
1342        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
1367// pub const fn from_hex(hex: u32) -> Color {
1368//     let bytes: [u8; 4] = hex.to_be_bytes();
1369//
1370//     color_u8!(bytes[1], bytes[2], bytes[3], 255)
1371// }
1372
1373pub 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)); // Ensure that the variance is non-negative
1537
1538        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
1558// pub struct MovingStats {
1559//     size: usize,
1560//     queue: VecDeque<f32>,
1561//     sum: f32,
1562//     sq_sum: f32,
1563// }
1564//
1565// impl MovingStats {
1566//     pub fn new(size: usize) -> Self {
1567//         MovingStats {
1568//             size,
1569//             queue: VecDeque::with_capacity(size),
1570//             sum: 0.0,
1571//             sq_sum: 0.0,
1572//         }
1573//     }
1574//
1575//     pub fn next(&mut self, val: f32) -> (f32, f32) {
1576//         if self.queue.len() == self.size {
1577//             let old_val = self.queue.pop_front().unwrap();
1578//             self.sum -= old_val;
1579//             self.sq_sum -= old_val * old_val;
1580//         }
1581//
1582//         self.queue.push_back(val);
1583//         self.sum += val;
1584//         self.sq_sum += val * val;
1585//
1586//         let mean = self.sum / self.queue.len() as f32;
1587//         let variance = (self.sq_sum / self.queue.len() as f32) - (mean * mean);
1588//         let std_dev = f32::sqrt(variance.max(0.0)); // Ensure that the variance is non-negative
1589//         (mean, std_dev)
1590//     }
1591// }
1592
1593pub 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    // Create the transformation matrix
1625    let transform = Mat3::from_translation(rect_center) *
1626        Mat3::from_rotation_z(rect_rotation);
1627
1628    // Invert the transformation matrix to apply the reverse transformations to the point
1629    let inv_transform = transform.inverse();
1630
1631    // Apply the transformations
1632    let new_point = inv_transform.transform_point2(point);
1633
1634    // Check if the point is within the rectangle
1635    (new_point.x.abs() <= rect_size.x / 2.0) &&
1636        (new_point.y.abs() <= rect_size.y / 2.0)
1637}
1638
1639// pub fn rescale(
1640//     value: i32,
1641//     from: Range<i32>,
1642//     to: Range<i32>,
1643// ) -> f32 {
1644//     let value = value.max(from.start).min(from.end);
1645//     let from_range = from.end - from.start;
1646//     let to_range = to.end - to.start;
1647//
1648//     to.start as f32 +
1649//         (value - from.start) as f32 / from_range as f32 * to_range as f32
1650// }
1651
1652pub 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}