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}