use crate::schema::ParamType;
use bevy_ecs::prelude::*;
use bevy_input::prelude::{ButtonInput, MouseButton};
use bevy_math::{Vec2, Vec4};
use bevy_render::extract_resource::ExtractResource;
use bevy_time::prelude::Time;
use bevy_window::{PrimaryWindow, Window};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum GlobalKind {
Resolution,
Time,
TimeDelta,
Frame,
PassIndex,
Mouse,
Date,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GlobalsSchema {
pub size: u32,
pub fields: Vec<(GlobalKind, u32)>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GlobalsValues {
pub resolution: Vec2,
pub time: f32,
pub time_delta: f32,
pub frame: u32,
pub pass_index: u32,
pub mouse: Vec4,
pub date: Vec4,
}
#[derive(Resource, ExtractResource, Clone, Copy, Debug, Default)]
pub struct FrameGlobals {
pub time: f32,
pub time_delta: f32,
pub frame: u32,
pub mouse: Vec4,
pub date: Vec4,
}
impl FrameGlobals {
pub fn values(&self, resolution: Vec2, pass_index: u32) -> GlobalsValues {
GlobalsValues {
resolution,
time: self.time,
time_delta: self.time_delta,
frame: self.frame,
pass_index,
mouse: self.mouse,
date: self.date,
}
}
}
impl GlobalKind {
pub const ALL: [Self; 7] = [
Self::Resolution,
Self::Time,
Self::TimeDelta,
Self::Frame,
Self::PassIndex,
Self::Mouse,
Self::Date,
];
pub fn from_name(name: &str) -> Option<Self> {
Self::ALL.into_iter().find(|kind| kind.name() == name)
}
pub fn name(self) -> &'static str {
match self {
Self::Resolution => "resolution",
Self::Time => "time",
Self::TimeDelta => "time_delta",
Self::Frame => "frame",
Self::PassIndex => "pass_index",
Self::Mouse => "mouse",
Self::Date => "date",
}
}
pub fn param_ty(self) -> ParamType {
match self {
Self::Resolution => ParamType::Vec2,
Self::Time | Self::TimeDelta => ParamType::F32,
Self::Frame | Self::PassIndex => ParamType::U32,
Self::Mouse | Self::Date => ParamType::Vec4,
}
}
}
pub fn pack_globals(schema: &GlobalsSchema, values: &GlobalsValues) -> Vec<u8> {
let mut bytes = vec![0u8; schema.size as usize];
for &(kind, offset) in &schema.fields {
let offset = offset as usize;
match kind {
GlobalKind::Resolution => write(&mut bytes, offset, &values.resolution.to_array()),
GlobalKind::Time => write(&mut bytes, offset, &[values.time]),
GlobalKind::TimeDelta => write(&mut bytes, offset, &[values.time_delta]),
GlobalKind::Frame => write(&mut bytes, offset, &[values.frame]),
GlobalKind::PassIndex => write(&mut bytes, offset, &[values.pass_index]),
GlobalKind::Mouse => write(&mut bytes, offset, &values.mouse.to_array()),
GlobalKind::Date => write(&mut bytes, offset, &values.date.to_array()),
}
}
bytes
}
pub fn date_vec4(unix_secs: u64) -> [f32; 4] {
let days = (unix_secs / 86_400) as i64;
let secs = (unix_secs % 86_400) as f32;
let (y, m, d) = civil_from_days(days);
[y as f32, m as f32, d as f32, secs]
}
pub fn update_frame_globals(
mut globals: ResMut<FrameGlobals>,
time: Res<Time>,
buttons: Res<ButtonInput<MouseButton>>,
windows: Query<&Window, With<PrimaryWindow>>,
) {
globals.time = time.elapsed_secs();
globals.time_delta = time.delta_secs();
globals.frame = globals.frame.wrapping_add(1);
let position = windows
.iter()
.next()
.and_then(Window::physical_cursor_position)
.unwrap_or(Vec2::ZERO);
let held = buttons.pressed(MouseButton::Left);
let pressed = buttons.just_pressed(MouseButton::Left);
globals.mouse = Vec4::new(
position.x,
position.y,
held as u32 as f32,
pressed as u32 as f32,
);
globals.date = Vec4::from_array(now_date());
}
#[cfg(not(target_arch = "wasm32"))]
fn now_date() -> [f32; 4] {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|since| date_vec4(since.as_secs()))
.unwrap_or_default()
}
#[cfg(target_arch = "wasm32")]
fn now_date() -> [f32; 4] {
[0.0; 4]
}
fn write<T: bytemuck::NoUninit>(bytes: &mut [u8], offset: usize, value: &[T]) {
let src = bytemuck::cast_slice(value);
bytes[offset..offset + src.len()].copy_from_slice(src);
}
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
(if m <= 2 { y + 1 } else { y }, m, d)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epoch() {
assert_eq!(date_vec4(0), [1970.0, 1.0, 1.0, 0.0]);
}
#[test]
fn day_two() {
assert_eq!(date_vec4(86_400), [1970.0, 1.0, 2.0, 0.0]);
}
#[test]
fn known_dates() {
assert_eq!(date_vec4(1_735_689_600), [2025.0, 1.0, 1.0, 0.0]);
assert_eq!(
date_vec4(1_735_689_600 + 3_661),
[2025.0, 1.0, 1.0, 3_661.0]
);
assert_eq!(date_vec4(11_016 * 86_400), [2000.0, 2.0, 29.0, 0.0]);
}
#[test]
fn pack_offsets() {
let schema = GlobalsSchema {
size: 16,
fields: vec![(GlobalKind::Time, 0), (GlobalKind::Resolution, 8)],
};
let values = GlobalsValues {
time: 2.0,
resolution: Vec2::new(640.0, 480.0),
..Default::default()
};
let bytes = pack_globals(&schema, &values);
assert_eq!(bytes.len(), 16);
let floats: &[f32] = bytemuck::cast_slice(&bytes);
assert_eq!(floats, &[2.0, 0.0, 640.0, 480.0]);
}
#[test]
fn pack_u32_fields() {
let schema = GlobalsSchema {
size: 8,
fields: vec![(GlobalKind::Frame, 0), (GlobalKind::PassIndex, 4)],
};
let values = GlobalsValues {
frame: 7,
pass_index: 2,
..Default::default()
};
let bytes = pack_globals(&schema, &values);
let words: &[u32] = bytemuck::cast_slice(&bytes);
assert_eq!(words, &[7, 2]);
}
}