use fixed::types::{I16F16, I32F32, U16F16};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Rounding {
Floor,
Ceil,
Nearest,
}
pub trait UnitValue: Copy + 'static {
fn zero() -> Self;
fn one() -> Self;
fn to_index(self) -> usize;
fn from_time_frac(elapsed_ms: u32, duration_ms: u32) -> Self;
fn to_time_offset(self, duration_ms: u32) -> u32;
fn lerp_u16(self, a: u16, b: u16) -> u16;
fn inv_lerp_u16(a: u16, b: u16, target: u16) -> Self;
}
impl UnitValue for u8 {
fn zero() -> Self {
0
}
fn one() -> Self {
255
}
fn to_index(self) -> usize {
self as usize
}
fn from_time_frac(elapsed_ms: u32, duration_ms: u32) -> Self {
if duration_ms == 0 || elapsed_ms >= duration_ms {
return 255;
}
if elapsed_ms == 0 {
return 0;
}
(u64::from(elapsed_ms) * 255 / u64::from(duration_ms)) as u8
}
fn to_time_offset(self, duration_ms: u32) -> u32 {
if duration_ms == 0 {
return 0;
}
let v = u32::from(self);
let k = duration_ms / 255;
let r = duration_ms % 255;
v * k + (v * r).div_ceil(255)
}
fn lerp_u16(self, a: u16, b: u16) -> u16 {
let a_fix = I32F32::from_num(a);
let b_fix = I32F32::from_num(b);
let t = I32F32::from_num(self) / I32F32::from_num(255);
let result = a_fix + t * (b_fix - a_fix);
result.to_num::<i64>().clamp(0, u16::MAX as i64) as u16
}
fn inv_lerp_u16(a: u16, b: u16, target: u16) -> Self {
if a == b {
return 255;
}
let target = target.clamp(a.min(b), a.max(b));
let delta = I32F32::from_num(b) - I32F32::from_num(a);
let numer = I32F32::from_num(target) - I32F32::from_num(a);
let frac = numer / delta;
let w = (frac * I32F32::from_num(255)).ceil();
w.to_num::<i32>().clamp(0, 255) as u8
}
}
impl UnitValue for u16 {
fn zero() -> Self {
0
}
fn one() -> Self {
65535
}
fn to_index(self) -> usize {
self as usize
}
fn from_time_frac(elapsed_ms: u32, duration_ms: u32) -> Self {
if duration_ms == 0 || elapsed_ms >= duration_ms {
return 65535;
}
if elapsed_ms == 0 {
return 0;
}
(u64::from(elapsed_ms) * 65535 / u64::from(duration_ms)) as u16
}
fn to_time_offset(self, duration_ms: u32) -> u32 {
if duration_ms == 0 {
return 0;
}
let v = u32::from(self);
let k = duration_ms / 65535;
let r = duration_ms % 65535;
v * k + (v * r).div_ceil(65535)
}
fn lerp_u16(self, a: u16, b: u16) -> u16 {
let a_fix = I32F32::from_num(a);
let b_fix = I32F32::from_num(b);
let t = I32F32::from_num(self) / I32F32::from_num(65535);
let result = a_fix + t * (b_fix - a_fix);
result.to_num::<i64>().clamp(0, u16::MAX as i64) as u16
}
fn inv_lerp_u16(a: u16, b: u16, target: u16) -> Self {
if a == b {
return 65535;
}
let target = target.clamp(a.min(b), a.max(b));
let numer = u64::from(target.abs_diff(a));
let denom = u64::from(a.abs_diff(b));
let w = numer.saturating_mul(65535).div_ceil(denom);
w.min(65535) as u16
}
}
fn weight_frac(w: u8) -> I16F16 {
I16F16::from_num(w) / I16F16::from_num(255)
}
pub fn lerp_u8(a: u8, b: u8, w: u8) -> u8 {
let a_fix = I16F16::from_num(a);
let b_fix = I16F16::from_num(b);
let t = weight_frac(w);
let result = a_fix + t * (b_fix - a_fix);
result.to_num::<i32>().clamp(0, 255) as u8
}
pub fn lerp_u16(a: u16, b: u16, w: u8) -> u16 {
let a_fix = I32F32::from_num(a);
let b_fix = I32F32::from_num(b);
let t = I32F32::from_num(w) / I32F32::from_num(255);
let result = a_fix + t * (b_fix - a_fix);
result.to_num::<i64>().clamp(0, u16::MAX as i64) as u16
}
pub fn map_u8_to_u16(w: u8, max: u16) -> u16 {
let t = U16F16::from_num(w) / U16F16::from_num(255);
let result = t * U16F16::from_num(max);
result.to_num::<u32>().min(u16::MAX as u32) as u16
}
pub fn quantize(value: u16, step: u16, rounding: Rounding) -> u16 {
let v = u32::from(value);
let s = u32::from(step);
let result = match rounding {
Rounding::Floor => (v / s) * s,
Rounding::Ceil => v.div_ceil(s) * s,
Rounding::Nearest => ((v + s / 2) / s) * s,
};
result.min(u32::from(u16::MAX)) as u16
}
pub fn next_target_value(current: u16, end: u16, step: u16, increasing: bool) -> u16 {
if increasing {
let next = current.saturating_add(step);
next.min(end)
} else {
let next = current.saturating_sub(step);
next.max(end)
}
}
#[cfg(test)]
fn first_raw_where(step: u16, rounding: Rounding, pred: impl Fn(u16) -> bool) -> Option<u16> {
if pred(quantize(0, step, rounding)) {
return Some(0);
}
if !pred(quantize(u16::MAX, step, rounding)) {
return None;
}
let mut lo = 0u16;
let mut hi = u16::MAX;
while lo + 1 < hi {
let mid = lo + (hi - lo) / 2;
if pred(quantize(mid, step, rounding)) {
hi = mid;
} else {
lo = mid;
}
}
Some(hi)
}
#[cfg(test)]
pub(crate) fn next_raw_quantization_boundary(
current_val: u16,
step: u16,
rounding: Rounding,
increasing: bool,
) -> u16 {
if increasing {
first_raw_where(step, rounding, |q| q > current_val).unwrap_or(u16::MAX)
} else {
first_raw_where(step, rounding, |q| q >= current_val)
.unwrap_or(0)
.saturating_sub(1)
}
}