use std::collections::VecDeque;
use crate::prelude::*;
#[derive(Clone, Debug, HasSchema)]
#[repr(C)]
pub struct Camera {
pub size: CameraSize,
pub active: bool,
pub viewport: Maybe<Viewport>,
pub priority: i32,
}
#[derive(HasSchema, Debug, Clone, Copy)]
#[repr(C, u8)]
pub enum CameraSize {
FixedHeight(f32),
FixedWidth(f32),
}
impl Default for CameraSize {
fn default() -> Self {
Self::FixedHeight(400.)
}
}
#[derive(Clone, Copy, Debug, HasSchema, Default)]
#[repr(C)]
pub struct Viewport {
pub position: UVec2,
pub size: UVec2,
pub depth_min: f32,
pub depth_max: f32,
}
impl Default for Camera {
fn default() -> Self {
Self {
active: true,
viewport: Unset,
priority: 0,
size: default(),
}
}
}
#[derive(Deref, DerefMut, Clone, Copy, HasSchema, Default)]
pub struct ClearColor(pub Color);
pub fn spawn_default_camera(
entities: &mut Entities,
transforms: &mut CompMut<Transform>,
cameras: &mut CompMut<Camera>,
) -> Entity {
let ent = entities.create();
cameras.insert(ent, default());
transforms.insert(ent, Transform::from_translation(Vec3::new(0., 0., 1000.)));
ent
}
pub fn plugin(session: &mut Session) {
session
.stages
.add_system_to_stage(CoreStage::Last, apply_shake)
.add_system_to_stage(CoreStage::Last, apply_trauma)
.add_system_to_stage(CoreStage::Last, decay_trauma);
}
#[derive(Clone, HasSchema)]
pub struct ShakeNoise(pub noise::permutationtable::PermutationTable);
impl Default for ShakeNoise {
fn default() -> Self {
Self(noise::permutationtable::PermutationTable::new(0))
}
}
#[derive(Clone, HasSchema, Debug, Copy)]
#[repr(C)]
pub struct CameraShake {
pub trauma: f32,
pub max_angle_rad: f32,
pub max_offset: Vec2,
pub decay_rate: f32,
pub speed: f32,
pub center: Vec3,
}
impl Default for CameraShake {
fn default() -> Self {
Self {
trauma: 0.0,
max_angle_rad: 90.0,
max_offset: Vec2::splat(100.0),
decay_rate: 0.5,
speed: 1.5,
center: Vec3::ZERO,
}
}
}
impl CameraShake {
pub fn new(max_angle_deg: f32, max_offset: Vec2, speed: f32, decay_rate: f32) -> Self {
Self {
max_angle_rad: max_angle_deg.to_radians(),
max_offset,
decay_rate,
speed,
..default()
}
}
pub fn with_trauma(
trauma: f32,
max_angle_deg: f32,
max_offset: Vec2,
speed: f32,
decay_rate: f32,
) -> Self {
let mut shake = Self::new(max_angle_deg, max_offset, speed, decay_rate);
shake.trauma = trauma.clamp(0.0, 1.0);
shake
}
pub fn add_trauma(&mut self, value: f32) {
self.trauma += value;
if 1.0 < self.trauma {
self.trauma = 1.0;
}
}
}
#[derive(Default, Clone, HasSchema)]
pub struct CameraTraumaEvents {
pub queue: VecDeque<f32>,
}
impl CameraTraumaEvents {
pub fn send(&mut self, trauma: f32) {
self.queue.push_back(trauma);
}
}
fn apply_trauma(
entities: Res<Entities>,
mut camera_shakes: CompMut<CameraShake>,
mut trauma_events: ResMutInit<CameraTraumaEvents>,
) {
for (_ent, camera_shake) in entities.iter_with(&mut camera_shakes) {
camera_shake.add_trauma(
trauma_events
.queue
.iter()
.fold(0.0, |acc, trauma| acc + trauma),
);
}
trauma_events.queue.clear();
}
fn decay_trauma(entities: Res<Entities>, mut camera_shakes: CompMut<CameraShake>, time: Res<Time>) {
for (_ent, shake) in entities.iter_with(&mut camera_shakes) {
shake.trauma = 0.0f32.max(shake.trauma - shake.decay_rate * time.delta_seconds())
}
}
fn apply_shake(
entities: Res<Entities>,
mut transforms: CompMut<Transform>,
camera_shakes: Comp<CameraShake>,
time: Res<Time>,
noise: ResInit<ShakeNoise>,
) {
macro_rules! offset_noise {
($offset:expr, $shake_speed:expr) => {
perlin_noise::perlin_1d(
((time.elapsed_seconds() + $offset) * $shake_speed * 0.001).into(),
&noise.0,
) as f32
};
}
for (_ent, (shake, transform)) in entities.iter_with((&camera_shakes, &mut transforms)) {
(transform.rotation, transform.translation) = if shake.trauma > 0.0 {
let sqr_trauma = shake.trauma * shake.trauma;
let rotation = Quat::from_axis_angle(
Vec3::Z,
sqr_trauma * offset_noise!(0.0, shake.speed) * shake.max_angle_rad,
);
let x_offset = sqr_trauma * offset_noise!(1.0, shake.speed) * shake.max_offset.x;
let y_offset = sqr_trauma * offset_noise!(2.0, shake.speed) * shake.max_offset.y;
(rotation, shake.center + Vec3::new(x_offset, y_offset, 0.0))
} else {
(Quat::IDENTITY, shake.center)
}
}
}
mod perlin_noise {
#[inline(always)]
pub fn perlin_1d<NH>(point: f64, hasher: &NH) -> f64
where
NH: noise::permutationtable::NoiseHasher + ?Sized,
{
const SCALE_FACTOR: f64 = 0.5;
#[inline(always)]
#[rustfmt::skip]
fn gradient_dot_v(perm: usize, point: f64) -> f64 {
let x = point;
match perm & 0b1 {
0 => x, 1 => -x, _ => unreachable!(),
}
}
let floored = point.floor();
let corner = floored as isize;
let distance = point - floored;
macro_rules! call_gradient(
($x_offset:expr) => {
{
gradient_dot_v(
hasher.hash(&[corner + $x_offset]),
distance - $x_offset as f64
)
}
}
);
let g0 = call_gradient!(0);
let g1 = call_gradient!(1);
let u = map_quintic(distance);
let unscaled_result = linear_interpolation(u, g0, g1);
let scaled_result = unscaled_result * SCALE_FACTOR;
scaled_result.clamp(-1.0, 1.0)
}
#[inline(always)]
fn linear_interpolation(u: f64, g0: f64, g1: f64) -> f64 {
let k0 = g0;
let k1 = g1 - g0;
k0 + k1 * u
}
fn map_quintic(n: f64) -> f64 {
let x = n.clamp(0.0, 1.0);
x * x * x * (x * (x * 6.0 - 15.0) + 10.0)
}
}