diff --git a/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs index a2a6f04..3e00c1d 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -123,8 +124,183 @@ namespace CodeWalker.GameFiles ClipMapEntries = ClipMap.Values.ToArray(); AnimMapEntries = AnimMap.Values.ToArray(); + } + + public void SaveOpenFormatsAnimation(Animation crAnim, Stream outStream) + { + var seqs = new int[(crAnim.Frames / crAnim.SequenceFrameLimit) + 1]; + for (int s = 0; s < seqs.Length; s++) + { + seqs[s] = crAnim.SequenceFrameLimit + 1; + } + + seqs[seqs.Length - 1] = crAnim.Frames % crAnim.SequenceFrameLimit; + + var writer = new StreamWriter(outStream); + writer.Write($@"Version 8 2 +{{ + Flags FLAG_0 FLAG_4 FLAG_5 FLAG_6 FLAG_7 FLAG_8 + Frames {crAnim.Frames} + SequenceFrameLimit {crAnim.SequenceFrameLimit} + Duration {crAnim.Frames / 30.0} + _f10 285410817 + ExtraFlags + Sequences {string.Join(" ", seqs.Select(f => f.ToString()))} + MaterialID -1 + Animation + {{ +"); + + int i = 0; + + foreach (var list in crAnim.BoneIds.data_items) + { + // for now, we only support BonePosition and BoneRotation tracks + if (list.Track != 0 && list.Track != 1) + { + continue; + } + + bool isRotation = false; + + var chList = crAnim.Sequences.data_items[0].Sequences[i].Channels; + + if (chList.Length == 4) + { + isRotation = true; + } + else if (chList.Length == 1) + { + if (chList[0] is AnimChannelStaticSmallestThreeQuaternion) + { + isRotation = true; + } + } + + // temp placeholder + if (list.Track == 1 && !isRotation) + { + continue; + } + + var type = (!isRotation) ? "BonePosition" : "BoneRotation"; + var dataType = (!isRotation) ? "Float3" : "Float4"; + + var sb = new StringBuilder(); + + foreach (var seq in crAnim.Sequences.data_items) + { + var channels = seq.Sequences[i].Channels; + + string ChannelType(AnimChannel chan) + { + if (seq.Sequences[i].IsType7Quat) + { + if (seq.Sequences[i].Channels[0] is AnimChannelStaticFloat && seq.Sequences[i].Channels[1] is AnimChannelStaticFloat && seq.Sequences[i].Channels[2] is AnimChannelStaticFloat) + { + return " Static"; + } + } + else if (chan is AnimChannelStaticFloat || chan is AnimChannelStaticVector3 || chan is AnimChannelStaticSmallestThreeQuaternion) + { + return " Static"; + } + + return ""; + } + + string ChannelData(AnimChannel chan) + { + if (seq.Sequences[i].IsType7Quat) + { + // get index this weird way + int index = 0; + + for (int j = 0; j < seq.Sequences[i].Channels.Length; j++) + { + if (seq.Sequences[i].Channels[j] == chan) + { + index = j; + break; + } + } + + // actually we should only export Static for 'real' channels, but as mapping for these is stupid, we'll just repeat the same value even if one channel is supposed to be static + if (seq.Sequences[i].Channels[0] is AnimChannelStaticFloat && seq.Sequences[i].Channels[1] is AnimChannelStaticFloat && seq.Sequences[i].Channels[2] is AnimChannelStaticFloat) + { + var q = seq.Sequences[i].EvaluateQuaternion(0); + + return $" {q[index]}\r\n"; + } + + StringBuilder db = new StringBuilder(); + for (int f = 0; f < seq.NumFrames; f++) + { + db.AppendLine($" {seq.Sequences[i].EvaluateQuaternion(f)[index]}"); + } + + return db.ToString(); + } + + switch (chan) + { + case AnimChannelStaticFloat sf: + return $" {sf.FloatValue}\r\n"; + case AnimChannelStaticVector3 v3: + return $" {v3.Value[0]} {v3.Value[1]} {v3.Value[2]}\r\n"; + case AnimChannelStaticSmallestThreeQuaternion q3: + return $" {q3.Value[0]} {q3.Value[1]} {q3.Value[2]} {q3.Value[3]}\r\n"; + default: + { + StringBuilder db = new StringBuilder(); + for (int f = 0; f < seq.NumFrames; f++) + { + db.AppendLine($" {chan.EvaluateFloat(f)}"); + } + + return db.ToString(); + } + } + } + + if (channels.Length == 1) + { + var chanType = ChannelType(channels[0]); + var chanData = ChannelData(channels[0]); + sb.Append($@" FramesData SingleChannel{chanType} + {{ +{chanData} }} +"); + } + else + { + sb.Append($@" FramesData MultiChannel + {{ +{string.Join("", channels.Select(ch => $@" channel{ChannelType(ch)} + {{ +{ChannelData(ch)} }} +"))} }} +"); + } + } + + var animBits = sb.ToString(); + + writer.Write($@" {type} {dataType} {list.BoneId} + {{ +{animBits} }} +"); + + i++; + } + + writer.Write($@"}} +}} +"); + + writer.Flush(); } } -} +} \ No newline at end of file diff --git a/CodeWalker.Core/GameFiles/Resources/Clip.cs b/CodeWalker.Core/GameFiles/Resources/Clip.cs index ba392b3..11ee52c 100644 --- a/CodeWalker.Core/GameFiles/Resources/Clip.cs +++ b/CodeWalker.Core/GameFiles/Resources/Clip.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -275,9 +276,9 @@ namespace CodeWalker.GameFiles public uint Unknown_0Ch { get; set; } // 0x00000000 public ushort Unknown_10h { get; set; } public ushort Unknown_12h { get; set; } - public ushort Unknown_14h { get; set; } - public ushort Unknown_16h { get; set; } - public float Unknown_18h { get; set; } + public ushort Frames { get; set; } + public ushort SequenceFrameLimit { get; set; } + public float Duration { get; set; } public byte Unknown_1Ch { get; set; } public byte Unknown_1Dh { get; set; } public byte Unknown_1Eh { get; set; } @@ -288,8 +289,8 @@ namespace CodeWalker.GameFiles public uint Unknown_2Ch { get; set; } // 0x00000000 public uint Unknown_30h { get; set; } // 0x00000000 public uint Unknown_34h { get; set; } // 0x00000000 - public uint Unknown_38h { get; set; } - public uint Unknown_3Ch { get; set; } + public uint RawDataSize { get; set; } + public uint UsageCount { get; set; } public ResourcePointerList64 Sequences { get; set; } public ResourceSimpleList64_s BoneIds { get; set; } //public ResourceSimpleList64Ptr BoneIdsPtr { get; set; } @@ -307,9 +308,9 @@ namespace CodeWalker.GameFiles this.Unknown_0Ch = reader.ReadUInt32(); //0 0 0 0 this.Unknown_10h = reader.ReadUInt16(); //257 257 257 257 flags? this.Unknown_12h = reader.ReadUInt16(); //0 0 0 0 - this.Unknown_14h = reader.ReadUInt16(); //221 17 151 201 frames - this.Unknown_16h = reader.ReadUInt16(); //223 31 159 207 sequence limit? - this.Unknown_18h = reader.ReadSingle(); //7.34 0.53 5.0 6.66 duration + this.Frames = reader.ReadUInt16(); //221 17 151 201 frames + this.SequenceFrameLimit = reader.ReadUInt16(); //223 31 159 207 sequence limit? + this.Duration = reader.ReadSingle(); //7.34 0.53 5.0 6.66 duration this.Unknown_1Ch = reader.ReadByte(); //118 0 216 116 this.Unknown_1Dh = reader.ReadByte(); //152 36 130 182 this.Unknown_1Eh = reader.ReadByte(); //99 0 66 180 @@ -320,8 +321,8 @@ namespace CodeWalker.GameFiles this.Unknown_2Ch = reader.ReadUInt32(); //0 0 0 0 this.Unknown_30h = reader.ReadUInt32(); //0 0 0 0 this.Unknown_34h = reader.ReadUInt32(); //0 0 0 0 - this.Unknown_38h = reader.ReadUInt32(); //314 174 1238 390 sequences length? - this.Unknown_3Ch = reader.ReadUInt32(); //2 2 2 2 material/type? + this.RawDataSize = reader.ReadUInt32(); //314 174 1238 390 sequences length? + this.UsageCount = reader.ReadUInt32(); //2 2 2 2 material/type? this.Sequences = reader.ReadBlock>(); this.BoneIds = reader.ReadBlock>(); //this.BoneIdsPtr = reader.ReadStruct(); @@ -338,9 +339,9 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_0Ch); writer.Write(this.Unknown_10h); writer.Write(this.Unknown_12h); - writer.Write(this.Unknown_14h); - writer.Write(this.Unknown_16h); - writer.Write(this.Unknown_18h); + writer.Write(this.Frames); + writer.Write(this.SequenceFrameLimit); + writer.Write(this.Duration); writer.Write(this.Unknown_1Ch); writer.Write(this.Unknown_1Eh); writer.Write(this.Unknown_20h); @@ -349,8 +350,8 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_2Ch); writer.Write(this.Unknown_30h); writer.Write(this.Unknown_34h); - writer.Write(this.Unknown_38h); - writer.Write(this.Unknown_3Ch); + writer.Write(this.RawDataSize); + writer.Write(this.UsageCount); writer.WriteBlock(this.Sequences); writer.WriteBlock(this.BoneIds); } @@ -367,10 +368,374 @@ namespace CodeWalker.GameFiles { public ushort BoneId { get; set; } public byte Unk0 { get; set; } - public byte Unk1 { get; set; } + public byte Track { get; set; } public override string ToString() { - return BoneId.ToString() + ": " + Unk0.ToString() + ", " + Unk1.ToString(); + return BoneId.ToString() + ": " + Unk0.ToString() + ", " + Track.ToString(); + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public abstract class AnimChannel + { + public int Sequence { get; private set; } + public int Index { get; private set; } + + public abstract void Read(Sequence blockStream, ref int channelOffset); + + public virtual void ReadData(Sequence blockStream, ref int channelOffset) + { + + } + + public virtual void ReadFrame(Sequence blockStream, int frame, ref int frameOffset) + { + + } + + public virtual float EvaluateFloat(int frame) => 0.0f; + + public void Associate(int sequence, int index) + { + Sequence = sequence; + Index = index; + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticFloat : AnimChannel + { + public float FloatValue { get; set; } + + public override void Read(Sequence blockStream, ref int channelOffset) + { + FloatValue = BitConverter.ToSingle(blockStream.Data, channelOffset); + channelOffset += 4; + } + + public override float EvaluateFloat(int frame) + { + return FloatValue; + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticVector3 : AnimChannel + { + public Vector3 Value { get; set; } + + public override void Read(Sequence blockStream, ref int channelOffset) + { + Value = new Vector3( + BitConverter.ToSingle(blockStream.Data, channelOffset), + BitConverter.ToSingle(blockStream.Data, channelOffset + 4), + BitConverter.ToSingle(blockStream.Data, channelOffset + 8) + ); + + channelOffset += 12; + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticSmallestThreeQuaternion : AnimChannel + { + public Quaternion Value { get; set; } + + public override void Read(Sequence blockStream, ref int channelOffset) + { + var vec = new Vector3( + BitConverter.ToSingle(blockStream.Data, channelOffset), + BitConverter.ToSingle(blockStream.Data, channelOffset + 4), + BitConverter.ToSingle(blockStream.Data, channelOffset + 8) + ); + + Value = new Quaternion( + vec, + (float)Math.Sqrt(Math.Max(1.0f - vec.LengthSquared(), 0.0)) + ); + + channelOffset += 12; + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelIndirectQuantizeFloat : AnimChannel + { + public int FrameBits { get; set; } + public int ValueBits { get; set; } + public float Quantum { get; set; } + public float Offset { get; set; } + public float[] Values { get; set; } + public uint[] Frames { get; set; } + + private int numInts; + + public override void Read(Sequence blockStream, ref int channelOffset) + { + FrameBits = BitConverter.ToInt32(blockStream.Data, channelOffset); + ValueBits = BitConverter.ToInt32(blockStream.Data, channelOffset + 4); + numInts = BitConverter.ToInt32(blockStream.Data, channelOffset + 8); + Quantum = BitConverter.ToSingle(blockStream.Data, channelOffset + 12); + Offset = BitConverter.ToSingle(blockStream.Data, channelOffset + 16); + + channelOffset += 20; + + var bit = channelOffset * 8; + var endBit = bit + (numInts * 32); + + channelOffset += numInts * 4; + + var valueList = new List(); + + while (bit < endBit) + { + valueList.Add((blockStream.GetBit(bit, ValueBits) * Quantum) + Offset); + + bit += ValueBits; + } + + Values = valueList.ToArray(); + + Frames = new uint[blockStream.NumFrames]; + } + + public override void ReadData(Sequence blockStream, ref int channelOffset) + { + + } + + public override void ReadFrame(Sequence blockStream, int frame, ref int frameOffset) + { + Frames[frame] = blockStream.GetBit(frameOffset, FrameBits); + + frameOffset += FrameBits; + } + + public override float EvaluateFloat(int frame) + { + return Values[Frames[frame]]; + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelQuantizeFloat : AnimChannel + { + public int ValueBits { get; set; } + public float Quantum { get; set; } + public float Offset { get; set; } + public float[] Values { get; set; } + + public override void Read(Sequence blockStream, ref int channelOffset) + { + ValueBits = BitConverter.ToInt32(blockStream.Data, channelOffset); + Quantum = BitConverter.ToSingle(blockStream.Data, channelOffset + 4); + Offset = BitConverter.ToSingle(blockStream.Data, channelOffset + 8); + Values = new float[blockStream.NumFrames]; + + channelOffset += 12; + } + + public override void ReadFrame(Sequence blockStream, int frame, ref int frameOffset) + { + Values[frame] = (blockStream.GetBit(frameOffset, ValueBits) * Quantum) + Offset; + frameOffset += ValueBits; + } + + public override float EvaluateFloat(int frame) + { + return Values[frame]; + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelLinearFloat : AnimChannel + { + public float Quantum { get; set; } + public float Offset { get; set; } + public float[] Values { get; set; } + + private Sequence blockStream; + + private int NumInts { get; set; } + + private int Count1 { get; set; } + private int Count2 { get; set; } + private int Count3 { get; set; } + + private int Bit { get; set; } + + public override void Read(Sequence blockStream, ref int channelOffset) + { + NumInts = BitConverter.ToInt32(blockStream.Data, channelOffset); + var counts = BitConverter.ToInt32(blockStream.Data, channelOffset + 4); + Quantum = BitConverter.ToSingle(blockStream.Data, channelOffset + 8); + Offset = BitConverter.ToSingle(blockStream.Data, channelOffset + 12); + + var count1 = counts & 0xFF; + var count2 = (counts >> 8) & 0xFF; + var count3 = (counts >> 16) & 0xFF; + + Count1 = count1; + Count2 = count2; + Count3 = count3; + + Values = new float[blockStream.NumFrames]; + + Bit = (channelOffset * 8) + 128; + + this.blockStream = blockStream; + + var numChunks = (blockStream.ChunkSize + blockStream.NumFrames) / blockStream.ChunkSize; + + for (int i = 0; i < blockStream.NumFrames; i++) + { + Values[i] = ReconstructFloat(i); + } + + channelOffset += NumInts * 4; + } + + private float ReconstructFloat(int frame) + { + var chunkSize = blockStream.ChunkSize; + var numChunks = (blockStream.ChunkSize + blockStream.NumFrames) / chunkSize; + + var startBit = Bit; + var startBit2 = (startBit + (numChunks * Count1)); + uint startBit3 = (uint)(startBit2 + (numChunks * Count2)); + + var chunkIdx = (frame / chunkSize); + var chunkOff = (frame % chunkSize); + + var offset = (Count1 != 0) ? blockStream.GetBit(startBit + (chunkIdx * Count1), Count1) : 0; + var value = (Count2 != 0) ? (int)blockStream.GetBit(startBit2 + (chunkIdx * Count2), Count2) : 0; + + var inc = 0; + + for (int i = 0; i < chunkOff; i++) + { + var delta = (Count3 != 0) ? (int)blockStream.GetBit((int)(startBit3 + offset), Count3) : 0; + offset += (uint)Count3; + + // scan for a '1' bit + uint bitIdx = 0; + + { + uint b = 0; + + do + { + b = blockStream.GetBit((int)(startBit3 + offset + bitIdx), 1); + bitIdx++; + } while (b == 0); + } + + offset += bitIdx; + + delta |= (int)(((bitIdx - 1) << Count3)); + + if (delta != 0) + { + var sign = blockStream.GetBit((int)(startBit3 + offset), 1); + offset += 1; + + if (sign == 1) + { + delta = -delta; + } + } + + inc += delta; + value += inc; + } + + return (value * Quantum) + Offset; + } + + public override float EvaluateFloat(int frame) + { + return Values[frame]; + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelType7 : AnimChannel + { + private Sequence blockStream; + + private float[] valueCache; + + public float[] Values + { + get + { + if (valueCache != null) + { + return valueCache; + } + + valueCache = new float[blockStream.NumFrames]; + + var channels = new AnimChannel[3]; + var ch = 0; + + for (int i = 0; i < 4; i++) + { + if (i != Index) + { + channels[ch] = blockStream.Sequences[Sequence].Channels[i]; + ch++; + } + } + + for (int i = 0; i < valueCache.Length; i++) + { + var vec = new Vector3( + channels[0].EvaluateFloat(i), + channels[1].EvaluateFloat(i), + channels[2].EvaluateFloat(i) + ); + + valueCache[i] = (float)Math.Sqrt(Math.Max(1.0f - vec.LengthSquared(), 0.0)); + } + + return valueCache; + } + } + + public int QuatIndex { get; internal set; } + + public override float EvaluateFloat(int frame) + { + return Values[frame]; + } + + public override void Read(Sequence blockStream, ref int channelOffset) + { + this.blockStream = blockStream; + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimSequence + { + public AnimChannel[] Channels { get; set; } + public bool IsType7Quat { get; internal set; } + + public Quaternion EvaluateQuaternion(int frame) + { + if (!IsType7Quat) + { + return new Quaternion( + Channels[0].EvaluateFloat(frame), + Channels[1].EvaluateFloat(frame), + Channels[2].EvaluateFloat(frame), + Channels[3].EvaluateFloat(frame) + ); + } + + var t7 = (AnimChannelType7)Channels[3]; + + var x = Channels[0].EvaluateFloat(frame); + var y = Channels[1].EvaluateFloat(frame); + var z = Channels[2].EvaluateFloat(frame); + var normalized = t7.EvaluateFloat(frame); + + switch (t7.QuatIndex) + { + case 0: + return new Quaternion(normalized, x, y, z); + case 1: + return new Quaternion(x, normalized, y, z); + case 2: + return new Quaternion(x, y, normalized, z); + case 3: + return new Quaternion(x, y, z, normalized); + default: + return Quaternion.Identity; + } } } [TypeConverter(typeof(ExpandableObjectConverter))] public class Sequence : ResourceSystemBlock @@ -384,41 +749,58 @@ namespace CodeWalker.GameFiles public MetaHash Unknown_00h { get; set; } //identifier / name? public uint DataLength { get; set; } public uint Unused_08h { get; set; } // 0x00000000 - public uint Part1Offset { get; set; } //offset to data items / bytes used by "Part0"? + public uint FrameOffset { get; set; } //offset to data items / bytes used by "Part0"? public uint UnkLength { get; set; } //total block length? usually == BlockLength public ushort Unused_14h { get; set; } //0x0000 - public ushort Part1Count { get; set; } // count of data items - public ushort Part1Stride { get; set; } //stride of data item + public ushort NumFrames { get; set; } // count of data items + public ushort FrameLength { get; set; } //stride of data item public ushort Unknown_1Ah { get; set; } //? public ushort Unknown_1Ch { get; set; } //? - public byte Unknown_1Eh_Type { get; set; } //64|255 0x40|0xFF + public byte ChunkSize { get; set; } //64|255 0x40|0xFF public byte Unknown_1Fh_Type { get; set; } //0|17|20|21|49|52|53 0x11|0x14|0x15|0x31|0x34|0x35 public byte[] Data { get; set; } - public SequencePart1[] Part1 { get; set; } + public SequencePart1[] FrameData { get; set; } public ushort[] Part2 { get; set; } public int Part2Count { get; set; } public int Part2Offset { get; set; } - + // parsed data + public AnimSequence[] Sequences { get; set; } //public static Dictionary SeqDict = new Dictionary(); + + + class AnimChannelListItem + { + public int Sequence; + public int Index; + public AnimChannel Channel; + public AnimChannelListItem(int seq, int ind, AnimChannel channel) + { + Sequence = seq; + Index = ind; + Channel = channel; + } + } + + public override void Read(ResourceDataReader reader, params object[] parameters) { // read structure data this.Unknown_00h = reader.ReadUInt32();//2965995365 2837183178 this.DataLength = reader.ReadUInt32(); //282 142 1206 358 this.Unused_08h = reader.ReadUInt32();//0 0 0 0 - this.Part1Offset = reader.ReadUInt32();//224 (E0) 32 (20) 536 (218) 300 + this.FrameOffset = reader.ReadUInt32();//224 (E0) 32 (20) 536 (218) 300 this.UnkLength = reader.ReadUInt32();//314 174 1238 390 (=Length) this.Unused_14h = reader.ReadUInt16();//0 0 0 0 - this.Part1Count = reader.ReadUInt16();//221 (DD) 17 (11) 151 (97) 201 - this.Part1Stride = reader.ReadUInt16();//0 4 4 0 + this.NumFrames = reader.ReadUInt16();//221 (DD) 17 (11) 151 (97) 201 + this.FrameLength = reader.ReadUInt16();//0 4 4 0 this.Unknown_1Ah = reader.ReadUInt16();//0 0 106 0 this.Unknown_1Ch = reader.ReadUInt16();//0 17 0 0 bone? - this.Unknown_1Eh_Type = reader.ReadByte(); //64 255 255 64 + this.ChunkSize = reader.ReadByte(); //64 255 255 64 this.Unknown_1Fh_Type = reader.ReadByte(); //0 0 0 0 @@ -435,22 +817,22 @@ namespace CodeWalker.GameFiles { } - if ((Part1Stride % 4) > 0) + if ((FrameLength % 4) > 0) { } - int offset = (int)Part1Offset; - if (Part1Stride > 0) + int offset = (int)FrameOffset; + if (FrameLength > 0) { - Part1 = new SequencePart1[Part1Count]; - for (int i = 0; i < Part1Count; i++) + FrameData = new SequencePart1[NumFrames]; + for (int i = 0; i < NumFrames; i++) { var sp = new SequencePart1(); - sp.Init(Data, offset, Part1Stride); - Part1[i] = sp; - offset += Part1Stride; + sp.Init(Data, offset, FrameLength); + FrameData[i] = sp; + offset += FrameLength; } } - else if (Part1Count != 0) + else if (NumFrames != 0) { } int brem = (int)DataLength - offset; @@ -476,15 +858,15 @@ namespace CodeWalker.GameFiles //if (SeqDict.ContainsKey(Unknown_1Ah)) SeqDict[Unknown_1Ah]++; //else SeqDict[Unknown_1Ah] = 1; - if ((Unknown_1Ah != 0) && (Unknown_1Ah > Part1Offset)) + if ((Unknown_1Ah != 0) && (Unknown_1Ah > FrameOffset)) { } - if ((Unknown_1Ch != 0) && (Unknown_1Ch > Part1Offset)) + if ((Unknown_1Ch != 0) && (Unknown_1Ch > FrameOffset)) { } - switch (Unknown_1Eh_Type) + switch (ChunkSize) { case 64: //0x40 case 255: //0xFF @@ -507,7 +889,156 @@ namespace CodeWalker.GameFiles break; } + int channelBitOffset = 0; + int channelFrameOffset = unchecked((int)(FrameOffset * 8)); + int channelListOffset = Part2Offset; + int channelDataOffset = Part2Offset + (9 * 2); + var animChannelList = new List(); + + var channelLists = new AnimChannel[9][]; + + for (int i = 0; i < 9; i++) + { + int channelCount = BitConverter.ToUInt16(Data, channelListOffset); + + /*if (channelCount > 4) + { + Debug.Assert(false, "More than 4 channels per type are currently unsupported!"); + }*/ + + var channels = new AnimChannel[channelCount]; + + for (int c = 0; c < channelCount; c++) + { + AnimChannel channel = null; + + switch (i) + { + case 0: + channel = new AnimChannelStaticSmallestThreeQuaternion(); + break; + case 1: + channel = new AnimChannelStaticVector3(); + break; + case 2: + channel = new AnimChannelStaticFloat(); + break; + case 3: + // crAnimChannelRawFloat + break; + case 4: + channel = new AnimChannelQuantizeFloat(); + break; + case 5: + channel = new AnimChannelIndirectQuantizeFloat(); + break; + case 6: + channel = new AnimChannelLinearFloat(); + break; + case 7: + // normalized W from quaternion (evaluate first three channels, calculate W) + channel = new AnimChannelType7(); + break; + case 8: + // unknown extra + // kind of the same as above but different at runtime? + channel = new AnimChannelType7(); + break; + } + + //Debug.Assert(channel != null, "Unsupported channel"); + + if (channel != null) + { + channel.Read(this, ref channelBitOffset); + } + + channels[c] = channel; + } + + for (int c = 0; c < channelCount; c++) + { + var channel = channels[c]; + + if (channel != null) + { + channel.ReadData(this, ref channelBitOffset); + } + + var channelDataBit = BitConverter.ToUInt16(Data, channelDataOffset + (c * 2)); + var sequence = channelDataBit >> 2; + var index = channelDataBit & 3; + + if (channel != null) + { + if (i == 7 || i == 8) + { + if (channel is AnimChannelType7 t7) + { + t7.QuatIndex = index; + } + + index = 3; + } + + channel.Associate(sequence, index); + + animChannelList.Add(new AnimChannelListItem(sequence, index, channel)); + } + } + + if (channelCount > 0) + { + var listSize = ((channelCount + 3) / 4) * 4; + + channelDataOffset += (2 * listSize); + } + + channelListOffset += 2; + + channelLists[i] = channels; + } + + for (int f = 0; f < NumFrames; f++) + { + channelFrameOffset = unchecked((int)((FrameOffset + (FrameLength * f)) * 8)); + + for (int i = 0; i < channelLists.Length; i++) + { + var channels = channelLists[i]; + + for (int c = 0; c < channels.Length; c++) + { + var channel = channels[c]; + + if (channel != null) + { + channel.ReadFrame(this, f, ref channelFrameOffset); + } + } + } + } + + Sequences = new AnimSequence[animChannelList.Max(a => a.Sequence) + 1]; + + for (int i = 0; i < Sequences.Length; i++) + { + var thisSeq = animChannelList.Where(a => a.Sequence == i); + + Sequences[i] = new AnimSequence(); + Sequences[i].Channels = new AnimChannel[thisSeq.Max(a => a.Index) + 1]; + + for (int j = 0; j < Sequences[i].Channels.Length; j++) + { + Sequences[i].Channels[j] = thisSeq.First(a => a.Index == j).Channel; + + if (Sequences[i].Channels[j] is AnimChannelType7) + { + Sequences[i].IsType7Quat = true; + } + } + } } public override void Write(ResourceDataWriter writer, params object[] parameters) @@ -516,12 +1047,12 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_00h); writer.Write(this.DataLength); writer.Write(this.Unused_08h); - writer.Write(this.Part1Offset); + writer.Write(this.FrameOffset); writer.Write(this.UnkLength); writer.Write(this.Unused_14h); - writer.Write(this.Part1Stride); + writer.Write(this.FrameLength); writer.Write(this.Unknown_1Ch); - writer.Write(this.Unknown_1Eh_Type); + writer.Write(this.ChunkSize); writer.Write(this.Data); } @@ -529,6 +1060,27 @@ namespace CodeWalker.GameFiles { return Unknown_00h.ToString() + ": " + DataLength.ToString(); } + + public uint GetBit(int startBit, int length) + { + var mask = MaskTable[length]; + + var lowByte = BitConverter.ToUInt32(Data, (startBit / 32) * 4); + var highByte = BitConverter.ToUInt32(Data, ((startBit / 32) + 1) * 4); + + var pair = ((ulong)highByte << 32) | lowByte; + + return (uint)((pair >> (startBit % 32)) & mask); + } + + private static uint[] MaskTable = new uint[] + { + 0, 1, 3, 7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, + 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, 0x1FFFF, + 0x3FFFF, 0x7FFFF, 0xFFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, + 0xFFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF, + 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF + }; } [TypeConverter(typeof(ExpandableObjectConverter))] public class SequencePart1 @@ -820,7 +1372,7 @@ namespace CodeWalker.GameFiles public ushort AnimationsCount1 { get; set; } public ushort AnimationsCount2 { get; set; } public uint Unknown_5Ch { get; set; } // 0x00000000 - public uint Unknown_60h { get; set; } + public float Duration { get; set; } public uint Unknown_64h { get; set; } // 0x00000001 public uint Unknown_68h { get; set; } // 0x00000000 public uint Unknown_6Ch { get; set; } // 0x00000000 @@ -835,7 +1387,7 @@ namespace CodeWalker.GameFiles this.AnimationsCount1 = reader.ReadUInt16(); this.AnimationsCount2 = reader.ReadUInt16(); this.Unknown_5Ch = reader.ReadUInt32(); - this.Unknown_60h = reader.ReadUInt32(); + this.Duration = reader.ReadSingle(); this.Unknown_64h = reader.ReadUInt32(); this.Unknown_68h = reader.ReadUInt32(); this.Unknown_6Ch = reader.ReadUInt32(); @@ -858,7 +1410,7 @@ namespace CodeWalker.GameFiles writer.Write(this.AnimationsCount1); writer.Write(this.AnimationsCount2); writer.Write(this.Unknown_5Ch); - writer.Write(this.Unknown_60h); + writer.Write(this.Duration); writer.Write(this.Unknown_64h); writer.Write(this.Unknown_68h); writer.Write(this.Unknown_6Ch); @@ -880,9 +1432,9 @@ namespace CodeWalker.GameFiles } // structure data - public float Unknown_00h { get; set; } - public float Unknown_04h { get; set; } - public float Unknown_08h { get; set; } + public float StartTime { get; set; } + public float EndTime { get; set; } + public float Rate { get; set; } public uint Unknown_0Ch { get; set; } // 0x00000000 public ulong AnimationPointer { get; set; } @@ -892,9 +1444,9 @@ namespace CodeWalker.GameFiles public override void Read(ResourceDataReader reader, params object[] parameters) { // read structure data - this.Unknown_00h = reader.ReadSingle(); - this.Unknown_04h = reader.ReadSingle(); - this.Unknown_08h = reader.ReadSingle(); + this.StartTime = reader.ReadSingle(); + this.EndTime = reader.ReadSingle(); + this.Rate = reader.ReadSingle(); this.Unknown_0Ch = reader.ReadUInt32(); this.AnimationPointer = reader.ReadUInt64(); @@ -910,9 +1462,9 @@ namespace CodeWalker.GameFiles this.AnimationPointer = (ulong)(this.Animation != null ? this.Animation.FilePosition : 0); // write structure data - writer.Write(this.Unknown_00h); - writer.Write(this.Unknown_04h); - writer.Write(this.Unknown_08h); + writer.Write(this.StartTime); + writer.Write(this.EndTime); + writer.Write(this.Rate); writer.Write(this.Unknown_0Ch); writer.Write(this.AnimationPointer); } @@ -1670,4 +2222,4 @@ namespace CodeWalker.GameFiles } -} +} \ No newline at end of file