plasma-prp 0.1.0

Read, write, inspect, and manipulate Plasma engine PRP files used by Myst Online: Uru Live
Documentation
//! plLayerAnimation — time-based UV scroll, opacity pulse, color cycling.
//!
//! C++ ref: plSurface/plLayerAnimation.h/.cpp

use crate::core::uoid::Uoid;

/// Layer animation flags.
#[allow(dead_code)]
pub mod layer_anim_flags {
    pub const UV_SCROLL: u32 = 0x01;
    pub const OPACITY_PULSE: u32 = 0x02;
    pub const COLOR_CYCLE: u32 = 0x04;
    pub const TRANSFORM: u32 = 0x08;
}

/// Parsed plLayerAnimation data.
#[derive(Debug, Clone)]
pub struct LayerAnimationData {
    pub self_key: Option<Uoid>,
    pub anim_name: String,
    pub flags: u32,
    /// UV scroll speed (texels per second).
    pub uv_scroll_speed: [f32; 2],
    /// Opacity range for pulsing.
    pub opacity_min: f32,
    pub opacity_max: f32,
    /// Time convert for driving the animation clock.
    pub atc_key: Option<Uoid>,
}

impl Default for LayerAnimationData {
    fn default() -> Self {
        Self {
            self_key: None,
            anim_name: String::new(),
            flags: 0,
            uv_scroll_speed: [0.0, 0.0],
            opacity_min: 0.0,
            opacity_max: 1.0,
            atc_key: None,
        }
    }
}

/// Parsed plLayerSDLAnimation data — driven by SDL state variable.
#[derive(Debug, Clone)]
pub struct LayerSDLAnimationData {
    pub base: LayerAnimationData,
    pub sdl_var_name: String,
}

/// Evaluate a color at a given time by linear interpolation of keyframes.
/// C++ ref: plLeafController::Interp(time, hsColorRGBA*) delegates to Point3 interp
/// (plController.cpp:221-228). Color keys store [r, g, b] as Point3 (x, y, z).
/// Returns None if no keyframes exist.
#[allow(dead_code)]
pub fn evaluate_color_at_time(keys: &[(f32, [f32; 3])], time: f32) -> Option<[f32; 3]> {
    if keys.is_empty() { return None; }
    if keys.len() == 1 { return Some(keys[0].1); }

    if time <= keys[0].0 {
        return Some(keys[0].1);
    }
    if time >= keys.last().unwrap().0 {
        return Some(keys.last().unwrap().1);
    }
    for w in keys.windows(2) {
        if time >= w[0].0 && time <= w[1].0 {
            let frac = (time - w[0].0) / (w[1].0 - w[0].0).max(0.0001);
            return Some([
                w[0].1[0] + (w[1].1[0] - w[0].1[0]) * frac,
                w[0].1[1] + (w[1].1[1] - w[0].1[1]) * frac,
                w[0].1[2] + (w[1].1[2] - w[0].1[2]) * frac,
            ]);
        }
    }
    Some(keys.last().unwrap().1)
}

/// Evaluate opacity at a given time by linear interpolation of keyframes.
/// C++ ref: plLayerAnimation.cpp:185 — opacity *= 1.e-2f (keyframe values are 0-100 range).
#[allow(dead_code)]
pub fn evaluate_opacity_at_time(keys: &[(f32, f32)], time: f32) -> f32 {
    if keys.is_empty() { return 1.0; }
    if keys.len() == 1 { return keys[0].1 * 0.01; }

    let raw = if time <= keys[0].0 {
        keys[0].1
    } else if time >= keys.last().unwrap().0 {
        keys.last().unwrap().1
    } else {
        let mut val = keys.last().unwrap().1;
        for w in keys.windows(2) {
            if time >= w[0].0 && time <= w[1].0 {
                let frac = (time - w[0].0) / (w[1].0 - w[0].0).max(0.0001);
                val = w[0].1 + (w[1].1 - w[0].1) * frac;
                break;
            }
        }
        val
    };
    (raw * 0.01).clamp(0.0, 1.0)
}