#[cfg(not(feature = "std"))]
use crate::math::F32Ext as _;
pub const NORMALIZED_MAX: i32 = 65_535;
pub const FRAME_INTERVAL_MS: u32 = 33;
pub const DEFAULT_DURATION_MS: u32 = 250;
pub const PORT_HOLE_DURATION_MS: u32 = 6 * FRAME_INTERVAL_MS;
pub const SHUTTER_DURATION_MS: u32 = 6 * FRAME_INTERVAL_MS;
pub const MOOOK_DURATION_MS: u32 =
(MOOOK_IN.len() as u32 + MOOOK_OUT.len() as u32) * FRAME_INTERVAL_MS;
const MOOOK_IN: [i32; 3] = [0, 1, 20];
const MOOOK_OUT: [i32; 4] = [4, 2, 1, 0];
#[inline]
pub fn timing_scaled(time_normalized: i32, interval_start: i32, interval_end: i32) -> i32 {
if interval_end == interval_start {
return NORMALIZED_MAX;
}
let result = time_normalized - interval_start;
(result * NORMALIZED_MAX) / (interval_end - interval_start)
}
#[inline]
pub fn timing_clip(progress: i32) -> i32 {
progress.clamp(0, NORMALIZED_MAX)
}
#[inline]
pub fn timing_half_phase(progress: f32) -> (f32, bool) {
if progress < 0.5 {
(progress * 2.0, true)
} else {
((progress - 0.5) * 2.0, false)
}
}
#[inline]
pub fn timing_shutter_phase(progress: f32) -> (f32, bool) {
const FIRST: f32 = 2.0 / 6.0;
if progress < FIRST {
(progress / FIRST, true)
} else {
((progress - FIRST) / (1.0 - FIRST), false)
}
}
#[inline]
pub fn moook_in_duration_ms() -> u32 {
MOOOK_IN.len() as u32 * FRAME_INTERVAL_MS
}
#[inline]
pub fn moook_out_duration_ms() -> u32 {
MOOOK_OUT.len() as u32 * FRAME_INTERVAL_MS
}
#[inline]
pub fn moook_duration_ms() -> u32 {
moook_in_duration_ms() + moook_out_duration_ms()
}
#[inline]
pub fn moook_soft_duration_ms(mid_frames: i32) -> u32 {
moook_duration_ms() + mid_frames.max(0) as u32 * FRAME_INTERVAL_MS
}
fn interpolate_linear(normalized: i32, from: i64, to: i64) -> i64 {
from + (normalized as i64 * (to - from)) / NORMALIZED_MAX as i64
}
fn interpolate_moook_frames(
normalized: i32,
from: i64,
to: i64,
frames_in: &[i32],
frames_out: &[i32],
mid_frames: i32,
bounce_back: bool,
) -> i64 {
let direction = if from == to {
0
} else if from < to {
1
} else {
-1
};
if direction == 0 {
return from;
}
let direction_out = if bounce_back { direction } else { -direction };
let num_in = frames_in.len() as i32;
let num_out = frames_out.len() as i32;
let num_total = num_in + mid_frames + num_out;
if num_total <= 0 {
return if normalized >= NORMALIZED_MAX {
to
} else {
from
};
}
let mut frame_idx = ((normalized as i64 * num_total as i64
+ (NORMALIZED_MAX as i64 / (2 * num_total as i64)))
/ NORMALIZED_MAX as i64) as i32;
frame_idx = frame_idx.clamp(0, num_total - 1);
if normalized >= NORMALIZED_MAX {
return to;
}
if frame_idx < 0 {
return from;
}
if frame_idx < num_in {
return from + direction as i64 * frames_in[frame_idx as usize] as i64;
}
if frame_idx < num_in + mid_frames && mid_frames > 0 {
let shifted =
normalized - ((num_in as i64 * NORMALIZED_MAX as i64) / num_total as i64) as i32;
let mid_normalized = ((num_total as i64 * shifted as i64) / mid_frames as i64) as i32;
let from_mid = from + direction as i64 * frames_in[(num_in - 1) as usize] as i64;
let to_mid = to + direction_out as i64 * frames_out[0] as i64;
return interpolate_linear(mid_normalized, from_mid, to_mid);
}
let out_idx = frame_idx - num_in - mid_frames;
to + direction_out as i64 * frames_out[out_idx as usize] as i64
}
pub fn interpolate_moook(normalized: i32, from: i64, to: i64) -> i64 {
interpolate_moook_frames(normalized, from, to, &MOOOK_IN, &MOOOK_OUT, 0, true)
}
pub fn interpolate_moook_soft(normalized: i32, from: i64, to: i64, mid_frames: i32) -> i64 {
interpolate_moook_frames(
normalized, from, to, &MOOOK_IN, &MOOOK_OUT, mid_frames, true,
)
}
pub fn moook_curve(t: f32) -> f32 {
let normalized = (t.clamp(0.0, 1.0) * NORMALIZED_MAX as f32).round() as i32;
let v = interpolate_moook(normalized, 0, NORMALIZED_MAX as i64);
v as f32 / NORMALIZED_MAX as f32
}
pub fn table_ease_in_sample(t: f32) -> f32 {
const TABLE: [u16; 33] = [
0, 64, 256, 576, 1024, 1600, 2304, 3136, 4096, 5184, 6400, 7744, 9216, 10816, 12544, 14400,
16384, 18496, 20736, 23104, 25600, 28224, 30976, 33856, 36864, 40000, 43264, 46656, 50176,
53824, 57600, 61504, 65535,
];
ease_table_sample(t, &TABLE)
}
fn ease_table_sample(t: f32, table: &[u16]) -> f32 {
if table.is_empty() {
return t;
}
let progress = (t.clamp(0.0, 1.0) * NORMALIZED_MAX as f32).round() as i32;
if progress <= 0 {
return 0.0;
}
if progress >= NORMALIZED_MAX {
return 1.0;
}
let max_entry = table.len() - 1;
let stride = NORMALIZED_MAX / max_entry as i32;
let index = (progress * max_entry as i32) / NORMALIZED_MAX;
let from = table[index as usize] as i64;
let delta = table[(index + 1) as usize] as i64 - from;
let v = from + (delta * (progress - index * stride) as i64) / stride as i64;
v as f32 / NORMALIZED_MAX as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn moook_reaches_endpoints() {
assert_eq!(interpolate_moook(0, 0, 100), 0);
assert_eq!(interpolate_moook(NORMALIZED_MAX, 0, 100), 100);
}
#[test]
fn timing_scaled_maps_interval() {
let mid = timing_scaled(NORMALIZED_MAX / 2, 0, NORMALIZED_MAX);
assert!((mid - NORMALIZED_MAX / 2).abs() <= 1);
}
#[test]
fn moook_curve_is_monotonic_overall() {
let a = moook_curve(0.0);
let b = moook_curve(1.0);
assert!((a - 0.0).abs() < 0.01);
assert!((b - 1.0).abs() < 0.01);
}
}