use bevy::prelude::*;
use crate::sequence::Shake;
const PHI: f32 = 1.618_034;
fn wobble(t: f32, hz: f32, phase: f32) -> f32 {
let x = t * hz * std::f32::consts::TAU + phase;
(x.sin() + (x * PHI).sin() * 0.5 + (x * PHI * PHI).sin() * 0.25) / 1.75
}
pub(crate) fn shake_offset(shake: &Shake, t: f32) -> (Vec3, Quat) {
let seed = shake.seed as f32 * 12.9898;
let amp = shake.amplitude_deg.to_radians();
let hz = shake.frequency_hz.max(0.01);
let yaw = wobble(t, hz, seed) * amp;
let pitch = wobble(t, hz * 1.13, seed + 7.31) * amp;
let roll = wobble(t, hz * 0.87, seed + 3.77) * amp * 0.5;
let pos = Vec3::new(
wobble(t, hz * 0.71, seed + 1.13),
wobble(t, hz * 0.79, seed + 9.02),
0.0,
) * shake.pos_amplitude;
(pos, Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll))
}
#[cfg(test)]
mod tests {
use super::*;
fn shake(seed: u32) -> Shake {
Shake {
amplitude_deg: 1.5,
frequency_hz: 9.0,
seed,
pos_amplitude: 0.02,
}
}
#[test]
fn same_seed_same_take() {
for t in [0.0, 0.37, 2.11, 8.4] {
assert_eq!(shake_offset(&shake(7), t), shake_offset(&shake(7), t));
}
}
#[test]
fn different_seeds_diverge_and_stay_bounded() {
let mut differs = false;
for i in 0..100 {
let t = i as f32 * 0.05;
let (pa, qa) = shake_offset(&shake(1), t);
let (pb, qb) = shake_offset(&shake(2), t);
differs |= pa.distance(pb) > 1e-4 || qa.angle_between(qb) > 1e-4;
assert!(pa.length() <= 0.03, "position blew past its amplitude");
assert!(qa.to_axis_angle().1 <= 3.0_f32.to_radians());
}
assert!(differs);
}
}