use core::f32::consts::TAU;
const BYTE: f32 = 255.0;
pub fn pack_flame_tag(phase: f32, height: f32, brightness: f32, max_height: f32) -> u32 {
let unorm16 = |v: f32| (v.clamp(0.0, 1.0) * 65535.0).round() as u32;
let unorm8 = |v: f32| (v.clamp(0.0, 1.0) * BYTE).round() as u32;
let height_share = if max_height > 0.0 { height / max_height } else { 0.0 };
unorm16(height_share) << 16 | unorm8(phase.rem_euclid(TAU) / TAU) << 8 | unorm8(brightness)
}
pub fn unpack_flame_tag(tag: u32, max_height: f32) -> (f32, f32, f32) {
let height = (tag >> 16) as f32 / 65535.0;
let phase = ((tag >> 8) & 0xff) as f32 / BYTE;
let brightness = (tag & 0xff) as f32 / BYTE;
(phase * TAU, height * max_height.max(0.0), brightness)
}
pub fn set_flame_brightness(tag: u32, brightness: f32) -> u32 {
let unorm8 = (brightness.clamp(0.0, 1.0) * BYTE).round() as u32;
(tag & !0xff) | unorm8
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
const PHASE_STEP: f32 = TAU / 256.0;
#[test]
fn a_flame_tag_round_trips_its_phase_height_and_brightness() {
for (phase, height, brightness) in
[(0.0, 0.0, 0.0), (1.0, 2.5, 0.85), (3.0, 8.0, 1.0), (TAU - 0.001, 0.35, 0.70), (-2.0, 1.5, 0.93)]
{
let tag = pack_flame_tag(phase, height, brightness, 8.0);
let (read_phase, read_height, read_brightness) = unpack_flame_tag(tag, 8.0);
let expected = phase.rem_euclid(TAU);
assert!(
(read_phase - expected).abs() <= PHASE_STEP / 2.0 + f32::EPSILON,
"phase {phase} read back as {read_phase}, wanted {expected}"
);
assert!((read_height - height).abs() < 1e-3, "height {height} read back as {read_height}");
assert!(
(read_brightness - brightness).abs() <= 0.5 / BYTE + f32::EPSILON,
"brightness {brightness} read back as {read_brightness}"
);
}
}
#[test]
fn the_three_fields_do_not_bleed_into_each_other() {
let tag = pack_flame_tag(0.0, 8.0, 0.0, 8.0);
assert_eq!(tag >> 16, 65535);
assert_eq!(tag & 0xffff, 0);
let bright = pack_flame_tag(0.0, 8.0, 1.0, 8.0);
assert_eq!(bright >> 16, tag >> 16);
assert_eq!(bright & 0xff, 255);
assert_eq!((bright >> 8) & 0xff, 0);
}
#[test]
fn a_height_over_the_maximum_packs_full_and_a_zero_maximum_packs_nothing() {
assert_eq!(pack_flame_tag(0.0, 20.0, 1.0, 8.0) >> 16, 65535);
assert_eq!(pack_flame_tag(0.0, 4.0, 1.0, 0.0) >> 16, 0);
assert_eq!(pack_flame_tag(0.0, 4.0, 1.0, -3.0) >> 16, 0);
assert_eq!(unpack_flame_tag(pack_flame_tag(0.0, 4.0, 1.0, 0.0), 0.0).1, 0.0);
}
#[test]
fn a_brightness_outside_its_range_clamps() {
assert_eq!(pack_flame_tag(0.0, 1.0, 3.0, 8.0) & 0xff, 255);
assert_eq!(pack_flame_tag(0.0, 1.0, -3.0, 8.0) & 0xff, 0);
}
}