use std::io::Read;
use anyhow::Result;
use crate::resource::prp::PlasmaRead;
#[allow(dead_code)]
pub mod atc_flags {
pub const NONE: u32 = 0x00;
pub const STOPPED: u32 = 0x01;
pub const LOOP: u32 = 0x02;
pub const BACKWARDS: u32 = 0x04;
pub const WRAP: u32 = 0x08;
pub const NEEDS_RESET: u32 = 0x10;
pub const EASING_IN: u32 = 0x20;
pub const FORCED_MOVE: u32 = 0x40;
pub const NO_CALLBACKS: u32 = 0x80;
pub const FLAGS_MASK: u32 = 0xFF;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EaseType {
None = 0,
ConstAccel = 1,
Spline = 2,
}
#[derive(Debug, Clone)]
pub struct EaseCurve {
pub min_length: f32,
pub max_length: f32,
pub norm_length: f32,
pub start_speed: f32,
pub speed: f32,
pub begin_world_time: f64,
pub spline_coefs: Option<[f32; 4]>,
}
impl EaseCurve {
pub fn read_creatable(reader: &mut impl Read) -> Result<Option<Self>> {
let class_idx = reader.read_u16()?;
if class_idx == 0x8000 {
return Ok(None);
}
let min_length = reader.read_f32()?;
let max_length = reader.read_f32()?;
let norm_length = reader.read_f32()?;
let start_speed = reader.read_f32()?;
let speed = reader.read_f32()?;
let begin_world_time = read_f64(reader)?;
let spline_coefs =
if class_idx == crate::core::class_index::ClassIndex::PL_SPLINE_EASE_CURVE {
Some([
reader.read_f32()?,
reader.read_f32()?,
reader.read_f32()?,
reader.read_f32()?,
])
} else {
None
};
Ok(Some(Self {
min_length,
max_length,
norm_length,
start_speed,
speed,
begin_world_time,
spline_coefs,
}))
}
}
#[derive(Debug, Clone)]
pub struct AnimTimeConvertData {
pub flags: u32,
pub begin: f32,
pub end: f32,
pub loop_end: f32,
pub loop_begin: f32,
pub speed: f32,
pub ease_in: Option<EaseCurve>,
pub ease_out: Option<EaseCurve>,
pub speed_ease: Option<EaseCurve>,
pub current_anim_time: f32,
pub last_eval_world_time: f64,
pub stop_points: Vec<f32>,
}
impl AnimTimeConvertData {
pub fn read(reader: &mut impl Read) -> Result<Self> {
let flags = reader.read_u32()?;
let begin = reader.read_f32()?;
let end = reader.read_f32()?;
let loop_end = reader.read_f32()?;
let loop_begin = reader.read_f32()?;
let speed = reader.read_f32()?;
let ease_in = EaseCurve::read_creatable(reader)?;
let ease_out = EaseCurve::read_creatable(reader)?;
let speed_ease = EaseCurve::read_creatable(reader)?;
let current_anim_time = reader.read_f32()?;
let last_eval_world_time = read_f64(reader)?;
let num_callbacks = reader.read_u32()?;
for _ in 0..num_callbacks {
let cb_class = reader.read_u16()?;
if cb_class != 0x8000 {
skip_event_callback_msg(reader)?;
}
}
let num_stop_points = reader.read_u32()?;
let mut stop_points = Vec::with_capacity(num_stop_points as usize);
for _ in 0..num_stop_points {
stop_points.push(reader.read_f32()?);
}
Ok(Self {
flags,
begin,
end,
loop_end,
loop_begin,
speed,
ease_in,
ease_out,
speed_ease,
current_anim_time,
last_eval_world_time,
stop_points,
})
}
pub fn is_stopped(&self) -> bool {
self.flags & atc_flags::STOPPED != 0
}
pub fn is_looping(&self) -> bool {
self.flags & atc_flags::LOOP != 0
}
pub fn is_backwards(&self) -> bool {
self.flags & atc_flags::BACKWARDS != 0
}
pub fn duration(&self) -> f32 {
self.end - self.begin
}
pub fn world_to_anim_time(&self, world_time: f64) -> f32 {
if self.is_stopped() {
return self.current_anim_time;
}
let elapsed = (world_time - self.last_eval_world_time) as f32 * self.speed;
let mut t = self.current_anim_time + elapsed;
if self.is_looping() {
let loop_len = self.loop_end - self.loop_begin;
if loop_len > 0.0 {
while t > self.loop_end {
t -= loop_len;
}
while t < self.loop_begin {
t += loop_len;
}
}
} else {
t = t.clamp(self.begin, self.end);
}
t
}
}
fn read_f64(reader: &mut impl Read) -> Result<f64> {
let mut buf = [0u8; 8];
reader.read_exact(&mut buf)?;
Ok(f64::from_le_bytes(buf))
}
fn skip_event_callback_msg(reader: &mut impl Read) -> Result<()> {
use crate::core::uoid::read_key_uoid;
let _ = read_key_uoid(reader)?; let num_recv = reader.read_u32()?;
for _ in 0..num_recv {
let _ = read_key_uoid(reader)?;
}
let _ = read_f64(reader)?; let _ = reader.read_u32()?;
let _ = reader.read_f32()?; let _ = reader.read_u16()?; let _ = reader.read_i16()?; let _ = reader.read_u8()?; let _ = reader.read_i16()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_world_to_anim_time_stopped() {
let atc = AnimTimeConvertData {
flags: atc_flags::STOPPED,
begin: 0.0,
end: 5.0,
loop_begin: 0.0,
loop_end: 5.0,
speed: 1.0,
ease_in: None,
ease_out: None,
speed_ease: None,
current_anim_time: 2.5,
last_eval_world_time: 0.0,
stop_points: Vec::new(),
};
assert_eq!(atc.world_to_anim_time(100.0), 2.5);
}
#[test]
fn test_world_to_anim_time_loop() {
let atc = AnimTimeConvertData {
flags: atc_flags::LOOP,
begin: 0.0,
end: 5.0,
loop_begin: 1.0,
loop_end: 4.0,
speed: 1.0,
ease_in: None,
ease_out: None,
speed_ease: None,
current_anim_time: 3.5,
last_eval_world_time: 0.0,
stop_points: Vec::new(),
};
let t = atc.world_to_anim_time(1.0);
assert!(t >= 1.0 && t <= 4.0, "t={} should be in loop range", t);
}
}