From 1f8bd2e19e0bfc150c5946f5907042f948195be4 Mon Sep 17 00:00:00 2001 From: KillzXGaming Date: Sat, 3 Aug 2019 19:33:29 -0400 Subject: [PATCH] Parse the skeletal animation format CSAB --- .vs/Toolbox/v15/.suo | Bin 744448 -> 797184 bytes .../FileFormats/Grezzo/CSAB.cs | 150 +++++++++++++++++- 2 files changed, 147 insertions(+), 3 deletions(-) diff --git a/.vs/Toolbox/v15/.suo b/.vs/Toolbox/v15/.suo index 4ea9b72c5c7bc9dd7ed8e2d8c7b99264cc7e088e..24dc10e8f876cba31be57665c2fe2fcc188495b1 100644 GIT binary patch delta 4932 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 delta 4883 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 Nodes = new List(); + public void Read(FileReader reader) { - reader.CheckSignature(4, "csab"); + reader.SetByteOrder(false); + + reader.ReadSignature(4, "csab"); uint FileSize = reader.ReadUInt32(); uint versionNum = reader.ReadUInt32(); if (versionNum == 5) @@ -94,17 +104,151 @@ namespace FirstPlugin uint unknown5 = reader.ReadUInt32();//0x00 uint unknown6 = reader.ReadUInt32();//0x00 uint unknown7 = reader.ReadUInt32();//0x00 + uint unknown8 = reader.ReadUInt32();//0x00 uint duration = reader.ReadUInt32(); uint nodeCount = reader.ReadUInt32(); uint boneCount = reader.ReadUInt32(); if (nodeCount != boneCount) throw new Exception("Unexpected bone and node count!"); ushort[] BoneIndexTable = reader.ReadUInt16s((int)boneCount); - for (int i = 0; i < boneCount; i++) + reader.Align(4); + uint[] nodeOffsets = reader.ReadUInt32s((int)nodeCount); + for (int i = 0; i < nodeCount; i++) { - + reader.SeekBegin(nodeOffsets[i] + 0x18); + AnimationNode node = new AnimationNode(); + node.Read(reader, Version); + Nodes.Add(node); } } } + + public class AnimationNode + { + public ushort BoneIndex { get; set; } + + public AnimTrack TranslateX { get; set; } + public AnimTrack TranslateY { get; set; } + public AnimTrack TranslateZ { get; set; } + public AnimTrack RotationX { get; set; } + public AnimTrack RotationY { get; set; } + public AnimTrack RotationZ { get; set; } + public AnimTrack ScaleX { get; set; } + public AnimTrack ScaleY { get; set; } + public AnimTrack ScaleZ { get; set; } + + public void Read(FileReader reader, GameVersion version) + { + long pos = reader.Position; + reader.ReadSignature(4, "anod"); + BoneIndex = reader.ReadUInt16(); + reader.ReadUInt16();//0x00 + TranslateX = ParseTrack(reader, version, pos); + TranslateY = ParseTrack(reader, version, pos); + TranslateZ = ParseTrack(reader, version, pos); + RotationX = ParseTrack(reader, version, pos); + RotationY = ParseTrack(reader, version, pos); + RotationZ = ParseTrack(reader, version, pos); + ScaleX = ParseTrack(reader, version, pos); + ScaleY = ParseTrack(reader, version, pos); + ScaleZ = ParseTrack(reader, version, pos); + reader.ReadUInt16();//0x00 + } + } + + private static AnimTrack ParseTrack(FileReader reader, GameVersion version, long startPos) + { + long pos = reader.Position; + + uint Offset = reader.ReadUInt16(); + if (Offset == 0) return null; + + reader.SeekBegin(startPos + Offset); + var track = new AnimTrack(reader, version); + + reader.SeekBegin(pos + sizeof(ushort)); //Seek back to next offset + return track; + } + + public class AnimTrack + { + public List KeyFramesLinear = new List(); + public List KeyFramesHermite = new List(); + + public uint InterpolationType; + + public AnimTrack(FileReader reader, GameVersion version) + { + uint numKeyFrames =0; + + if (version >= GameVersion.MM3D) + { + InterpolationType = reader.ReadByte(); + numKeyFrames = reader.ReadUInt16(); + } + else + { + InterpolationType = reader.ReadUInt32(); + numKeyFrames = reader.ReadUInt32(); + uint unknown = reader.ReadUInt32(); + uint endFrame = reader.ReadUInt32(); + } + + if (InterpolationType == (uint)AnimationTrackType.LINEAR) + { + if (version >= GameVersion.MM3D) + { + float scale = reader.ReadSingle(); + float bias = reader.ReadSingle(); + + for (uint i = 0; i < numKeyFrames; i++) + { + LinearKeyFrame keyFrame = new LinearKeyFrame(); + keyFrame.Time = i; + keyFrame.Value = reader.ReadUInt16() * scale - bias; + KeyFramesLinear.Add(keyFrame); + } + } + else + { + for (int i = 0; i < numKeyFrames; i++) + { + LinearKeyFrame keyFrame = new LinearKeyFrame(); + keyFrame.Time = reader.ReadUInt32(); + keyFrame.Value = reader.ReadSingle(); + KeyFramesLinear.Add(keyFrame); + } + } + } + else if (InterpolationType == (uint)AnimationTrackType.HERMITE) + { + for (int i = 0; i < numKeyFrames; i++) + { + HermiteKeyFrame keyFrame = new HermiteKeyFrame(); + keyFrame.Time = reader.ReadUInt32(); + keyFrame.Value = reader.ReadSingle(); + keyFrame.TangentIn = reader.ReadSingle(); + keyFrame.TangentOut = reader.ReadSingle(); + KeyFramesHermite.Add(keyFrame); + } + } + else + throw new Exception("Unknown interpolation type! " + InterpolationType); + } + } + + public class HermiteKeyFrame + { + public uint Time { get; set; } + public float Value { get; set; } + public float TangentIn { get; set; } + public float TangentOut { get; set; } + } + + public class LinearKeyFrame + { + public uint Time { get; set; } + public float Value { get; set; } + } } }