using System; using System.Collections.Generic; using System.Linq; using Syroot.NintenTools.NSW.Bfres; using ResU = Syroot.NintenTools.Bfres; using System.Windows; using Syroot.Maths; using BrawlLib.SSBB.ResourceNodes; using BrawlLib.Wii.Animations; namespace BrawlboxHelper { public class KeyGroupData { public float Value; } public class BoneAnimKeyData { public Dictionary XPOS = new Dictionary(); public Dictionary YPOS = new Dictionary(); public Dictionary ZPOS = new Dictionary(); public Dictionary XROT = new Dictionary(); public Dictionary YROT = new Dictionary(); public Dictionary ZROT = new Dictionary(); public Dictionary XSCA = new Dictionary(); public Dictionary YSCA = new Dictionary(); public Dictionary ZSCA = new Dictionary(); } public class FSHUConverter { public static ResU.ShaderParamAnim Clr02Fshu(string FileName) { CLR0Node clr0 = NodeFactory.FromFile(null, FileName) as CLR0Node; ResU.ShaderParamAnim fshu = new ResU.ShaderParamAnim(); fshu.FrameCount = clr0.FrameCount; fshu.Name = clr0.Name; fshu.Path = clr0.OriginalPath; fshu.UserData = new ResU.ResDict(); //Set flags if (clr0.Loop) fshu.Flags |= ResU.ShaderParamAnimFlags.Looping; //Set mat anims and then calculate data after foreach (var entry in clr0.Children) { if (entry is CLR0MaterialNode) fshu.ShaderParamMatAnims.Add(Clr0Entry2ShaderMatAnim(clr0, (CLR0MaterialNode)entry)); } fshu.BakedSize = CalculateBakeSize(fshu); fshu.BindIndices = SetIndices(fshu); return fshu; } public static ResU.ShaderParamMatAnim Clr0Entry2ShaderMatAnim(CLR0Node clr0, CLR0MaterialNode clrMaterial) { ResU.ShaderParamMatAnim matAnim = new ResU.ShaderParamMatAnim(); matAnim.Name = clrMaterial.Name; matAnim.Constants = new List(); matAnim.Curves = new List(); matAnim.ParamAnimInfos = new List(); foreach (var entry in clrMaterial.Children) { ushort curveIndex = 0; ushort constantIndex = 0; CLR0MaterialEntryNode ParamEntry = (CLR0MaterialEntryNode)entry; //Add constants for RGBA if constant if (ParamEntry.Constant) { //Red matAnim.Constants.Add(new ResU.AnimConstant() { AnimDataOffset = 0, Value = (float)ParamEntry.Colors[0].R / 255f, }); //Green matAnim.Constants.Add(new ResU.AnimConstant() { AnimDataOffset = 4, Value = (float)ParamEntry.Colors[0].G / 255f, }); //Blue matAnim.Constants.Add(new ResU.AnimConstant() { AnimDataOffset = 8, Value = (float)ParamEntry.Colors[0].B / 255f, }); //Alpha matAnim.Constants.Add(new ResU.AnimConstant() { AnimDataOffset = 12, Value = (float)ParamEntry.Colors[0].A / 255f, }); } var RedCurve = GenerateCurve(0, clr0, ParamEntry); var GreenCurve = GenerateCurve(4, clr0, ParamEntry); var BlueCurve = GenerateCurve(8, clr0, ParamEntry); var AlphaCurve = GenerateCurve(12, clr0, ParamEntry); if (RedCurve != null) matAnim.Curves.Add(RedCurve); if (GreenCurve != null) matAnim.Curves.Add(GreenCurve); if (BlueCurve != null) matAnim.Curves.Add(BlueCurve); if (AlphaCurve != null) matAnim.Curves.Add(AlphaCurve); matAnim.ParamAnimInfos.Add(new ResU.ParamAnimInfo() { Name = entry.Name, BeginCurve = matAnim.Curves.Count > 0 ? curveIndex : ushort.MaxValue, FloatCurveCount = (ushort)matAnim.Curves.Count, SubBindIndex = ushort.MaxValue, ConstantCount = (ushort)matAnim.Constants.Count, BeginConstant = matAnim.Constants.Count > 0 ? constantIndex : ushort.MaxValue, }); constantIndex += (ushort)matAnim.Constants.Count; curveIndex += (ushort)matAnim.Curves.Count; } return matAnim; } private static ResU.AnimCurve GenerateCurve(uint AnimOffset, CLR0Node anim, CLR0MaterialEntryNode entry) { ResU.AnimCurve curve = new ResU.AnimCurve(); curve.AnimDataOffset = AnimOffset; curve.StartFrame = 0; curve.Offset = 0; curve.Scale = 1; curve.FrameType = ResU.AnimCurveFrameType.Single; curve.KeyType = ResU.AnimCurveKeyType.Single; curve.CurveType = ResU.AnimCurveType.Linear; List Frames = new List(); List Keys = new List(); for (int c = 0; c < entry.Colors.Count; c++) { Frames.Add(c); //Max of 4 values. Cubic using 4, linear using 2, and step using 1 float[] KeyValues = new float[4]; switch (AnimOffset) { case 0: //Red Keys.Add((float)entry.Colors[c].R / 255f); break; case 4: //Green Keys.Add((float)entry.Colors[c].G / 255f); break; case 8: //Blue Keys.Add((float)entry.Colors[c].B / 255f); break; case 12: //Alpha Keys.Add((float)entry.Colors[c].A / 255f); break; default: throw new Exception("Invalid animation offset set!"); } } //Max value in frames is our end frame curve.EndFrame = Frames.Max(); curve.Frames = Frames.ToArray(); //If a curve only has one frame we don't need to interpolate or add keys to a curve as it's constant if (curve.Frames.Length <= 1) return null; switch (curve.CurveType) { case ResU.AnimCurveType.Cubic: curve.Keys = new float[Keys.Count, 4]; for (int frame = 0; frame < Keys.Count; frame++) { float Delta = 0; if (frame < Keys.Count - 1) Delta = Keys[frame + 1] - Keys[frame]; float value = Keys[frame]; float Slope = 0; float Slope2 = 0; curve.Keys[frame, 0] = value; curve.Keys[frame, 1] = Slope; curve.Keys[frame, 2] = Slope2; curve.Keys[frame, 3] = Delta; } break; case ResU.AnimCurveType.StepInt: //Step requires no interpolation curve.Keys = new float[Keys.Count, 1]; for (int frame = 0; frame < Keys.Count; frame++) { curve.Keys[frame, 0] = 0; } break; case ResU.AnimCurveType.Linear: curve.Keys = new float[Keys.Count, 2]; for (int frame = 0; frame < Keys.Count; frame++) { //Delta for second value used in linear curves float time = curve.Frames[frame]; float Delta = 0; if (frame < Keys.Count - 1) Delta = Keys[frame + 1] - Keys[frame]; curve.Keys[frame, 0] = Keys[frame]; curve.Keys[frame, 1] = Delta; } break; } return curve; } private static ushort[] SetIndices(ResU.ShaderParamAnim fshu) { List indces = new List(); foreach (var matAnim in fshu.ShaderParamMatAnims) indces.Add(65535); return indces.ToArray(); } private static uint CalculateBakeSize(ResU.ShaderParamAnim fshu) { return 0; } } public class FTXPConverter { public static void Pat02Ftxp(MaterialAnim matAnim, string FileName) { PAT0Node pat0 = new PAT0Node(); matAnim.FrameCount = pat0.FrameCount; matAnim.Name = pat0.Name; matAnim.Path = pat0.OriginalPath; matAnim.Loop = pat0.Loop; foreach (var entry in pat0.Children) { var Material = (PAT0EntryNode)entry; } } } public class FSKAConverter { public static List BoneAnimKeys = new List(); static float Deg2Rad = (float)(Math.PI / 180f); static float Rad2Deg = (float)(180f / Math.PI); public static SkeletalAnim Anim2Fska(string FileName) { CHR0Node chr0 = AnimFormat.Read(FileName); return Chr02Fska(chr0); } public static void Fska2Chr0(SkeletalAnim fska, string FileName) { Console.WriteLine("Making CHR0Node"); CHR0Node chr0 = new CHR0Node(); Console.WriteLine("Created CHR0Node"); chr0.FrameCount = fska.FrameCount; chr0.Name = fska.Name; chr0.OriginalPath = fska.Path; chr0.UserEntries = new UserDataCollection(); chr0.Loop = fska.Loop; foreach (var entry in fska.BoneAnims) BoneAnim2Chr0Entry(entry, chr0); Console.WriteLine("Exporting CHR0Node"); chr0.Export(FileName); } public class FSKAKeyNode { public float Value = 0; public float Slope = 0; public float Slope2 = 0; public float Delta = 0; public float Frame = 0; } public static void BoneAnim2Chr0Entry(BoneAnim boneAnim, CHR0Node chr0) { CHR0EntryNode chr0Entry = chr0.CreateEntry(boneAnim.Name); chr0Entry.UseModelRotate = false; chr0Entry.UseModelScale = false; chr0Entry.UseModelTranslate = false; chr0Entry.ScaleCompensateApply = boneAnim.ApplySegmentScaleCompensate; //Float for time/frame Dictionary TranslateX = new Dictionary(); Dictionary TranslateY = new Dictionary(); Dictionary TranslateZ = new Dictionary(); Dictionary RotateX = new Dictionary(); Dictionary RotateY = new Dictionary(); Dictionary RotateZ = new Dictionary(); Dictionary ScaleX = new Dictionary(); Dictionary ScaleY = new Dictionary(); Dictionary ScaleZ = new Dictionary(); if (boneAnim.FlagsBase.HasFlag(BoneAnimFlagsBase.Translate)) { TranslateX.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Translate.X, }); TranslateY.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Translate.Y, }); TranslateZ.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Translate.Z, }); } if (boneAnim.FlagsBase.HasFlag(BoneAnimFlagsBase.Rotate)) { RotateX.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Rotate.X, }); RotateY.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Rotate.Y, }); RotateZ.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Rotate.Z, }); } if (boneAnim.FlagsBase.HasFlag(BoneAnimFlagsBase.Scale)) { ScaleX.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Scale.X, }); ScaleY.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Scale.Y, }); ScaleZ.Add(0, new FSKAKeyNode() { Value = boneAnim.BaseData.Scale.Z, }); } else { ScaleX.Add(0, new FSKAKeyNode() { Value = 1 }); ScaleY.Add(0, new FSKAKeyNode() { Value = 1 }); ScaleZ.Add(0, new FSKAKeyNode() { Value = 1 }); } foreach (var curve in boneAnim.Curves) { for (int frame = 0; frame < curve.Frames.Length; frame++) { float time = curve.Frames[frame]; float value = 0; float slope = 0; float slope2 = 0; float delta = 0; float scale = curve.Scale; if (scale <= 0) scale = 1; if (curve.CurveType == AnimCurveType.Cubic) { value = curve.Offset + curve.Keys[frame, 0] * scale; slope = curve.Offset + curve.Keys[frame, 1] * scale; slope2 = curve.Offset + curve.Keys[frame, 2] * scale; delta = curve.Offset + curve.Keys[frame, 3] * scale; } if (curve.CurveType == AnimCurveType.Linear) { value = curve.Offset + curve.Keys[frame, 0] * scale; delta = curve.Offset + curve.Keys[frame, 1] * scale; } if (curve.CurveType == AnimCurveType.StepInt) { value = curve.Offset + curve.Keys[frame, 0] * scale; } switch (curve.AnimDataOffset) { case 0x10: if (TranslateX.Count > 0 && frame == 0) TranslateX.Remove(0); TranslateX.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; case 0x14: if (TranslateY.Count > 0 && frame == 0) TranslateY.Remove(0); TranslateY.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; case 0x18: if (TranslateZ.Count > 0 && frame == 0) TranslateZ.Remove(0); TranslateZ.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; case 0x20: if (RotateX.Count > 0 && frame == 0) RotateX.Remove(0); RotateX.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; case 0x24: if (RotateY.Count > 0 && frame == 0) RotateY.Remove(0); RotateY.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; case 0x28: if (RotateZ.Count > 0 && frame == 0) RotateZ.Remove(0); RotateZ.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; case 0x04: if (ScaleX.Count > 0 && frame == 0) ScaleX.Remove(0); ScaleX.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; case 0x08: if (ScaleY.Count > 0 && frame == 0) ScaleY.Remove(0); ScaleY.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; case 0x0C: if (ScaleZ.Count > 0 && frame == 0) ScaleZ.Remove(0); ScaleZ.Add(time, new FSKAKeyNode() { Value = value, Slope = slope, Slope2 = slope2, Delta = delta, Frame = time, }); break; } } } for (int frame = 0; frame < chr0.FrameCount; frame++) { if (TranslateX.ContainsKey(frame)) chr0Entry.SetKeyframe(6, frame, TranslateX[frame].Value); if (TranslateY.ContainsKey(frame)) chr0Entry.SetKeyframe(7, frame, TranslateY[frame].Value); if (TranslateZ.ContainsKey(frame)) chr0Entry.SetKeyframe(8, frame, TranslateZ[frame].Value); if (RotateX.ContainsKey(frame)) chr0Entry.SetKeyframe(3, frame, RotateX[frame].Value * Rad2Deg); if (RotateY.ContainsKey(frame)) chr0Entry.SetKeyframe(4, frame, RotateY[frame].Value * Rad2Deg); if (RotateZ.ContainsKey(frame)) chr0Entry.SetKeyframe(5, frame, RotateZ[frame].Value * Rad2Deg); if (ScaleX.ContainsKey(frame)) chr0Entry.SetKeyframe(0, frame, ScaleX[frame].Value); if (ScaleY.ContainsKey(frame)) chr0Entry.SetKeyframe(1, frame, ScaleY[frame].Value); if (ScaleZ.ContainsKey(frame)) chr0Entry.SetKeyframe(2, frame, ScaleZ[frame].Value); } } public static SkeletalAnim Chr02Fska(string FileName) { CHR0Node chr0 = CHR0Node.FromFile(FileName); return Chr02Fska(chr0); } private static SkeletalAnim Chr02Fska(CHR0Node chr0) { SkeletalAnim fska = new SkeletalAnim(); fska.FrameCount = chr0.FrameCount; fska.Name = chr0.Name; fska.Path = chr0.OriginalPath; fska.UserDatas = new List(); fska.UserDataDict = new ResDict(); //Set flags if (chr0.Loop) fska.FlagsAnimSettings |= SkeletalAnimFlags.Looping; fska.FlagsRotate = SkeletalAnimFlagsRotate.EulerXYZ; fska.FlagsScale = SkeletalAnimFlagsScale.Maya; //Set bone anims and then calculate data after foreach (var entry in chr0.Children) fska.BoneAnims.Add(Chr0Entry2BoneAnim((CHR0EntryNode)entry)); fska.BakedSize = CalculateBakeSize(fska); fska.BindIndices = SetIndices(fska); return fska; } private static string NameConverterMkWii2Mk8(string name) { switch (name) { case "skl_root": return "Skl_Root"; case "leg_l1": return "LegL"; case "leg_l2": return "KneeL"; case "ankle_l1": return "FootL"; case "leg_r1": return "LegR"; case "leg_r2": return "KneeR"; case "ankle_r1": return "FootR"; case "spin": return "Spine1"; case "arm_l1": return "ShoulderL"; case "arm_l2": return "ArmL"; case "wrist_l1": return "HandL"; case "arm_r1": return "ShoulderR"; case "arm_r2": return "ArmR"; case "wrist_r1": return "HandR"; case "face_1": return "Head"; } return name; } private static BoneAnim Chr0Entry2BoneAnim(CHR0EntryNode entry) { BoneAnim boneAnim = new BoneAnim(); boneAnim.Name = entry.Name; // boneAnim.Name = NameConverterMkWii2Mk8(boneAnim.Name); if (entry.UseModelTranslate) boneAnim.FlagsBase |= BoneAnimFlagsBase.Translate; if (entry.UseModelRotate) boneAnim.FlagsBase |= BoneAnimFlagsBase.Rotate; if (entry.UseModelScale) boneAnim.FlagsBase |= BoneAnimFlagsBase.Scale; var baseData = new BoneAnimData(); var FirstFrame = entry.GetAnimFrame(0); if (FirstFrame.HasKeys) { float xRadian = FirstFrame.Rotation._x * Deg2Rad; float yRadian = FirstFrame.Rotation._y * Deg2Rad; float zRadian = FirstFrame.Rotation._z * Deg2Rad; baseData.Translate = new Vector3F(FirstFrame.Translation._x, FirstFrame.Translation._y, FirstFrame.Translation._z); baseData.Rotate = new Vector4F(xRadian, yRadian, zRadian, 1); baseData.Scale = new Vector3F(FirstFrame.Scale._x, FirstFrame.Scale._y, FirstFrame.Scale._z); baseData.Flags = 0; boneAnim.BaseData = baseData; } else { baseData.Translate = new Vector3F(0,0,0); baseData.Rotate = new Vector4F(0, 0, 0, 1); baseData.Scale = new Vector3F(1, 1, 1); baseData.Flags = 0; boneAnim.BaseData = baseData; } boneAnim.FlagsBase |= BoneAnimFlagsBase.Translate; boneAnim.FlagsBase |= BoneAnimFlagsBase.Scale; boneAnim.FlagsBase |= BoneAnimFlagsBase.Rotate; if (baseData.Rotate == new Vector4F(0, 0, 0, 1)) boneAnim.FlagsTransform |= BoneAnimFlagsTransform.RotateZero; if (baseData.Translate == new Vector3F(0, 0, 0)) boneAnim.FlagsTransform |= BoneAnimFlagsTransform.TranslateZero; if (baseData.Scale == new Vector3F(1, 1, 1)) boneAnim.FlagsTransform |= BoneAnimFlagsTransform.ScaleOne; if (IsUniform(baseData.Scale)) boneAnim.FlagsTransform |= BoneAnimFlagsTransform.ScaleUniform; if (!IsRoot(boneAnim)) boneAnim.FlagsTransform |= BoneAnimFlagsTransform.SegmentScaleCompensate; boneAnim.BeginTranslate = 6; boneAnim.BeginRotate = 3; if (FirstFrame.HasKeys) { var AnimFrame = entry.GetAnimFrame(0); if (AnimFrame.hasTx) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.TranslateX; var curve = GenerateCurve(0x10, entry); if (curve != null) boneAnim.Curves.Add(curve); } if (AnimFrame.hasTy) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.TranslateY; var curve = GenerateCurve(0x14, entry); if (curve != null) boneAnim.Curves.Add(curve); } if (AnimFrame.hasTz) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.TranslateZ; var curve = GenerateCurve(0x18, entry); if (curve != null) boneAnim.Curves.Add(curve); } if (AnimFrame.hasRx) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.RotateX; var curve = GenerateCurve(0x20, entry); if (curve != null) boneAnim.Curves.Add(curve); } if (AnimFrame.hasRy) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.RotateY; var curve = GenerateCurve(0x24, entry); if (curve != null) boneAnim.Curves.Add(curve); } if (AnimFrame.hasRz) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.RotateZ; var curve = GenerateCurve(0x28, entry); if (curve != null) boneAnim.Curves.Add(curve); } if (AnimFrame.hasSx) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.ScaleX; var curve = GenerateCurve(0x4, entry); if (curve != null) boneAnim.Curves.Add(curve); } if (AnimFrame.hasSy) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.ScaleY; var curve = GenerateCurve(0x8, entry); if (curve != null) boneAnim.Curves.Add(curve); } if (AnimFrame.hasSz) { boneAnim.FlagsCurve |= BoneAnimFlagsCurve.ScaleZ; var curve = GenerateCurve(0xC, entry); if (curve != null) boneAnim.Curves.Add(curve); } } return boneAnim; } private static bool IsRoot(BoneAnim boneAnim) { if (boneAnim.Name.Contains("root") || boneAnim.Name.Contains("center")) return true; return false; } private static bool IsUniform(Vector3F value) { return value.X == value.Y && value.X == value.Z; } private static void QuantizeCurveData(AnimCurve curve) { float MaxFrame = 0; float MaxValues = 0; List IntegerValues = new List(); for (int frame = 0; frame < curve.Frames.Length; frame++) { MaxFrame = Math.Max(MaxFrame, curve.Frames[frame]); if (curve.CurveType == AnimCurveType.Linear) { MaxValues = Math.Max(MaxValues, curve.Keys[frame, 0]); MaxValues = Math.Max(MaxValues, curve.Keys[frame, 1]); IntegerValues.Add(IsInt(curve.Keys[frame, 0])); IntegerValues.Add(IsInt(curve.Keys[frame, 1])); } else if (curve.CurveType == AnimCurveType.Cubic) { MaxValues = Math.Max(MaxValues, curve.Keys[frame, 0]); MaxValues = Math.Max(MaxValues, curve.Keys[frame, 1]); MaxValues = Math.Max(MaxValues, curve.Keys[frame, 2]); MaxValues = Math.Max(MaxValues, curve.Keys[frame, 3]); IntegerValues.Add(IsInt(curve.Keys[frame, 0])); IntegerValues.Add(IsInt(curve.Keys[frame, 1])); IntegerValues.Add(IsInt(curve.Keys[frame, 2])); IntegerValues.Add(IsInt(curve.Keys[frame, 3])); } else { MaxValues = Math.Max(MaxValues, curve.Keys[frame, 0]); IntegerValues.Add(IsInt(curve.Keys[frame, 0])); } int ConvertedInt = Convert.ToInt32(MaxValues); } if (MaxFrame < Byte.MaxValue) curve.FrameType = AnimCurveFrameType.Byte; else if (MaxFrame < Int16.MaxValue) curve.FrameType = AnimCurveFrameType.Decimal10x5; else curve.FrameType = AnimCurveFrameType.Single; if (IntegerValues.Any(x => x == false)) { curve.KeyType = AnimCurveKeyType.Single; } else { if (MaxValues < Byte.MaxValue) curve.KeyType = AnimCurveKeyType.SByte; else if (MaxFrame < Int16.MaxValue) curve.KeyType = AnimCurveKeyType.Int16; else curve.KeyType = AnimCurveKeyType.Single; } } private static bool IsInt(float value) => value == Math.Truncate(value); private static AnimCurve GenerateCurve(uint AnimOffset, CHR0EntryNode entry) { AnimCurve curve = new AnimCurve(); curve.AnimDataOffset = AnimOffset; curve.StartFrame = 0; curve.Offset = 0; curve.Scale = 1; curve.FrameType = AnimCurveFrameType.Single; curve.KeyType = AnimCurveKeyType.Single; curve.CurveType = AnimCurveType.Linear; List Frames = new List(); List Keys = new List(); CHRAnimationFrame ain, aout; for (int frame = 0; frame < entry.FrameCount; frame++) { //Max of 4 values. Cubic using 4, linear using 2, and step using 1 float[] KeyValues = new float[4]; //Set the main values to the curve based on offset for encoding later if ((ain = entry.GetAnimFrame(frame)).HasKeys) { aout = entry.GetAnimFrame(frame, true); FillKeyList(aout, frame, curve.AnimDataOffset, Frames, Keys); if (!ain.Equals(aout)) { FillKeyList(aout, frame, curve.AnimDataOffset, Frames, Keys); } } } if (Frames.Count <= 0) return null; // Console.WriteLine($"AnimOffset {AnimOffset} Keys {Keys.Count}"); //Max value in frames is our end frame curve.EndFrame = Frames.Max(); curve.Frames = Frames.ToArray(); //If a curve only has one frame we don't need to interpolate or add keys to a curve as it's constant if (curve.Frames.Length <= 1) return null; switch (curve.CurveType) { case AnimCurveType.Cubic: curve.Keys = new float[Keys.Count, 4]; for (int frame = 0; frame < Keys.Count; frame++) { float Delta = 0; if (frame < Keys.Count - 1) Delta = Keys[frame + 1] - Keys[frame]; float value = Keys[frame]; float Slope = 0; float Slope2 = 0; curve.Keys[frame, 0] = value; curve.Keys[frame, 1] = Slope; curve.Keys[frame, 2] = Slope2; curve.Keys[frame, 3] = Delta; } break; case AnimCurveType.StepInt: //Step requires no interpolation curve.Keys = new float[Keys.Count, 1]; for (int frame = 0; frame < Keys.Count; frame++) { curve.Keys[frame, 0] = Keys[frame]; } break; case AnimCurveType.Linear: curve.Keys = new float[Keys.Count, 2]; for (int frame = 0; frame < Keys.Count; frame++) { //Delta for second value used in linear curves float time = curve.Frames[frame]; float Delta = 0; if (frame < Keys.Count - 1) Delta = Keys[frame + 1] - Keys[frame]; curve.Keys[frame, 0] = Keys[frame]; curve.Keys[frame, 1] = Delta; } break; } QuantizeCurveData(curve); return curve; } private static void FillKeyList(CHRAnimationFrame AnimFrame, int frame, uint AnimOffset, List Frames, List Keys) { if (AnimFrame.hasTx && AnimOffset == 0x10) { Frames.Add(frame); Keys.Add(AnimFrame.Translation._x); } if (AnimFrame.hasTy && AnimOffset == 0x14) { Frames.Add(frame); Keys.Add(AnimFrame.Translation._y); } if (AnimFrame.hasTz && AnimOffset == 0x18) { Frames.Add(frame); Keys.Add(AnimFrame.Translation._z); } if (AnimFrame.hasRx && AnimOffset == 0x20) { Frames.Add(frame); Keys.Add(AnimFrame.Rotation._x * Deg2Rad); } if (AnimFrame.hasRy && AnimOffset == 0x24) { Frames.Add(frame); Keys.Add(AnimFrame.Rotation._y * Deg2Rad); } if (AnimFrame.hasRz && AnimOffset == 0x28) { Frames.Add(frame); Keys.Add(AnimFrame.Rotation._z * Deg2Rad); } if (AnimFrame.hasSx && AnimOffset == 0x04) { Frames.Add(frame); Keys.Add(AnimFrame.Scale._x); } if (AnimFrame.hasSy && AnimOffset == 0x08) { Frames.Add(frame); Keys.Add(AnimFrame.Scale._y); } if (AnimFrame.hasSz && AnimOffset == 0x0C) { Frames.Add(frame); Keys.Add(AnimFrame.Scale._z); } } private static ushort[] SetIndices(SkeletalAnim fska) { List indces = new List(); foreach (var boneAnim in fska.BoneAnims) indces.Add(65535); return indces.ToArray(); } private static uint CalculateBakeSize(SkeletalAnim fska) { return 0; } } }