Skip to main content

plasma_prp/animation/
ag_anim.rs

1//! plAGAnim — animation clips holding named channels with applicators.
2//!
3//! C++ ref: plAnimation/plAGAnim.h/.cpp, plMatrixChannel.h/.cpp,
4//!          plScalarChannel.h/.cpp, plPointChannel.h/.cpp, plQuatChannel.h/.cpp
5//!
6//! ## Binary Format for plAGAnim applicator list
7//!
8//! Each animation contains a list of applicator+channel pairs:
9//! ```text
10//! [u32 num_applicators]
11//! For each pair:
12//!   [Applicator creatable]   — u16 class_idx + applicator data
13//!   [Channel creatable]      — u16 class_idx + channel data
14//! ```
15//!
16//! ## Applicator format (plAGApplicator::Read, plAGApplicator.cpp:141-148)
17//!
18//! All applicator subclasses use the base Read with no additional fields:
19//! ```text
20//! u8       fEnabled
21//! safe_str fChannelName        — target bone/node name
22//! ```
23//!
24//! Applicator class indices (all share same binary format):
25//!   0x030E  plMatrixChannelApplicator
26//!   0x030F  plPointChannelApplicator
27//!   0x0314  plQuatChannelApplicator
28//!   0x0319  plScalarChannelApplicator
29//!   0x0310  plLightDiffuseApplicator
30//!   0x0311  plLightAmbientApplicator
31//!   0x0312  plLightSpecularApplicator
32//!   0x0313  plOmniApplicator
33//!   0x031A  plSpotInnerApplicator
34//!   0x031B  plSpotOuterApplicator
35//!   0x03A8  plOmniCutoffApplicator
36//!   0x022A  plOmniSqApplicator
37//!
38//! ## Channel formats (each starts with base plAGChannel::Read → safe_string fName)
39//!
40//! plMatrixControllerChannel (0x02DE):
41//!   safe_str fName
42//!   creatable fController      — plTMController/plCompoundController/plLeafController
43//!   AffineParts fAP            — 15 floats (T:3f + Q:4f + U:4f + K:3f + F:1f)
44//!
45//! plMatrixConstant (0x0331):
46//!   safe_str fName
47//!   AffineParts fAP            — 15 floats (T:3f + Q:4f + U:4f + K:3f + F:1f)
48//!
49//! plScalarControllerChannel (0x0318):
50//!   safe_str fName
51//!   creatable fController      — plLeafController
52//!
53//! plScalarConstant (0x0330):
54//!   safe_str fName
55//!   f32 fResult
56//!
57//! plPointControllerChannel (0x030B):
58//!   safe_str fName
59//!   creatable fController      — plCompoundController/plLeafController
60//!
61//! plPointConstant (0x02E1):
62//!   safe_str fName
63//!   Point3 fResult             — 3 floats (x, y, z)
64//!
65//! plQuatConstant (0x02E4):
66//!   safe_str fName
67//!   Quat fResult               — 4 floats (x, y, z, w)
68//!
69//! ## hsAffineParts format (plTransform/hsAffineParts.cpp:417-425)
70//!
71//! ```text
72//! hsVector3 fT   — Translation (3× f32 LE)
73//! hsQuat    fQ   — Essential rotation (4× f32 LE: x, y, z, w)
74//! hsQuat    fU   — Stretch rotation (4× f32 LE: x, y, z, w)
75//! hsVector3 fK   — Stretch factors / scale (3× f32 LE)
76//! float     fF   — Sign of determinant (1× f32 LE)
77//! ```
78//! Total: 15 floats = 60 bytes
79
80use std::io::Read;
81
82use anyhow::{Result, bail};
83
84use crate::core::class_index::ClassIndex;
85use crate::core::synched_object::SynchedObjectData;
86use crate::core::uoid::{Uoid, read_key_uoid};
87use crate::resource::prp::PlasmaRead;
88
89use super::controller::Controller;
90
91/// Body usage for animations.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[repr(u8)]
94pub enum BodyUsage {
95    Unknown = 0,
96    Upper = 1,
97    Full = 2,
98    Lower = 3,
99}
100
101impl BodyUsage {
102    pub fn from_u8(v: u8) -> Self {
103        match v {
104            1 => Self::Upper,
105            2 => Self::Full,
106            3 => Self::Lower,
107            _ => Self::Unknown,
108        }
109    }
110}
111
112/// Decomposed affine transform (hsAffineParts).
113/// C++ ref: plTransform/hsAffineParts.h
114#[derive(Debug, Clone)]
115pub struct AffineParts {
116    pub translation: [f32; 3],       // fT — translation
117    pub rotation: [f32; 4],          // fQ — essential rotation (quaternion x,y,z,w)
118    pub stretch_rotation: [f32; 4],  // fU — stretch rotation (quaternion)
119    pub scale: [f32; 3],             // fK — stretch factors
120    pub det_sign: f32,               // fF — sign of determinant
121}
122
123impl AffineParts {
124    pub fn read(reader: &mut impl Read) -> Result<Self> {
125        Ok(Self {
126            translation: [reader.read_f32()?, reader.read_f32()?, reader.read_f32()?],
127            rotation: [reader.read_f32()?, reader.read_f32()?, reader.read_f32()?, reader.read_f32()?],
128            stretch_rotation: [reader.read_f32()?, reader.read_f32()?, reader.read_f32()?, reader.read_f32()?],
129            scale: [reader.read_f32()?, reader.read_f32()?, reader.read_f32()?],
130            det_sign: reader.read_f32()?,
131        })
132    }
133}
134
135/// Channel data extracted from an animation applicator.
136///
137/// Each channel targets a named bone/node and contains either a controller
138/// with keyframe data or a constant value.
139#[derive(Debug, Clone)]
140pub enum ChannelData {
141    /// Matrix controller channel — drives a bone's transform via keyframes.
142    /// Contains a plController + initial hsAffineParts rest pose.
143    /// C++ ref: plMatrixControllerChannel (plMatrixChannel.cpp:596-604)
144    MatrixController {
145        controller: Option<Controller>,
146        initial_pose: AffineParts,
147    },
148    /// Matrix constant — static bone transform with no animation.
149    /// C++ ref: plMatrixConstant (plMatrixChannel.cpp:217-222)
150    MatrixConstant {
151        pose: AffineParts,
152    },
153    /// Scalar controller channel — drives a single float via keyframes.
154    /// C++ ref: plScalarControllerChannel (plScalarChannel.cpp:379-384)
155    ScalarController(Option<Controller>),
156    /// Scalar constant — static float value.
157    /// C++ ref: plScalarConstant (plScalarChannel.cpp:148-152)
158    ScalarConstant(f32),
159    /// Point controller channel — drives a 3D point via keyframes.
160    /// C++ ref: plPointControllerChannel (plPointChannel.cpp:381-386)
161    PointController(Option<Controller>),
162    /// Point constant — static 3D point.
163    /// C++ ref: plPointConstant (plPointChannel.cpp:132-136)
164    PointConstant([f32; 3]),
165    /// Quaternion constant — static rotation.
166    /// C++ ref: plQuatConstant (plQuatChannel.cpp:147-151)
167    QuatConstant([f32; 4]),
168}
169
170/// A channel-applicator pair in an animation.
171///
172/// The applicator routes a channel's output to a target (bone transform,
173/// scalar property, etc.). The channel_name identifies the target bone.
174///
175/// C++ ref: plAGApplicator::Read (plAGApplicator.cpp:141-148)
176#[derive(Debug, Clone)]
177pub struct AnimApplicator {
178    /// Applicator class index.
179    pub applicator_class: u16,
180    /// Channel name from the applicator (target bone/node name).
181    pub channel_name: String,
182    pub enabled: bool,
183    /// Channel class index.
184    pub channel_class: u16,
185    /// Channel name from the channel's own data (fName from plAGChannel::Read).
186    pub channel_name_from_channel: String,
187    /// Parsed channel data including controller with keyframes.
188    pub channel_data: ChannelData,
189}
190
191impl AnimApplicator {
192    /// Read an applicator+channel pair from a stream.
193    ///
194    /// plAGAnim::Read serializes applicators and channels as interleaved
195    /// creatable pairs: [applicator_creatable][channel_creatable] for each.
196    /// C++ ref: plAGAnim.cpp:203-224
197    pub fn read(reader: &mut impl Read) -> Result<Self> {
198        // === Read applicator creatable ===
199        // All applicator subclasses use the same base plAGApplicator::Read:
200        //   fEnabled (u8 bool), fChannelName (safe_string)
201        // C++ ref: plAGApplicator.cpp:141-148
202        let app_class = reader.read_u16()?;
203        let (channel_name, enabled) = if app_class != 0x8000 {
204            let enabled = reader.read_u8()? != 0;
205            let name = reader.read_safe_string()?;
206            (name, enabled)
207        } else {
208            (String::new(), false)
209        };
210
211        // === Read channel creatable ===
212        // Dispatch on class index to read subclass-specific data.
213        // All channels start with base plAGChannel::Read → safe_string fName.
214        let chan_class = reader.read_u16()?;
215        if chan_class == 0x8000 {
216            return Ok(Self {
217                applicator_class: app_class,
218                channel_name,
219                enabled,
220                channel_class: 0x8000,
221                channel_name_from_channel: String::new(),
222                channel_data: ChannelData::ScalarConstant(0.0),
223            });
224        }
225
226        // Base plAGChannel::Read — all channel types read fName first
227        let channel_name_from_channel = reader.read_safe_string()?;
228
229        let channel_data = match chan_class {
230            // plMatrixControllerChannel (0x02DE):
231            //   base name (already read) + controller creatable + AffineParts
232            ClassIndex::PL_MATRIX_CONTROLLER_CHANNEL => {
233                let controller = Controller::read_creatable(reader)?;
234                let initial_pose = AffineParts::read(reader)?;
235                ChannelData::MatrixController { controller, initial_pose }
236            }
237
238            // plMatrixConstant (0x0331):
239            //   base name (already read) + AffineParts
240            ClassIndex::PL_MATRIX_CONSTANT => {
241                let pose = AffineParts::read(reader)?;
242                ChannelData::MatrixConstant { pose }
243            }
244
245            // plScalarControllerChannel (0x0318):
246            //   base name (already read) + controller creatable
247            ClassIndex::PL_SCALAR_CONTROLLER_CHANNEL => {
248                let controller = Controller::read_creatable(reader)?;
249                ChannelData::ScalarController(controller)
250            }
251
252            // plScalarConstant (0x0330):
253            //   base name (already read) + f32 value
254            ClassIndex::PL_SCALAR_CONSTANT => {
255                let value = reader.read_f32()?;
256                ChannelData::ScalarConstant(value)
257            }
258
259            // plPointControllerChannel (0x030B):
260            //   base name (already read) + controller creatable
261            ClassIndex::PL_POINT_CONTROLLER_CHANNEL => {
262                let controller = Controller::read_creatable(reader)?;
263                ChannelData::PointController(controller)
264            }
265
266            // plPointConstant (0x02E1):
267            //   base name (already read) + Point3 (3 floats)
268            ClassIndex::PL_POINT_CONSTANT => {
269                let value = [reader.read_f32()?, reader.read_f32()?, reader.read_f32()?];
270                ChannelData::PointConstant(value)
271            }
272
273            // plQuatConstant (0x02E4):
274            //   base name (already read) + Quat (4 floats: x, y, z, w)
275            ClassIndex::PL_QUAT_CONSTANT => {
276                let value = [reader.read_f32()?, reader.read_f32()?, reader.read_f32()?, reader.read_f32()?];
277                ChannelData::QuatConstant(value)
278            }
279
280            _ => {
281                bail!("Unknown channel class 0x{:04X} ({}) for applicator '{}'",
282                    chan_class,
283                    ClassIndex::class_name(chan_class),
284                    channel_name);
285            }
286        };
287
288        Ok(Self {
289            applicator_class: app_class,
290            channel_name,
291            enabled,
292            channel_class: chan_class,
293            channel_name_from_channel,
294            channel_data,
295        })
296    }
297}
298
299/// Named time marker in an animation.
300#[derive(Debug, Clone)]
301pub struct AnimMarker {
302    pub name: String,
303    pub time: f32,
304}
305
306/// Named loop in an animation.
307#[derive(Debug, Clone)]
308pub struct AnimLoop {
309    pub name: String,
310    pub begin: f32,
311    pub end: f32,
312}
313
314/// Parsed plAGAnim data (base animation clip).
315#[derive(Debug, Clone)]
316pub struct AGAnimData {
317    pub self_key: Option<Uoid>,
318    pub synched: SynchedObjectData,
319    pub name: String,
320    pub start: f32,
321    pub end: f32,
322    pub applicators: Vec<AnimApplicator>,
323}
324
325impl AGAnimData {
326    /// Read a plAGAnim from a stream (after creatable class index).
327    pub fn read(reader: &mut impl Read) -> Result<Self> {
328        let self_key = read_key_uoid(reader)?;
329        let synched = SynchedObjectData::read(reader)?;
330
331        let name = reader.read_safe_string()?;
332        let start = reader.read_f32()?;
333        let end = reader.read_f32()?;
334
335        let num_apps = reader.read_u32()?;
336        let mut applicators = Vec::with_capacity(num_apps as usize);
337        for _ in 0..num_apps {
338            applicators.push(AnimApplicator::read(reader)?);
339        }
340
341        Ok(Self {
342            self_key,
343            synched,
344            name,
345            start,
346            end,
347            applicators,
348        })
349    }
350}
351
352/// Parsed plATCAnim data (timed animation with looping, markers, etc.).
353#[derive(Debug, Clone)]
354pub struct ATCAnimData {
355    pub base: AGAnimData,
356    pub initial: f32,
357    pub auto_start: bool,
358    pub loop_start: f32,
359    pub loop_end: f32,
360    pub do_loop: bool,
361    pub ease_in_type: u8,
362    pub ease_in_min: f32,
363    pub ease_in_max: f32,
364    pub ease_in_length: f32,
365    pub ease_out_type: u8,
366    pub ease_out_min: f32,
367    pub ease_out_max: f32,
368    pub ease_out_length: f32,
369    pub markers: Vec<AnimMarker>,
370    pub loops: Vec<AnimLoop>,
371    pub stop_points: Vec<f32>,
372}
373
374impl ATCAnimData {
375    /// Read a plATCAnim from a stream (after creatable class index).
376    pub fn read(reader: &mut impl Read) -> Result<Self> {
377        let base = AGAnimData::read(reader)?;
378
379        let initial = reader.read_f32()?;
380        let auto_start = reader.read_u8()? != 0;
381        let loop_start = reader.read_f32()?;
382        let loop_end = reader.read_f32()?;
383        let do_loop = reader.read_u8()? != 0;
384
385        let ease_in_type = reader.read_u8()?;
386        let ease_in_min = reader.read_f32()?;
387        let ease_in_max = reader.read_f32()?;
388        let ease_in_length = reader.read_f32()?;
389        let ease_out_type = reader.read_u8()?;
390        let ease_out_min = reader.read_f32()?;
391        let ease_out_max = reader.read_f32()?;
392        let ease_out_length = reader.read_f32()?;
393
394        let num_markers = reader.read_u32()?;
395        let mut markers = Vec::with_capacity(num_markers as usize);
396        for _ in 0..num_markers {
397            markers.push(AnimMarker {
398                name: reader.read_safe_string()?,
399                time: reader.read_f32()?,
400            });
401        }
402
403        let num_loops = reader.read_u32()?;
404        let mut loops = Vec::with_capacity(num_loops as usize);
405        for _ in 0..num_loops {
406            loops.push(AnimLoop {
407                name: reader.read_safe_string()?,
408                begin: reader.read_f32()?,
409                end: reader.read_f32()?,
410            });
411        }
412
413        let num_stop_points = reader.read_u32()?;
414        let mut stop_points = Vec::with_capacity(num_stop_points as usize);
415        for _ in 0..num_stop_points {
416            stop_points.push(reader.read_f32()?);
417        }
418
419        Ok(Self {
420            base,
421            initial,
422            auto_start,
423            loop_start,
424            loop_end,
425            do_loop,
426            ease_in_type,
427            ease_in_min,
428            ease_in_max,
429            ease_in_length,
430            ease_out_type,
431            ease_out_min,
432            ease_out_max,
433            ease_out_length,
434            markers,
435            loops,
436            stop_points,
437        })
438    }
439}
440
441/// Parsed plEmoteAnim data.
442#[derive(Debug, Clone)]
443pub struct EmoteAnimData {
444    pub base: ATCAnimData,
445    pub fade_in: f32,
446    pub fade_out: f32,
447    pub body_usage: BodyUsage,
448}
449
450impl EmoteAnimData {
451    pub fn read(reader: &mut impl Read) -> Result<Self> {
452        let base = ATCAnimData::read(reader)?;
453        let fade_in = reader.read_f32()?;
454        let fade_out = reader.read_f32()?;
455        let body_usage = BodyUsage::from_u8(reader.read_u8()?);
456
457        Ok(Self {
458            base,
459            fade_in,
460            fade_out,
461            body_usage,
462        })
463    }
464}
465
466/// Parsed plAgeGlobalAnim data.
467#[derive(Debug, Clone)]
468pub struct AgeGlobalAnimData {
469    pub base: AGAnimData,
470    pub global_var_name: String,
471}
472
473impl AgeGlobalAnimData {
474    pub fn read(reader: &mut impl Read) -> Result<Self> {
475        let base = AGAnimData::read(reader)?;
476        let global_var_name = reader.read_safe_string()?;
477
478        Ok(Self {
479            base,
480            global_var_name,
481        })
482    }
483}