From ec32a768db631cfc7f765edc3818b02810dcb1c1 Mon Sep 17 00:00:00 2001 From: Canite <7261074+Canite@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:20:17 -0400 Subject: [PATCH 1/7] Fixed an issue causing compilation issues in vs2022 with bracket array constructors --- .../Assets/Sources/Scripts/UI/Common/FF9UIDataTool.cs | 2 +- Assembly-CSharp/Memoria/Assets/3DModel/FbxBinary.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Assembly-CSharp/Assets/Sources/Scripts/UI/Common/FF9UIDataTool.cs b/Assembly-CSharp/Assets/Sources/Scripts/UI/Common/FF9UIDataTool.cs index 71405bd61..e98a738a6 100644 --- a/Assembly-CSharp/Assets/Sources/Scripts/UI/Common/FF9UIDataTool.cs +++ b/Assembly-CSharp/Assets/Sources/Scripts/UI/Common/FF9UIDataTool.cs @@ -66,7 +66,7 @@ public static void DisplayMultipleItems(UILabel label, Dictionary> enumerator = items; if (items.Count == 1 && items.First().Value == 2) - enumerator = new List>([new(items.First().Key, 1), new(items.First().Key, 1)]); + enumerator = new List>() { new(items.First().Key, 1), new(items.First().Key, 1) }; foreach (KeyValuePair kvp in enumerator) { if (kvp.Key == RegularItem.NoItem || kvp.Value <= 0) diff --git a/Assembly-CSharp/Memoria/Assets/3DModel/FbxBinary.cs b/Assembly-CSharp/Memoria/Assets/3DModel/FbxBinary.cs index 07e0ac7a9..86bad4251 100644 --- a/Assembly-CSharp/Memoria/Assets/3DModel/FbxBinary.cs +++ b/Assembly-CSharp/Memoria/Assets/3DModel/FbxBinary.cs @@ -77,7 +77,8 @@ static void Encrypt(byte[] a, byte[] b) const string timePath1 = "FBXHeaderExtension"; const string timePath2 = "CreationTimeStamp"; - static readonly Stack timePath = new Stack([timePath1, timePath2]); + static string[] timePaths = [ timePath1, timePath2 ]; + static readonly Stack timePath = new Stack(timePaths); // Gets a single timestamp component static int GetTimestampVar(FbxNode timestamp, string element) From 57763a90505f09ad2ad6be9ab7e534445b41a863 Mon Sep 17 00:00:00 2001 From: Canite <7261074+Canite@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:52:21 -0400 Subject: [PATCH 2/7] Added keyframe increment/decrement Added a keyframe display and controls to the UI Changed the pause animation to not reset the frame --- .../Assets/ModelViewer/ModelViewerScene.cs | 81 ++++++++++++++++++- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs index bc05e4bc8..05860e8b4 100644 --- a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs +++ b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs @@ -93,6 +93,8 @@ public static class ModelViewerScene private static GameObject backgroundGo; private static GameObject labelGo; private static UISprite background; + private static Boolean isAnimStopped; + private static float animStoppedTime; public static void Init() { @@ -530,6 +532,11 @@ public static void Update() else if (speedFactor == 0.1f) speedFactor = 1f; } + + if (toggleAnim && (Input.GetKeyDown(KeyCode.Z) || Input.GetKeyDown(KeyCode.X))) // frame advance + { + toggleAnim = false; + } } if (!downUpProcessed) // Browse anims { @@ -1322,16 +1329,41 @@ public static void Update() } } Animation animation = currentModel.GetComponent(); - if (animation != null && !animation.IsPlaying(currentAnimName) && toggleAnim) // make animation a loop by default + if (animation != null && (!animation.IsPlaying(currentAnimName) || isAnimStopped) && toggleAnim) // make animation a loop by default { + isAnimStopped = false; animation.Play(currentAnimName); if (animation[currentAnimName] != null) animation[currentAnimName].speed = speedFactor; } - else if (animation != null && animation.IsPlaying(currentAnimName) && (!toggleAnim || Input.GetKeyDown(KeyCode.S))) + else if (animation != null && !isAnimStopped && (!toggleAnim || Input.GetKeyDown(KeyCode.S))) { - animation.Stop(); + //animation.Stop(); + isAnimStopped = true; + if (animation[currentAnimName] != null) + { + animation[currentAnimName].speed = 0; + animStoppedTime = animation[currentAnimName].time; + } } + + if (animation != null && isAnimStopped) + { + if (Input.GetKeyDown(KeyCode.Z)) + { + AdvanceAnimationByFrames(animation, -1); + } + else if (Input.GetKeyDown(KeyCode.X)) + { + AdvanceAnimationByFrames(animation, 1); + } + else + { + // Pause animation + animation[currentAnimName].time = animStoppedTime; + } + } + UpdateRender(); ProcessBuiltInWeapon(); if (!LoadingWeaponConfig) @@ -1503,6 +1535,7 @@ public static void UpdateRender() String extraInfo = ""; if (partcontrolled == PartControlled.MODEL && currentModel != null) { + Animation animation = currentModel.GetComponent(); if (currentModelWrapper == null) currentModelWrapper = new GameObject("CurrentModelWrapper"); currentModel.transform.SetParent(currentModelWrapper.transform); @@ -1512,6 +1545,10 @@ public static void UpdateRender() extraInfo += $" Rot(Quat): [x]{Math.Round(currentModelWrapper.transform.localRotation.x, 2)} [y]{Math.Round(currentModelWrapper.transform.localRotation.y, 2)} [z]{Math.Round(currentModelWrapper.transform.localRotation.z, 2)} [w]{Math.Round(currentModelWrapper.transform.localRotation.w, 2)}"; extraInfo += $" Rot(Eul): {Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.x, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.y, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.z, 0)}"; extraInfo += $" Scale: {Math.Round(currentModelWrapper.transform.localScale.x, 2)}/{Math.Round(currentModelWrapper.transform.localScale.y, 2)}/{Math.Round(currentModelWrapper.transform.localScale.z, 2)}"; + + int currentKeyFrame = (int)Math.Round(animation[currentAnimName].clip.length * (animation[currentAnimName].time % 1) * animation[currentAnimName].clip.frameRate); + int maxKeyFrame = (int)Math.Round(animation[currentAnimName].clip.length * (animation[currentAnimName].length % 1) * animation[currentAnimName].clip.frameRate); + extraInfo += $" Frame: {currentKeyFrame}/{maxKeyFrame}"; extraInfoLabel.color = Color.green; //extraInfo += $" | Rot(Eul): {Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.x,0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.y, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.z, 0)}"; } @@ -1652,7 +1689,9 @@ private static Int32 GetFirstModelOfCategory(Int32 categoryNum) {"F 5", "Refresh"}, {"W", "Mod/orig textures"}, {"R", "Reset position"}, - {"⇧R", "Full Reset"} + {"⇧R", "Full Reset"}, + {"Z", "Decrement Keyframe"}, + {"X", "Increment Keyframe"}, }; private static Camera GetCamera() @@ -1994,6 +2033,12 @@ private static void ChangeAnimation(Int32 index) { anim.Play(currentAnimName); anim[currentAnimName].speed = speedFactor; + if (isAnimStopped) + { + anim[currentAnimName].time = 0; + anim[currentAnimName].speed = 0; + animStoppedTime = 0; + } } } } @@ -2726,6 +2771,34 @@ public static void ReadModelViewerConfigFile(ParamIni Parameter, out String Line Line = ""; } + public static void AdvanceAnimationByFrames(Animation animation, int frames) + { + AnimationState animationState = animation[currentAnimName]; + if (animationState) + { + AnimationClip clip = animationState.clip; + if (clip != null) + { + int currentKeyFrame = (int)Math.Round(clip.length * (animationState.time % 1) * clip.frameRate); + int maxKeyFrame = (int)Math.Round(clip.length * animationState.length * clip.frameRate); + int nextKeyFrame = ((currentKeyFrame + frames) % (maxKeyFrame + 1)); + if (nextKeyFrame < 0) + { + nextKeyFrame += maxKeyFrame + 1; + } + + float nextAnimTime = nextKeyFrame / (clip.length * clip.frameRate); + if (nextAnimTime > animationState.length) + { + nextAnimTime = animationState.length; + } + + animationState.time = nextAnimTime; + animStoppedTime = animationState.time; + } + } + } + // TODO: maybe add that kind of API somewhere else (ExtensionMethodsVector3?) private static Quaternion VectorToQuaternion(Vector4 val) { From 8d7ecddff65a51ac8dafaa91172be63ab5051ef7 Mon Sep 17 00:00:00 2001 From: Canite <7261074+Canite@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:50:01 -0400 Subject: [PATCH 3/7] Fixed some inaccuracies in the keyframe calculations --- .../Memoria/Assets/ModelViewer/ModelViewerScene.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs index 05860e8b4..833e42c6b 100644 --- a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs +++ b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs @@ -1546,8 +1546,8 @@ public static void UpdateRender() extraInfo += $" Rot(Eul): {Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.x, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.y, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.z, 0)}"; extraInfo += $" Scale: {Math.Round(currentModelWrapper.transform.localScale.x, 2)}/{Math.Round(currentModelWrapper.transform.localScale.y, 2)}/{Math.Round(currentModelWrapper.transform.localScale.z, 2)}"; - int currentKeyFrame = (int)Math.Round(animation[currentAnimName].clip.length * (animation[currentAnimName].time % 1) * animation[currentAnimName].clip.frameRate); - int maxKeyFrame = (int)Math.Round(animation[currentAnimName].clip.length * (animation[currentAnimName].length % 1) * animation[currentAnimName].clip.frameRate); + int currentKeyFrame = (int)Math.Round((animation[currentAnimName].time % 1) * animation[currentAnimName].clip.frameRate); + int maxKeyFrame = (int)Math.Round(animation[currentAnimName].clip.length * animation[currentAnimName].clip.frameRate); extraInfo += $" Frame: {currentKeyFrame}/{maxKeyFrame}"; extraInfoLabel.color = Color.green; //extraInfo += $" | Rot(Eul): {Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.x,0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.y, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.z, 0)}"; @@ -2779,15 +2779,15 @@ public static void AdvanceAnimationByFrames(Animation animation, int frames) AnimationClip clip = animationState.clip; if (clip != null) { - int currentKeyFrame = (int)Math.Round(clip.length * (animationState.time % 1) * clip.frameRate); - int maxKeyFrame = (int)Math.Round(clip.length * animationState.length * clip.frameRate); + int currentKeyFrame = (int)Math.Round((animationState.time % 1) * clip.frameRate); + int maxKeyFrame = (int)Math.Round(clip.length * clip.frameRate); int nextKeyFrame = ((currentKeyFrame + frames) % (maxKeyFrame + 1)); if (nextKeyFrame < 0) { nextKeyFrame += maxKeyFrame + 1; } - float nextAnimTime = nextKeyFrame / (clip.length * clip.frameRate); + float nextAnimTime = nextKeyFrame / clip.frameRate; if (nextAnimTime > animationState.length) { nextAnimTime = animationState.length; From 9d2516997ed0c68b022b50f5ef41d9ec2dac1f20 Mon Sep 17 00:00:00 2001 From: Canite <7261074+Canite@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:51:04 -0400 Subject: [PATCH 4/7] Added inner and outer tangent transforms in animationclipreader --- .../Assets/3DModel/AnimationClipReader.cs | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/Assembly-CSharp/Memoria/Assets/3DModel/AnimationClipReader.cs b/Assembly-CSharp/Memoria/Assets/3DModel/AnimationClipReader.cs index c40031fea..28e6b536b 100644 --- a/Assembly-CSharp/Memoria/Assets/3DModel/AnimationClipReader.cs +++ b/Assembly-CSharp/Memoria/Assets/3DModel/AnimationClipReader.cs @@ -182,6 +182,17 @@ private static AnimationClip ReadAnimationClip_JSON(String filecontent, out Anim List keys_y = new List(); List keys_z = new List(); List keys_w = new List(); + + List keys_xIT = new List(); + List keys_yIT = new List(); + List keys_zIT = new List(); + List keys_wIT = new List(); + + List keys_xOT = new List(); + List keys_yOT = new List(); + List keys_zOT = new List(); + List keys_wOT = new List(); + ta.transformType = localType; foreach (JSONNode frameNode in rotArray.Childs) { @@ -216,7 +227,48 @@ private static AnimationClip ReadAnimationClip_JSON(String filecontent, out Anim fa.pos.w = keyNode.AsFloat; keys_w.Add(new Keyframe(time, fa.pos.w)); } - // TODO: read inward/outward tangent (with keys "xInnerTangent", "xOuterTangent", etc.) + + if (frameClass.Dict.TryGetValue("xInnerTangent", out keyNode)) + { + fa.posInnerTangent.x = keyNode.AsFloat; + keys_xIT.Add(new Keyframe(time, fa.posInnerTangent.x)); + } + if (frameClass.Dict.TryGetValue("yInnerTangent", out keyNode)) + { + fa.posInnerTangent.y = keyNode.AsFloat; + keys_yIT.Add(new Keyframe(time, fa.posInnerTangent.y)); + } + if (frameClass.Dict.TryGetValue("zInnerTangent", out keyNode)) + { + fa.posInnerTangent.z = keyNode.AsFloat; + keys_zIT.Add(new Keyframe(time, fa.posInnerTangent.z)); + } + if (frameClass.Dict.TryGetValue("wInnerTangent", out keyNode)) + { + fa.posInnerTangent.w = keyNode.AsFloat; + keys_wIT.Add(new Keyframe(time, fa.posInnerTangent.w)); + } + + if (frameClass.Dict.TryGetValue("xOuterTangent", out keyNode)) + { + fa.posOuterTangent.x = keyNode.AsFloat; + keys_xOT.Add(new Keyframe(time, fa.posOuterTangent.x)); + } + if (frameClass.Dict.TryGetValue("yOuterTangent", out keyNode)) + { + fa.posOuterTangent.y = keyNode.AsFloat; + keys_yOT.Add(new Keyframe(time, fa.posOuterTangent.y)); + } + if (frameClass.Dict.TryGetValue("zOuterTangent", out keyNode)) + { + fa.posOuterTangent.z = keyNode.AsFloat; + keys_zOT.Add(new Keyframe(time, fa.posOuterTangent.z)); + } + if (frameClass.Dict.TryGetValue("wOuterTangent", out keyNode)) + { + fa.posOuterTangent.w = keyNode.AsFloat; + keys_wOT.Add(new Keyframe(time, fa.posOuterTangent.w)); + } } if (!keys_x.IsNullOrEmpty()) { @@ -238,6 +290,49 @@ private static AnimationClip ReadAnimationClip_JSON(String filecontent, out Anim animCurve = new AnimationCurve(keys_w.ToArray()); clip.SetCurve(boneName, typeof(Transform), localType + ".w", animCurve); } + + if (!keys_xIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_xIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".xInnerTangent", animCurve); + } + if (!keys_yIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_yIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".yInnerTangent", animCurve); + } + if (!keys_zIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_zIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".zInnerTangent", animCurve); + } + if (!keys_wIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_wIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".wInnerTangent", animCurve); + } + + if (!keys_xOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_xOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".xOuterTangent", animCurve); + } + if (!keys_yOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_yOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".yOuterTangent", animCurve); + } + if (!keys_zOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_zOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".zOuterTangent", animCurve); + } + if (!keys_wOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_wOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".wOuterTangent", animCurve); + } + ta.frameAnimList.AddRange(faDict.Values); ba.transformAnimList.Add(ta); } From 6de6d51201b4d88fb33442155d47ecfd7637b4ce Mon Sep 17 00:00:00 2001 From: Canite <7261074+Canite@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:28:56 -0400 Subject: [PATCH 5/7] Allow modifying bones on existing animations and saving new anim files --- .../Assets/ModelViewer/ModelViewerScene.cs | 185 ++++++++++++++++-- 1 file changed, 168 insertions(+), 17 deletions(-) diff --git a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs index 833e42c6b..5fbbb14e5 100644 --- a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs +++ b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs @@ -63,7 +63,9 @@ public static class ModelViewerScene private static CommonSPSSystem spsUtility; private static SPSEffect spsEffect; private static Single speedFactor; + private static Int32 currentPausedKeyFrame; private static String savedAnimationPath; + private static AnimationClipReader savedAnimationClip; private static Boolean isLoadingModel; private static Boolean isLoadingWeaponModel; private static Boolean isLoadingFloorModel; @@ -114,6 +116,7 @@ public static void Init() currentHiddenBonesID = new List(); speedFactor = 1f; savedAnimationPath = null; + savedAnimationClip = null; spsUtility = new CommonSPSSystem(); GameObject spsGo = new GameObject($"ModelViewer_SPS"); MeshRenderer meshRenderer = spsGo.AddComponent(); @@ -419,6 +422,10 @@ public static void Update() Boolean alt = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); Boolean altgr = Input.GetKey(KeyCode.AltGr); + Boolean rotationModified = false; + Boolean positionModified = false; + Boolean scaleModified = false; + if (Input.GetKeyDown(KeyCode.RightArrow)) { Int32 nextIndex = currentGeoIndex + 1; @@ -661,6 +668,7 @@ public static void Update() BoneSelected.localRotation *= Quaternion.Euler(0f, 0f, -1f); OffsetBonesRot[currentBoneIndex] += (BoneSelected.localRotation.eulerAngles - PreviousRot); + rotationModified = true; } else if (partcontrolled == PartControlled.FLOOR) { @@ -727,6 +735,8 @@ public static void Update() OffsetBonesPos[currentBoneIndex] += moveSpeed * Vector3.left; else OffsetBonesPos.Add(currentBoneIndex, moveSpeed * Vector3.left); + + positionModified = true; } if (Input.GetKey(KeyCode.Keypad4)) { @@ -735,6 +745,8 @@ public static void Update() OffsetBonesPos[currentBoneIndex] += moveSpeed * Vector3.right; else OffsetBonesPos.Add(currentBoneIndex, moveSpeed * Vector3.right); + + positionModified = true; } if (Input.GetKey(KeyCode.Keypad8)) { @@ -743,6 +755,8 @@ public static void Update() OffsetBonesPos[currentBoneIndex] += moveSpeed * Vector3.down; else OffsetBonesPos.Add(currentBoneIndex, moveSpeed * Vector3.down); + + positionModified = true; } if (Input.GetKey(KeyCode.Keypad2)) { @@ -751,6 +765,8 @@ public static void Update() OffsetBonesPos[currentBoneIndex] += moveSpeed * Vector3.up; else OffsetBonesPos.Add(currentBoneIndex, moveSpeed * Vector3.up); + + positionModified = true; } if (Input.GetKey(KeyCode.Keypad7) || Input.GetKey(KeyCode.Keypad9)) @@ -760,6 +776,8 @@ public static void Update() OffsetBonesPos[currentBoneIndex] += moveSpeed * Vector3.back; else OffsetBonesPos.Add(currentBoneIndex, moveSpeed * Vector3.back); + + positionModified = true; } if (Input.GetKey(KeyCode.Keypad1) || Input.GetKey(KeyCode.Keypad3)) { @@ -768,6 +786,8 @@ public static void Update() OffsetBonesPos[currentBoneIndex] += moveSpeed * Vector3.forward; else OffsetBonesPos.Add(currentBoneIndex, moveSpeed * Vector3.forward); + + positionModified = true; } } else if (partcontrolled == PartControlled.FLOOR) @@ -830,6 +850,8 @@ public static void Update() BoneSelected.localScale += Input.GetKey(KeyCode.KeypadPlus) ? new Vector3(0.01f, 0.01f, 0.01f) : -new Vector3(0.01f, 0.01f, 0.01f); OffsetBonesScale[currentBoneIndex] = BoneSelected.localScale; + + scaleModified = true; } else if (partcontrolled == PartControlled.FLOOR) { @@ -1030,6 +1052,8 @@ public static void Update() OffsetBonesPos[currentBoneIndex] -= mouseSensibility * new Vector3(mouseDelta.x, mouseDelta.y, mouseDelta.z); else OffsetBonesPos.Add(currentBoneIndex, mouseSensibility * new Vector3(mouseDelta.x, mouseDelta.y, mouseDelta.z)); + + positionModified = true; } else if (partcontrolled == PartControlled.FLOOR) { @@ -1121,6 +1145,7 @@ public static void Update() } OffsetBonesRot[currentBoneIndex] += (BoneSelected.localRotation.eulerAngles - PreviousRot); + rotationModified = true; } else if (partcontrolled == PartControlled.FLOOR) { @@ -1221,6 +1246,8 @@ public static void Update() boneselected_scaleFactor.y = boneselected_scaleFactor.z = boneselected_scaleFactor.x; BoneSelected.localScale = boneselected_scaleFactor; OffsetBonesScale[currentBoneIndex] = boneselected_scaleFactor; + + scaleModified = true; } else if (partcontrolled == PartControlled.FLOOR) { @@ -1244,7 +1271,11 @@ public static void Update() exportedAnims = animList.Select(a => a.Value).ToList(); else exportedAnims = new List() { animList[currentAnimIndex].Value }; - ExportAnimation(exportedAnims); + + if (currentAnimName == "CUSTOM_CLIP") + ExportAnimation(exportedAnims, savedAnimationClip); + else + ExportAnimation(exportedAnims, null); } else if (geoList[currentGeoIndex].Kind == MODEL_KIND_SPS && currentModel != null) { @@ -1338,16 +1369,24 @@ public static void Update() } else if (animation != null && !isAnimStopped && (!toggleAnim || Input.GetKeyDown(KeyCode.S))) { - //animation.Stop(); - isAnimStopped = true; - if (animation[currentAnimName] != null) + // Advancing through and changing keyframes requires a custom clip to be loaded + if (currentAnimName == "CUSTOM_CLIP") { - animation[currentAnimName].speed = 0; - animStoppedTime = animation[currentAnimName].time; + isAnimStopped = true; + if (animation[currentAnimName] != null && animation[currentAnimName].clip != null) + { + animation[currentAnimName].speed = 0; + animStoppedTime = animation[currentAnimName].time; + currentPausedKeyFrame = (int)Math.Round((animation[currentAnimName].time % 1) * animation[currentAnimName].clip.frameRate); + } + } + else + { + animation.Stop(); } } - if (animation != null && isAnimStopped) + if (animation != null && isAnimStopped && currentAnimName == "CUSTOM_CLIP") { if (Input.GetKeyDown(KeyCode.Z)) { @@ -1359,7 +1398,10 @@ public static void Update() } else { + UpdateCustomAnimationCurves(BoneSelected, rotationModified, positionModified, scaleModified); + // Pause animation + animation[currentAnimName].speed = 0; animation[currentAnimName].time = animStoppedTime; } } @@ -2104,29 +2146,32 @@ private static void MoveBoneConnection(GameObject connectionMesh, Vector3 parent mesh.vertices = meshVert; } - private static void ExportAnimation(List animNameList) + private static void ExportAnimation(List animNameList, AnimationClipReader clipIO) { String exportConf = "MemoriaExportAnim.txt"; if (File.Exists(exportConf)) { - if (ProcessExportAnimation(File.ReadAllText(exportConf))) + if (ProcessExportAnimation(File.ReadAllText(exportConf), clipIO)) File.Delete(exportConf); } else { - if (GenerateExportAnimationFile(exportConf, animNameList)) + if (GenerateExportAnimationFile(exportConf, animNameList, clipIO)) Process.Start(Path.GetFullPath(exportConf)); } } - private static Boolean GenerateExportAnimationFile(String filepath, List animNameList) + private static Boolean GenerateExportAnimationFile(String filepath, List animNameList, AnimationClipReader clipIO) { String[] animNameToken = animNameList[0].Split('_'); String animModelName = "GEO_" + animNameToken[1] + "_" + animNameToken[2] + "_" + animNameToken[3]; String assetPath = "Animations/" + animModelName + "/" + animNameList[0]; assetPath = AnimationFactory.GetRenameAnimationPath(assetPath); - AnimationClipReader clipIO; - AnimationClipReader.ReadAnimationClipFromDisc("StreamingAssets/Assets/Resources/" + assetPath + ".anim", out clipIO); + if (clipIO == null) + { + AnimationClipReader.ReadAnimationClipFromDisc("StreamingAssets/Assets/Resources/" + assetPath + ".anim", out clipIO); + } + if (clipIO == null) { FF9Sfx.FF9SFX_Play(102); @@ -2158,7 +2203,7 @@ private static Boolean GenerateExportAnimationFile(String filepath, List return true; } - private static Boolean ProcessExportAnimation(String config) + private static Boolean ProcessExportAnimation(String config, AnimationClipReader clipIO) { Dictionary> animPatch = new Dictionary>(); List animNameList = new List(); @@ -2241,8 +2286,11 @@ private static Boolean ProcessExportAnimation(String config) String animModelName = "GEO_" + animNameToken[1] + "_" + animNameToken[2] + "_" + animNameToken[3]; String assetPath = "Animations/" + animModelName + "/" + animNameList[i]; assetPath = AnimationFactory.GetRenameAnimationPath(assetPath); - AnimationClipReader clipIO; - AnimationClipReader.ReadAnimationClipFromDisc("StreamingAssets/Assets/Resources/" + assetPath + ".anim", out clipIO); + if (clipIO == null) + { + AnimationClipReader.ReadAnimationClipFromDisc("StreamingAssets/Assets/Resources/" + assetPath + ".anim", out clipIO); + } + if (clipIO == null) { allGood = false; @@ -2322,6 +2370,7 @@ private static Boolean ProcessExportAnimation(String config) AnimationClipReader.LoadedClips.Remove("StreamingAssets/Assets/Resources/" + assetPath + ".anim"); AnimationClipReader.LoadedClips.Remove(outputPath); savedAnimationPath = outputPath; + savedAnimationClip = clipIO; } if (!allGood) { @@ -2787,7 +2836,7 @@ public static void AdvanceAnimationByFrames(Animation animation, int frames) nextKeyFrame += maxKeyFrame + 1; } - float nextAnimTime = nextKeyFrame / clip.frameRate; + float nextAnimTime = (float)Math.Round(nextKeyFrame / clip.frameRate, 6); if (nextAnimTime > animationState.length) { nextAnimTime = animationState.length; @@ -2795,10 +2844,112 @@ public static void AdvanceAnimationByFrames(Animation animation, int frames) animationState.time = nextAnimTime; animStoppedTime = animationState.time; + currentPausedKeyFrame = nextKeyFrame; } } } + private static void UpdateCustomAnimationCurves(Transform selectedBone, Boolean rotationModified, Boolean positionModified, Boolean scaleModified) + { + // If this is a custom clip, save the modified bone + if (isAnimStopped && currentAnimName == "CUSTOM_CLIP" && savedAnimationClip != null && (rotationModified || positionModified || scaleModified)) + { + AnimationClipReader.BoneAnimation boneAnim = savedAnimationClip.boneAnimList[currentBoneIndex]; + String boneName = boneAnim.boneNameInHierarchy; + List modifiedTranforms = new List(); + if (rotationModified) + { + modifiedTranforms.Add("localRotation"); + } + if (positionModified) + { + modifiedTranforms.Add("localPosition"); + } + if (scaleModified) + { + modifiedTranforms.Add("localScale"); + } + + Animation anim = currentModel.GetComponent(); + AnimationClip clip = anim[currentAnimName].clip; + + foreach (String localType in modifiedTranforms) + { + List keys_x = new List(); + List keys_y = new List(); + List keys_z = new List(); + List keys_w = new List(); + + // TODO: + // Add some way to modify inner/outer tangents? + + foreach (AnimationClipReader.BoneAnimation.TransformAnimation ta in boneAnim.transformAnimList) + { + if (ta.transformType == localType) + { + int keyFrame = 0; + foreach (AnimationClipReader.BoneAnimation.TransformAnimation.FrameAnimation fa in ta.frameAnimList) + { + if (keyFrame == currentPausedKeyFrame) + { + switch(localType) + { + case "localRotation": + fa.pos = QuaternionToVector(selectedBone.localRotation); + break; + case "localPosition": + fa.pos = selectedBone.localPosition; + break; + case "localScale": + fa.pos = selectedBone.localScale; + break; + default: + break; + } + + // make sure the keyframe times are synced or you get jitter + animStoppedTime = fa.time; + } + + keys_x.Add(new Keyframe(fa.time, fa.pos.x)); + keys_y.Add(new Keyframe(fa.time, fa.pos.y)); + keys_z.Add(new Keyframe(fa.time, fa.pos.z)); + keys_w.Add(new Keyframe(fa.time, fa.pos.w)); + keyFrame++; + } + } + } + + AnimationCurve animCurve; + if (!keys_x.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_x.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".x", animCurve); + } + if (!keys_y.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_y.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".y", animCurve); + } + if (!keys_z.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_z.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".z", animCurve); + } + if (!keys_w.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_w.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".w", animCurve); + } + + } + + anim.RemoveClip("CUSTOM_CLIP"); + anim.AddClip(clip, "CUSTOM_CLIP"); + anim.Play("CUSTOM_CLIP"); + } + } + // TODO: maybe add that kind of API somewhere else (ExtensionMethodsVector3?) private static Quaternion VectorToQuaternion(Vector4 val) { From c3db36519839de36e47c6207b363ba7b042855d5 Mon Sep 17 00:00:00 2001 From: Canite <7261074+Canite@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:16:18 -0400 Subject: [PATCH 6/7] Implemented adding new keyframes to custom animations Extended the extra info panel a bit to fit keyframe output --- .../Assets/ModelViewer/ModelViewerScene.cs | 181 +++++++++++++++++- 1 file changed, 172 insertions(+), 9 deletions(-) diff --git a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs index 5fbbb14e5..d620743de 100644 --- a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs +++ b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs @@ -149,7 +149,7 @@ public static void Init() extraInfoPanel = new ControlPanel(PersistenSingleton.Instance.transform, ""); extraInfoLabel = extraInfoPanel.AddSimpleLabel("", NGUIText.Alignment.Center, 1); extraInfoPanel.EndInitialization(UIWidget.Pivot.BottomRight); - extraInfoPanel.BasePanel.SetRect(-50f, 0f, 1500f, 580f); + extraInfoPanel.BasePanel.SetRect(-50f, 0f, 1750f, 580f); InsertTextGUI = UnityEngine.Object.Instantiate(PersistenSingleton.Instance.NameSettingScene.NameInputField.gameObject); input = InsertTextGUI.GetComponent(); @@ -1359,6 +1359,11 @@ public static void Update() else camera.backgroundColor = Color.black; } } + if (Input.GetKeyDown(KeyCode.K) && isAnimStopped && currentAnimName == "CUSTOM_CLIP") + { + AddKeyframeToCustomAnimation(); + } + Animation animation = currentModel.GetComponent(); if (animation != null && (!animation.IsPlaying(currentAnimName) || isAnimStopped) && toggleAnim) // make animation a loop by default { @@ -1574,10 +1579,15 @@ public static void UpdateRender() controlist += $"{entry.Value} [FFFF00][{entry.Key}][FFFFFF]\r\n"; controlLabel.rawText = controlist; + Animation animation = null; + if (currentModel != null) + { + animation = currentModel.GetComponent(); + } + String extraInfo = ""; - if (partcontrolled == PartControlled.MODEL && currentModel != null) + if (partcontrolled == PartControlled.MODEL && currentModel != null && animation != null) { - Animation animation = currentModel.GetComponent(); if (currentModelWrapper == null) currentModelWrapper = new GameObject("CurrentModelWrapper"); currentModel.transform.SetParent(currentModelWrapper.transform); @@ -1587,14 +1597,11 @@ public static void UpdateRender() extraInfo += $" Rot(Quat): [x]{Math.Round(currentModelWrapper.transform.localRotation.x, 2)} [y]{Math.Round(currentModelWrapper.transform.localRotation.y, 2)} [z]{Math.Round(currentModelWrapper.transform.localRotation.z, 2)} [w]{Math.Round(currentModelWrapper.transform.localRotation.w, 2)}"; extraInfo += $" Rot(Eul): {Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.x, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.y, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.z, 0)}"; extraInfo += $" Scale: {Math.Round(currentModelWrapper.transform.localScale.x, 2)}/{Math.Round(currentModelWrapper.transform.localScale.y, 2)}/{Math.Round(currentModelWrapper.transform.localScale.z, 2)}"; - - int currentKeyFrame = (int)Math.Round((animation[currentAnimName].time % 1) * animation[currentAnimName].clip.frameRate); - int maxKeyFrame = (int)Math.Round(animation[currentAnimName].clip.length * animation[currentAnimName].clip.frameRate); - extraInfo += $" Frame: {currentKeyFrame}/{maxKeyFrame}"; + extraInfo += $" Frame: {Math.Round((animation[currentAnimName].time % 1) * animation[currentAnimName].clip.frameRate)}/{Math.Round(animation[currentAnimName].clip.length * animation[currentAnimName].clip.frameRate)}"; extraInfoLabel.color = Color.green; //extraInfo += $" | Rot(Eul): {Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.x,0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.y, 0)}/{Math.Round(currentModelWrapper.transform.localRotation.eulerAngles.z, 0)}"; } - if (partcontrolled == PartControlled.WEAPON && currentWeaponModel != null) + if (partcontrolled == PartControlled.WEAPON && currentWeaponModel != null && animation != null) { extraInfo += "[WEAPON] ¤ "; extraInfo += UseModdedTextures ? "text_mod | " : "text_orig | "; @@ -1602,9 +1609,10 @@ public static void UpdateRender() extraInfo += $" Rot(Quat): [x]{Math.Round(currentWeaponModel.transform.localRotation.x, 2)} [y]{Math.Round(currentWeaponModel.transform.localRotation.y, 2)} [z]{Math.Round(currentWeaponModel.transform.localRotation.z, 2)} [w]{Math.Round(currentWeaponModel.transform.localRotation.w, 2)}"; extraInfo += $" Rot(Eul): {Math.Round(currentWeaponModel.transform.localRotation.eulerAngles.x, 0)}/{Math.Round(currentWeaponModel.transform.localRotation.eulerAngles.y, 0)}/{Math.Round(currentWeaponModel.transform.localRotation.eulerAngles.z, 0)}"; extraInfo += $" Scale: {Math.Round(currentWeaponModel.transform.localScale.x, 2)}/{Math.Round(currentWeaponModel.transform.localScale.y, 2)}/{Math.Round(currentWeaponModel.transform.localScale.z, 2)}"; + extraInfo += $" Frame: {Math.Round((animation[currentAnimName].time % 1) * animation[currentAnimName].clip.frameRate)}/{Math.Round(animation[currentAnimName].clip.length * animation[currentAnimName].clip.frameRate)}"; extraInfoLabel.color = Color.cyan; } - else if (partcontrolled == PartControlled.BONE && currentModel != null) + else if (partcontrolled == PartControlled.BONE && currentModel != null && animation != null) { Transform BoneSelected = currentModel.transform.GetChildByName("bone" + currentBoneIndex.ToString("D3")); extraInfo += "[BONE] ¤ "; @@ -1613,6 +1621,7 @@ public static void UpdateRender() extraInfo += $" Rot(Quat): [x]{Math.Round(BoneSelected.localRotation.x, 2)} [y]{Math.Round(BoneSelected.localRotation.y, 2)} [z]{Math.Round(BoneSelected.localRotation.z, 2)} [w]{Math.Round(BoneSelected.localRotation.w, 2)}"; extraInfo += $" Rot(Eul): {Math.Round(BoneSelected.localRotation.eulerAngles.x, 0)}/{Math.Round(BoneSelected.localRotation.eulerAngles.y, 0)}/{Math.Round(BoneSelected.localRotation.eulerAngles.z, 0)}"; extraInfo += $" Scale: {Math.Round(BoneSelected.localScale.x, 2)}/{Math.Round(BoneSelected.localScale.y, 2)}/{Math.Round(BoneSelected.localScale.z, 2)}"; + extraInfo += $" Frame: {Math.Round((animation[currentAnimName].time % 1) * animation[currentAnimName].clip.frameRate)}/{Math.Round(animation[currentAnimName].clip.length * animation[currentAnimName].clip.frameRate)}"; extraInfoLabel.color = new Color(0.85865f, 0.00327f, 0.48478f, 1f); // Deep Red } else if (partcontrolled == PartControlled.FLOOR && currentFloorModel != null) @@ -1645,6 +1654,7 @@ public static void UpdateRender() // controlist += $"\r\n"; //controlLabel.text = controlist; } + infoPanel.BasePanel.transform.localPosition = new Vector3(0 + InfoPanelPosX, 0, 0); controlPanel.BasePanel.transform.localPosition = new Vector3(1000 + ControlPanelPosX, 0, 0); controlLabel.fontSize = 22; @@ -1734,6 +1744,7 @@ private static Int32 GetFirstModelOfCategory(Int32 categoryNum) {"⇧R", "Full Reset"}, {"Z", "Decrement Keyframe"}, {"X", "Increment Keyframe"}, + {"K", "Add New Keyframe"}, }; private static Camera GetCamera() @@ -2950,6 +2961,158 @@ private static void UpdateCustomAnimationCurves(Transform selectedBone, Boolean } } + private static void AddKeyframeToCustomAnimation() + { + if (isAnimStopped && currentAnimName == "CUSTOM_CLIP" && savedAnimationClip != null) + { + Animation anim = currentModel.GetComponent(); + AnimationClip clip = anim[currentAnimName].clip; + // Already at max animation length + if (clip.length >= 1.0f) + { + FF9Sfx.FF9SFX_Play(102); + return; + } + + clip.legacy = true; + clip.frameRate = 30.0f; + clip.name = "CUSTOM_CLIP"; + + foreach (AnimationClipReader.BoneAnimation boneAnim in savedAnimationClip.boneAnimList) + { + String boneName = boneAnim.boneNameInHierarchy; + + foreach (String localType in new String[] { "localRotation", "localPosition", "localScale" }) + { + List keys_x = new List(); + List keys_y = new List(); + List keys_z = new List(); + List keys_w = new List(); + + List keys_xIT = new List(); + List keys_yIT = new List(); + List keys_zIT = new List(); + List keys_wIT = new List(); + + List keys_xOT = new List(); + List keys_yOT = new List(); + List keys_zOT = new List(); + List keys_wOT = new List(); + + foreach (AnimationClipReader.BoneAnimation.TransformAnimation ta in boneAnim.transformAnimList) + { + if (ta.transformType == localType) + { + int keyFrame = 0; + AnimationClipReader.BoneAnimation.TransformAnimation.FrameAnimation keyframeAnim = ta.frameAnimList[currentPausedKeyFrame]; + AnimationClipReader.BoneAnimation.TransformAnimation.FrameAnimation duplicateKeyframeAnim = new AnimationClipReader.BoneAnimation.TransformAnimation.FrameAnimation(); + + // copy to the frame directly after current + duplicateKeyframeAnim.pos = new Vector4(keyframeAnim.pos.x, keyframeAnim.pos.y, keyframeAnim.pos.z, keyframeAnim.pos.w); + duplicateKeyframeAnim.posInnerTangent = new Vector4(keyframeAnim.posInnerTangent.x, keyframeAnim.posInnerTangent.y, keyframeAnim.posInnerTangent.z, keyframeAnim.posInnerTangent.w); + duplicateKeyframeAnim.posOuterTangent = new Vector4(keyframeAnim.posOuterTangent.x, keyframeAnim.posOuterTangent.y, keyframeAnim.posOuterTangent.z, keyframeAnim.posOuterTangent.w); + + ta.frameAnimList.Insert(currentPausedKeyFrame + 1, duplicateKeyframeAnim); + + foreach (AnimationClipReader.BoneAnimation.TransformAnimation.FrameAnimation fa in ta.frameAnimList) + { + // Recalculate frame times + fa.time = (float)(Math.Round(keyFrame / clip.frameRate, 6)); + + keys_x.Add(new Keyframe(fa.time, fa.pos.x)); + keys_y.Add(new Keyframe(fa.time, fa.pos.y)); + keys_z.Add(new Keyframe(fa.time, fa.pos.z)); + keys_w.Add(new Keyframe(fa.time, fa.pos.w)); + + keys_xIT.Add(new Keyframe(fa.time, fa.posInnerTangent.x)); + keys_yIT.Add(new Keyframe(fa.time, fa.posInnerTangent.y)); + keys_zIT.Add(new Keyframe(fa.time, fa.posInnerTangent.z)); + keys_wIT.Add(new Keyframe(fa.time, fa.posInnerTangent.w)); + + keys_xOT.Add(new Keyframe(fa.time, fa.posOuterTangent.x)); + keys_yOT.Add(new Keyframe(fa.time, fa.posOuterTangent.y)); + keys_zOT.Add(new Keyframe(fa.time, fa.posOuterTangent.z)); + keys_wOT.Add(new Keyframe(fa.time, fa.posOuterTangent.w)); + keyFrame++; + } + + animStoppedTime = duplicateKeyframeAnim.time; + } + } + + AnimationCurve animCurve; + if (!keys_x.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_x.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".x", animCurve); + } + if (!keys_y.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_y.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".y", animCurve); + } + if (!keys_z.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_z.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".z", animCurve); + } + if (!keys_w.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_w.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".w", animCurve); + } + + if (!keys_xIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_xIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".xInnerTangent", animCurve); + } + if (!keys_yIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_yIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".yInnerTangent", animCurve); + } + if (!keys_zIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_zIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".zInnerTangent", animCurve); + } + if (!keys_wIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_wIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".wInnerTangent", animCurve); + } + + if (!keys_xOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_xOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".xOuterTangent", animCurve); + } + if (!keys_yOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_yOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".yOuterTangent", animCurve); + } + if (!keys_zOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_zOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".zOuterTangent", animCurve); + } + if (!keys_wOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_wOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".wOuterTangent", animCurve); + } + } + } + + currentPausedKeyFrame++; + anim.RemoveClip("CUSTOM_CLIP"); + anim.AddClip(clip, "CUSTOM_CLIP"); + anim.Play("CUSTOM_CLIP"); + } + } + // TODO: maybe add that kind of API somewhere else (ExtensionMethodsVector3?) private static Quaternion VectorToQuaternion(Vector4 val) { From 8a865af9595d4c3dbb1a2ddd463c16daec2ee803 Mon Sep 17 00:00:00 2001 From: Canite <7261074+Canite@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:47:50 -0400 Subject: [PATCH 7/7] Implemented removing keyframes from custom animations Added a limit on adding/removing keyframes to the game min/max --- .../Assets/ModelViewer/ModelViewerScene.cs | 151 +++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) diff --git a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs index d620743de..2d242978f 100644 --- a/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs +++ b/Assembly-CSharp/Memoria/Assets/ModelViewer/ModelViewerScene.cs @@ -145,7 +145,7 @@ public static void Init() controlPanel = new ControlPanel(PersistenSingleton.Instance.transform, ""); controlLabel = controlPanel.AddSimpleLabel("", NGUIText.Alignment.Right, 11); controlPanel.EndInitialization(UIWidget.Pivot.BottomRight); - controlPanel.BasePanel.SetRect(-50f, 0f, 1000f, 580f); + controlPanel.BasePanel.SetRect(-50f, 0f, 1000f, 520f); extraInfoPanel = new ControlPanel(PersistenSingleton.Instance.transform, ""); extraInfoLabel = extraInfoPanel.AddSimpleLabel("", NGUIText.Alignment.Center, 1); extraInfoPanel.EndInitialization(UIWidget.Pivot.BottomRight); @@ -1363,6 +1363,10 @@ public static void Update() { AddKeyframeToCustomAnimation(); } + if (Input.GetKeyDown(KeyCode.D) && isAnimStopped && currentAnimName == "CUSTOM_CLIP") + { + RemoveKeyframeToCustomAnimation(); + } Animation animation = currentModel.GetComponent(); if (animation != null && (!animation.IsPlaying(currentAnimName) || isAnimStopped) && toggleAnim) // make animation a loop by default @@ -1745,6 +1749,7 @@ private static Int32 GetFirstModelOfCategory(Int32 categoryNum) {"Z", "Decrement Keyframe"}, {"X", "Increment Keyframe"}, {"K", "Add New Keyframe"}, + {"D", "Delete Current Keyframe"}, }; private static Camera GetCamera() @@ -3113,6 +3118,150 @@ private static void AddKeyframeToCustomAnimation() } } + private static void RemoveKeyframeToCustomAnimation() + { + if (isAnimStopped && currentAnimName == "CUSTOM_CLIP" && savedAnimationClip != null) + { + Animation anim = currentModel.GetComponent(); + AnimationClip currentClip = anim[currentAnimName].clip; + AnimationClip clip = new AnimationClip(); + // Already at min animation length + if (currentClip.length <= 1.0f/currentClip.frameRate) + { + FF9Sfx.FF9SFX_Play(102); + return; + } + + clip.legacy = true; + clip.frameRate = 30.0f; + clip.name = "CUSTOM_CLIP"; + + foreach (AnimationClipReader.BoneAnimation boneAnim in savedAnimationClip.boneAnimList) + { + String boneName = boneAnim.boneNameInHierarchy; + + foreach (String localType in new String[] { "localRotation", "localPosition", "localScale" }) + { + List keys_x = new List(); + List keys_y = new List(); + List keys_z = new List(); + List keys_w = new List(); + + List keys_xIT = new List(); + List keys_yIT = new List(); + List keys_zIT = new List(); + List keys_wIT = new List(); + + List keys_xOT = new List(); + List keys_yOT = new List(); + List keys_zOT = new List(); + List keys_wOT = new List(); + + foreach (AnimationClipReader.BoneAnimation.TransformAnimation ta in boneAnim.transformAnimList) + { + if (ta.transformType == localType) + { + int keyFrame = 0; + AnimationClipReader.BoneAnimation.TransformAnimation.FrameAnimation keyframeAnim = ta.frameAnimList[currentPausedKeyFrame]; + ta.frameAnimList.Remove(keyframeAnim); + + foreach (AnimationClipReader.BoneAnimation.TransformAnimation.FrameAnimation fa in ta.frameAnimList) + { + // Recalculate frame times + fa.time = (float)(Math.Round(keyFrame / clip.frameRate, 6)); + + keys_x.Add(new Keyframe(fa.time, fa.pos.x)); + keys_y.Add(new Keyframe(fa.time, fa.pos.y)); + keys_z.Add(new Keyframe(fa.time, fa.pos.z)); + keys_w.Add(new Keyframe(fa.time, fa.pos.w)); + + keys_xIT.Add(new Keyframe(fa.time, fa.posInnerTangent.x)); + keys_yIT.Add(new Keyframe(fa.time, fa.posInnerTangent.y)); + keys_zIT.Add(new Keyframe(fa.time, fa.posInnerTangent.z)); + keys_wIT.Add(new Keyframe(fa.time, fa.posInnerTangent.w)); + + keys_xOT.Add(new Keyframe(fa.time, fa.posOuterTangent.x)); + keys_yOT.Add(new Keyframe(fa.time, fa.posOuterTangent.y)); + keys_zOT.Add(new Keyframe(fa.time, fa.posOuterTangent.z)); + keys_wOT.Add(new Keyframe(fa.time, fa.posOuterTangent.w)); + keyFrame++; + } + } + } + + AnimationCurve animCurve; + if (!keys_x.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_x.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".x", animCurve); + } + if (!keys_y.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_y.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".y", animCurve); + } + if (!keys_z.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_z.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".z", animCurve); + } + if (!keys_w.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_w.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".w", animCurve); + } + + if (!keys_xIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_xIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".xInnerTangent", animCurve); + } + if (!keys_yIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_yIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".yInnerTangent", animCurve); + } + if (!keys_zIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_zIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".zInnerTangent", animCurve); + } + if (!keys_wIT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_wIT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".wInnerTangent", animCurve); + } + + if (!keys_xOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_xOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".xOuterTangent", animCurve); + } + if (!keys_yOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_yOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".yOuterTangent", animCurve); + } + if (!keys_zOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_zOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".zOuterTangent", animCurve); + } + if (!keys_wOT.IsNullOrEmpty()) + { + animCurve = new AnimationCurve(keys_wOT.ToArray()); + clip.SetCurve(boneName, typeof(Transform), localType + ".wOuterTangent", animCurve); + } + } + } + + anim.RemoveClip("CUSTOM_CLIP"); + anim.AddClip(clip, "CUSTOM_CLIP"); + anim.Play("CUSTOM_CLIP"); + } + } + + // TODO: maybe add that kind of API somewhere else (ExtensionMethodsVector3?) private static Quaternion VectorToQuaternion(Vector4 val) {