vmdl/mdl/raw/
animation.rs

1use crate::compressed_vector::{Quaternion48, Quaternion64, Vector48};
2use crate::mdl::Bone;
3use crate::{
4    index_range, read_relative, read_single, ModelError, Quaternion, RadianEuler, ReadRelative,
5    Readable, ReadableRelative, Vector,
6};
7use bitflags::bitflags;
8use bytemuck::{Pod, Zeroable};
9use cgmath::{Matrix4, SquareMatrix};
10use std::mem::size_of;
11
12#[derive(Debug, Clone, Copy, Zeroable, Pod)]
13#[repr(C)]
14pub struct PoseParameterDescriptionHeader {
15    name_index: i32,
16    flags: i32,
17    start: f32,
18    end: f32,
19    loop_range: f32,
20}
21
22static_assertions::const_assert_eq!(size_of::<PoseParameterDescriptionHeader>(), 20);
23
24#[derive(Clone, Debug)]
25pub struct PoseParameterDescription {
26    pub name: String,
27    pub flags: i32,
28    pub start: f32,
29    pub end: f32,
30    pub loop_range: f32,
31}
32
33impl ReadRelative for PoseParameterDescription {
34    type Header = PoseParameterDescriptionHeader;
35
36    fn read(data: &[u8], header: Self::Header) -> Result<Self, ModelError> {
37        Ok(PoseParameterDescription {
38            name: read_single(data, header.name_index)?,
39            flags: header.flags,
40            start: header.start,
41            end: header.end,
42            loop_range: header.loop_range,
43        })
44    }
45}
46
47#[derive(Debug, Clone, Copy, Zeroable, Pod)]
48#[repr(C)]
49pub struct AnimationDescriptionHeader {
50    base_ptr: i32,
51    name_offset: i32,
52    fps: f32,
53    flags: i32,
54
55    frame_count: i32,
56
57    movement_count: i32,
58    movement_offset: i32,
59
60    _padding: [i32; 6],
61
62    animation_block: i32,
63    animation_index: i32, // non-zero when anim data isn't in sections
64
65    ik_rule_count: i32,
66    ik_rule_offset: i32,
67    animation_block_ik_rule_index: i32,
68
69    local_hierarchy_count: i32,
70    local_hierarchy_offset: i32,
71
72    section_offset: i32,
73    section_frames: i32,
74
75    zero_frame_span: i16,
76    zero_frame_count: i16,
77    zero_frame_offset: i32,
78
79    zero_frame_stall_time: f32,
80}
81
82static_assertions::const_assert_eq!(size_of::<AnimationDescriptionHeader>(), 100);
83
84#[derive(Clone, Debug)]
85pub struct AnimationDescription {
86    pub name: String,
87    pub fps: f32,
88    pub frame_count: usize,
89    pub animations: Vec<Animation>,
90}
91
92impl AnimationDescription {
93    pub fn get_bone_transform(&self, bone: u8, frame: usize) -> Matrix4<f32> {
94        let Some(animation) = self.animations.iter().find(|anim| anim.bone == bone) else {
95            return Matrix4::identity();
96        };
97        Matrix4::from_translation(animation.position(frame).into())
98            * Matrix4::from(animation.rotation(frame))
99    }
100}
101
102impl ReadRelative for AnimationDescription {
103    type Header = AnimationDescriptionHeader;
104
105    fn read(data: &[u8], header: Self::Header) -> Result<Self, ModelError> {
106        let mut animations = Vec::with_capacity(1);
107        let mut offset = header.animation_index as usize;
108        loop {
109            let (animation, next_offset) = if header.animation_block == 0 {
110                read_animation(data, offset, header.frame_count as usize)?
111            } else {
112                todo!("read animation from animation block");
113            };
114            animations.push(animation);
115            if next_offset == 0 {
116                break;
117            }
118            offset += next_offset;
119        }
120
121        Ok(AnimationDescription {
122            name: read_single(data, header.name_offset)?,
123            fps: header.fps,
124            frame_count: header.frame_count as usize,
125            animations,
126        })
127    }
128}
129
130#[derive(Debug, Clone, Copy, Zeroable, Pod)]
131#[repr(C)]
132pub struct AnimationBlock {
133    start: i32,
134    end: i32,
135}
136
137impl ReadableRelative for AnimationBlock {}
138
139#[derive(Debug, Clone, Copy, Zeroable, Pod)]
140#[repr(C)]
141pub struct AnimationHeader {
142    bone: u8,
143    flags: AnimationFlags,
144    next_offset: u16,
145}
146
147#[derive(Zeroable, Pod, Copy, Clone, Debug)]
148#[repr(C)]
149pub struct AnimationFlags(u8);
150
151bitflags! {
152    impl AnimationFlags: u8 {
153        /// Vector48
154        const STUDIO_ANIM_RAWPOS = 	0x00000001;
155        /// Quaternion48
156        const STUDIO_ANIM_RAWROT = 	0x00000002;
157        /// mstudioanim_valueptr_t
158        const STUDIO_ANIM_ANIMPOS = 0x00000004;
159        /// mstudioanim_valueptr_t
160        const STUDIO_ANIM_ANIMROT = 0x00000008;
161        const STUDIO_ANIM_DELTA = 	0x00000010;
162        /// Quaternion64
163        const STUDIO_ANIM_RAWROT2 = 0x00000020;
164    }
165}
166
167#[derive(Zeroable, Pod, Copy, Clone, Debug)]
168#[repr(C)]
169struct AnimationValuePointer([u16; 3]);
170impl ReadableRelative for AnimationValuePointer {}
171
172#[derive(Zeroable, Pod, Copy, Clone, Debug, Default)]
173#[repr(C)]
174struct ValueHeader {
175    valid: u8,
176    total: u8,
177}
178impl ReadableRelative for ValueHeader {}
179
180fn read_animation_values(
181    data: &[u8], // data starting at the AnimationValuePointer
182    frame: usize,
183    base_pointers: AnimationValuePointer,
184) -> Result<[f32; 3], ModelError> {
185    let mut result = [0.0; 3];
186    for (out, base_pointer) in result.iter_mut().zip(base_pointers.0) {
187        if base_pointer == 0 {
188            *out = 0.0;
189        } else {
190            let header: ValueHeader = read_single(data, base_pointer)?;
191            let values = FrameValues {
192                header,
193                data: &data[base_pointer as usize..],
194            };
195            *out = values.get(frame as u8).map(|val| val as f32)?;
196        }
197    }
198    Ok(result)
199}
200
201/// I hate this data structure
202///
203/// Seems to be an array of
204///
205/// FrameValues {
206///     header: ValueHeader,
207///     values: [u16; self.header.valid]
208/// }
209///
210/// each item containing `header.total` worth of frames (for frames larger than `header.valid` it re-uses the last valid data)
211/// when looking up frame `k` we skip through the list of values until we find the value range for the frame
212struct FrameValues<'a> {
213    header: ValueHeader,
214    data: &'a [u8], // data starting at self.header
215}
216
217impl<'a> FrameValues<'a> {
218    pub fn get(&self, index: u8) -> Result<u16, ModelError> {
219        if self.header.total <= index {
220            let offset_count = self.header.valid + 1;
221            let offset = (offset_count as usize) * size_of::<u16>();
222            let next_header: ValueHeader = read_single(self.data, offset)?;
223            let next = FrameValues {
224                header: next_header,
225                data: &self.data[offset..],
226            };
227            if next_header.total == 0 {
228                return Ok(0);
229            }
230            next.get(index - self.header.total)
231        } else {
232            let offset_count = if self.header.valid > index {
233                index + 1
234            } else {
235                self.header.valid
236            };
237            let offset = (offset_count as usize) * size_of::<u16>();
238            read_single(self.data, offset)
239        }
240    }
241}
242
243#[derive(Clone, Debug)]
244pub enum RotationData {
245    Quaternion48(Quaternion),
246    Quaternion64(Quaternion),
247    Animated(Vec<RadianEuler>),
248    None,
249}
250
251impl From<Quaternion48> for RotationData {
252    fn from(value: Quaternion48) -> Self {
253        let q = Quaternion::from(value);
254        RotationData::Quaternion48(q)
255    }
256}
257
258impl From<Quaternion64> for RotationData {
259    fn from(value: Quaternion64) -> Self {
260        let q = Quaternion::from(value);
261        RotationData::Quaternion64(q)
262    }
263}
264
265impl From<Vec<RadianEuler>> for RotationData {
266    fn from(value: Vec<RadianEuler>) -> Self {
267        // axis get fixed up when applying the scale
268        RotationData::Animated(value)
269    }
270}
271
272impl RotationData {
273    pub fn rotation(&self, frame: usize) -> Quaternion {
274        match self {
275            RotationData::Quaternion48(q) => *q,
276            RotationData::Quaternion64(q) => *q,
277            RotationData::Animated(values) => values
278                .get(frame)
279                .copied()
280                .unwrap_or_else(|| values.last().copied().unwrap_or_default())
281                .into(),
282            RotationData::None => Quaternion::default(),
283        }
284    }
285
286    pub fn size(&self) -> usize {
287        match self {
288            RotationData::Quaternion48(_) => size_of::<Quaternion48>(),
289            RotationData::Quaternion64(_) => size_of::<Quaternion64>(),
290            RotationData::Animated(_) => size_of::<AnimationValuePointer>(),
291            RotationData::None => 0,
292        }
293    }
294
295    fn set_scale(&mut self, scale: Vector) {
296        if let RotationData::Animated(values) = self {
297            values.iter_mut().for_each(|value| {
298                // scale and fixup the angles
299                *value = RadianEuler {
300                    y: value.x * scale.x,
301                    z: value.y * scale.y,
302                    x: value.z * scale.z,
303                }
304            });
305        }
306    }
307}
308
309#[derive(Clone, Debug)]
310pub enum PositionData {
311    Vector48(Vector48),
312    PositionValues(Vec<Vector>),
313    None,
314}
315
316impl PositionData {
317    pub fn position(&self, frame: usize) -> Vector {
318        match self {
319            PositionData::Vector48(vector) => Vector::from(*vector),
320            PositionData::PositionValues(values) => values.get(frame).copied().unwrap_or_default(),
321            PositionData::None => Vector::default(),
322        }
323    }
324
325    fn set_scale(&mut self, scale: Vector) {
326        if let PositionData::PositionValues(values) = self {
327            values.iter_mut().for_each(|value| {
328                *value = Vector {
329                    x: value.x * scale.x,
330                    y: value.y * scale.y,
331                    z: value.z * scale.z,
332                }
333            });
334        }
335    }
336}
337
338/// Per bone animation data
339#[derive(Clone, Debug)]
340pub struct Animation {
341    pub bone: u8,
342    pub flags: AnimationFlags,
343    rotation_data: RotationData,
344    position_data: PositionData,
345}
346
347impl Animation {
348    pub fn rotation(&self, frame: usize) -> Quaternion {
349        self.rotation_data.rotation(frame)
350    }
351
352    pub(crate) fn rotation_looks_valid(&self) -> bool {
353        true
354    }
355
356    pub fn position(&self, frame: usize) -> Vector {
357        self.position_data.position(frame)
358    }
359
360    pub(crate) fn set_scales(&mut self, bone: &Bone) {
361        self.rotation_data.set_scale(bone.rot_scale);
362        self.position_data.set_scale(bone.pos_scale);
363    }
364}
365
366fn read_animation(
367    data: &[u8],
368    header_offset: usize,
369    frames: usize,
370) -> Result<(Animation, usize), ModelError> {
371    let data = data
372        .get(header_offset..)
373        .ok_or_else(|| ModelError::OutOfBounds {
374            data: "animation data",
375            offset: header_offset,
376        })?;
377    let header = <AnimationHeader as Readable>::read(data)?;
378
379    let offset = size_of::<AnimationHeader>();
380
381    let rotation_data = if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWROT) {
382        RotationData::from(read_single::<Quaternion48, _>(data, offset)?)
383    } else if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWROT2) {
384        RotationData::from(read_single::<Quaternion64, _>(data, offset)?)
385    } else if header.flags.contains(AnimationFlags::STUDIO_ANIM_ANIMROT) {
386        let pointers: AnimationValuePointer = read_single(data, offset)?;
387        let value_data = &data[offset..];
388        let values: Vec<RadianEuler> = (0..frames)
389            .map(|frame| read_animation_values(value_data, frame, pointers))
390            .map(|r| r.map(|[x, y, z]| RadianEuler { x, z, y }))
391            .collect::<Result<_, ModelError>>()?;
392        RotationData::from(values)
393    } else {
394        RotationData::None
395    };
396
397    let position_offset = offset + rotation_data.size();
398    let position_data = if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWPOS) {
399        PositionData::Vector48(read_single(data, position_offset)?)
400    } else if header.flags.contains(AnimationFlags::STUDIO_ANIM_ANIMPOS) {
401        let pointers: AnimationValuePointer = read_single(data, position_offset)?;
402        let value_data = &data[position_offset..];
403        let values = (0..frames)
404            .map(|frame| read_animation_values(value_data, frame, pointers))
405            .map(|r| r.map(Vector::from))
406            .collect::<Result<_, ModelError>>()?;
407        PositionData::PositionValues(values)
408    } else {
409        PositionData::None
410    };
411
412    Ok((
413        Animation {
414            bone: header.bone,
415            flags: header.flags,
416            rotation_data,
417            position_data,
418        },
419        header.next_offset as usize,
420    ))
421}
422
423#[derive(Zeroable, Pod, Copy, Clone, Debug, Default)]
424#[repr(C)]
425pub struct AnimationSequenceHeader {
426    base: i32,
427    label_index: i32,
428    activity_name_index: i32,
429    flags: i32, // todo
430    activity: i32,
431    weight: i32,
432    event_count: i32,
433    event_offset: i32,
434    bounding_box_min: Vector,
435    bounding_box_max: Vector,
436    blend_count: i32,
437    animation_index_index: i32,
438    movement_index: i32,
439    group_size: [i32; 2],
440    param_index: [i32; 2],
441    param_start: [i32; 2],
442    param_end: [i32; 2],
443    param_parent: i32,
444
445    fade_in_time: f32,
446    fade_out_time: f32,
447
448    local_entry_node: i32,
449    local_exit_node: i32,
450    node_flags: i32,
451
452    entry_phase: f32,
453    exit_phase: f32,
454
455    last_frame: f32,
456
457    next_sequence: i32,
458    pose: i32,
459
460    ik_rule_count: i32,
461
462    auto_layer_count: i32,
463    auto_layer_offset: i32,
464
465    weight_list_offset: i32,
466
467    pose_key_offset: i32,
468
469    ik_lock_count: i32,
470    ik_lock_offset: i32,
471
472    key_value_offset: i32,
473    key_value_size: i32,
474
475    cycle_pose_offset: i32,
476
477    activity_modifiers_offset: i32,
478    activity_modifiers_count: i32,
479
480    _padding: [i32; 5],
481}
482
483impl AnimationSequenceHeader {
484    fn bone_weight_indices(&self) -> impl Iterator<Item = usize> {
485        // weight/bone count isn't stored here, so we assume the next indexed values is stored after it in the file
486        // we trim down the list of weights later
487        let other_indices = [
488            self.pose_key_offset,
489            self.ik_lock_offset,
490            self.key_value_offset,
491            self.activity_modifiers_offset,
492        ];
493        let weight_count = if let Some(next_index) = other_indices
494            .iter()
495            .copied()
496            .find(|index| *index > self.weight_list_offset)
497        {
498            (next_index - self.weight_list_offset) as usize / size_of::<f32>()
499        } else {
500            0
501        };
502        index_range(
503            self.weight_list_offset,
504            weight_count as i32,
505            size_of::<f32>(),
506        )
507    }
508}
509
510#[derive(Debug, Clone)]
511pub struct AnimationSequence {
512    pub name: String,
513    pub label: String,
514    pub bone_weights: Vec<f32>,
515}
516
517impl ReadRelative for AnimationSequence {
518    type Header = AnimationSequenceHeader;
519
520    fn read(data: &[u8], header: Self::Header) -> Result<Self, ModelError> {
521        Ok(AnimationSequence {
522            name: read_single(data, header.activity_name_index)?,
523            label: read_single(data, header.label_index)?,
524            bone_weights: read_relative(data, header.bone_weight_indices())?,
525        })
526    }
527}