From 7a0bc796423b73e7d4a26f8a9ff9d4e538261020 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Wed, 14 Jun 2023 18:51:24 -0400 Subject: [PATCH 01/37] wip --- CustomizePlus/Data/Armature/AliasedBone.cs | 90 ++++++++++++ CustomizePlus/Data/Armature/Armature.cs | 100 +++++-------- .../Data/Armature/ArmatureManager.cs | 2 +- CustomizePlus/Data/Armature/ModelBone.cs | 79 +++++++--- CustomizePlus/Data/BoneTransform.cs | 6 +- .../Extensions/TransformExtensions.cs | 4 +- CustomizePlus/Extensions/VectorExtensions.cs | 136 ++++++++++++++---- CustomizePlus/Plugin.cs | 68 ++++----- .../UI/Windows/Debug/BoneMonitorWindow.cs | 2 +- 9 files changed, 335 insertions(+), 152 deletions(-) create mode 100644 CustomizePlus/Data/Armature/AliasedBone.cs diff --git a/CustomizePlus/Data/Armature/AliasedBone.cs b/CustomizePlus/Data/Armature/AliasedBone.cs new file mode 100644 index 0000000..f804bee --- /dev/null +++ b/CustomizePlus/Data/Armature/AliasedBone.cs @@ -0,0 +1,90 @@ +// © Customize+. +// Licensed under the MIT license. + +using CustomizePlus.Extensions; + +using FFXIVClientStructs.FFXIV.Common.Math; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.Havok; +using System.Transactions; + +namespace CustomizePlus.Data.Armature +{ + /// + /// A fake model bone that doesn't actually correspond to a bone within a skeleton, + /// but instead some other data that can be nonetheless be transformed LIKE a bone. + /// + internal unsafe class AliasedBone : ModelBone + { + private delegate hkQsTransformf TransformGetter(CharacterBase* cBase, ModelBone mb, PoseType refFrame); + private delegate void TransformSetter(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame); + + private TransformGetter _getTransform; + private TransformSetter _setTransform; + + private AliasedBone(Armature arm, string codeName, TransformGetter tg, TransformSetter ts) : base(arm, codeName, 0, 0) + { + _getTransform = tg; + _setTransform = ts; + } + + public static AliasedBone CreateRootBone(Armature arm, string codename) + { + return new AliasedBone(arm, codename, GetFakeRootTransform, SetFakeRootTransform); + } + + public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase, PoseType refFrame) + { + return _getTransform(cBase, this, refFrame); + } + + protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + { + _setTransform(cBase, transform, refFrame); + } + + public override unsafe void ApplyModelTransform(CharacterBase* cBase) + { + if (cBase != null + && CustomizedTransform.IsEdited() + && GetGameTransform(cBase, PoseType.Model) is hkQsTransformf gameTransform + && !gameTransform.Equals(Constants.NullTransform)) + { + + } + } + + + #region Stock accessor functions + + private static hkQsTransformf GetFakeRootTransform(CharacterBase* cBase, ModelBone mb, PoseType refFrame) + { + return new hkQsTransformf() + { + Translation = cBase->Skeleton->Transform.Position.ToHavokVector(), + Rotation = cBase->Skeleton->Transform.Rotation.ToHavokRotation(), + Scale = cBase->Skeleton->Transform.Scale.ToHavokVector() + }; + } + + private static void SetFakeRootTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + { + //move the camera upward to adjust for rescaling of the character's height...? + //cBase->Skeleton->PartialSkeletons[0].GetHavokPose(Constants.TruePoseIndex)->ModelPose.Data[0].Translation = + // new Vector3(transform.Scale.X, 1, 1).ToHavokVector(); + + cBase->DrawObject.Object.Position = new Vector3() + { + X = transform.Translation.X, + Y = transform.Translation.Y, + Z = transform.Translation.Z, + }; + //cBase->DrawObject.Object.Rotation = + // new Quaternion(transform.Rotation.X, transform.Rotation.Y, transform.Rotation.Z, transform.Rotation.W); + //cBase->DrawObject.Object.Scale = + // new Vector3(transform.Translation.X, transform.Translation.Y, transform.Translation.Z); + } + + #endregion + } +} diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 58f90b3..6e2624d 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -310,33 +310,40 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C if (currentPose->Skeleton->Bones[boneIndex].Name.String is string boneName && boneName != null) { - //time to build a new bone - ModelBone newBone = new(arm, boneName, pSkeleIndex, boneIndex); - - if (currentPose->Skeleton->ParentIndices[boneIndex] is short parentIndex - && parentIndex >= 0) + if (pSkeleIndex == 0 && boneIndex == 0) { - newBone.AddParent(pSkeleIndex, parentIndex); - newPartials[pSkeleIndex][parentIndex].AddChild(pSkeleIndex, boneIndex); + newPartials.Last().Add(AliasedBone.CreateRootBone(arm, boneName)); } - - foreach (ModelBone mb in newPartials.SelectMany(x => x)) + else { - if (AreTwinnedNames(boneName, mb.BoneName)) + //time to build a new bone + ModelBone newBone = new(arm, boneName, pSkeleIndex, boneIndex); + + if (currentPose->Skeleton->ParentIndices[boneIndex] is short parentIndex + && parentIndex >= 0) { - newBone.AddTwin(mb.PartialSkeletonIndex, mb.BoneIndex); - mb.AddTwin(pSkeleIndex, boneIndex); - break; + newBone.AddParent(pSkeleIndex, parentIndex); + newPartials[pSkeleIndex][parentIndex].AddChild(pSkeleIndex, boneIndex); } - } - if (arm.Profile.Bones.TryGetValue(boneName, out BoneTransform? bt) - && bt != null) - { - newBone.UpdateModel(bt); - } + foreach (ModelBone mb in newPartials.SelectMany(x => x)) + { + if (AreTwinnedNames(boneName, mb.BoneName)) + { + newBone.AddTwin(mb.PartialSkeletonIndex, mb.BoneIndex); + mb.AddTwin(pSkeleIndex, boneIndex); + break; + } + } - newPartials.Last().Add(newBone); + if (arm.Profile.Bones.TryGetValue(boneName, out BoneTransform? bt) + && bt != null) + { + newBone.UpdateModel(bt); + } + + newPartials.Last().Add(newBone); + } } else { @@ -362,47 +369,12 @@ public void UpdateBoneTransform(int partialIdx, int boneIdx, BoneTransform bt, b /// /// Iterate through this armature's model bones and apply their associated transformations - /// to all of their in-game siblings + /// to all of their in-game siblings. /// public unsafe void ApplyTransformation(GameObject obj) { CharacterBase* cBase = obj.ToCharacterBase(); - if (cBase != null) - { - foreach (ModelBone mb in GetAllBones().Where(x => x.CustomizedTransform.IsEdited())) - { - if (mb == MainRootBone) - { - //the main root bone's position information is handled by a different hook - //so there's no point in trying to update it here - //meanwhile root scaling has special rules - - if (obj.HasScalableRoot()) - { - mb.ApplyModelScale(cBase); - } - - mb.ApplyModelRotation(cBase); - } - else - { - mb.ApplyModelTransform(cBase); - } - } - } - } - - - /// - /// Iterate through the skeleton of the given character base, and apply any transformations - /// for which this armature contains corresponding model bones. This method of application - /// is safer but more computationally costly - /// - public unsafe void ApplyPiecewiseTransformation(GameObject obj) - { - CharacterBase* cBase = obj.ToCharacterBase(); - if (cBase != null) { for (int pSkeleIndex = 0; pSkeleIndex < cBase->Skeleton->PartialSkeletonCount; ++pSkeleIndex) @@ -411,22 +383,18 @@ public unsafe void ApplyPiecewiseTransformation(GameObject obj) if (currentPose != null) { + //if (SnapToReferencePose) + //{ + // currentPose->SetToReferencePose(); + //} + for (int boneIndex = 0; boneIndex < currentPose->Skeleton->Bones.Length; ++boneIndex) { if (GetBoneAt(pSkeleIndex, boneIndex) is ModelBone mb && mb != null && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String) { - if (mb == MainRootBone) - { - if (obj.HasScalableRoot()) - { - mb.ApplyModelScale(cBase); - } - - mb.ApplyModelRotation(cBase); - } - else if (GameStateHelper.GameInPosingMode()) + if (GameStateHelper.GameInPosingMode()) { mb.ApplyModelScale(cBase); } diff --git a/CustomizePlus/Data/Armature/ArmatureManager.cs b/CustomizePlus/Data/Armature/ArmatureManager.cs index 9099786..8375109 100644 --- a/CustomizePlus/Data/Armature/ArmatureManager.cs +++ b/CustomizePlus/Data/Armature/ArmatureManager.cs @@ -71,7 +71,7 @@ private unsafe void ApplyArmatureTransforms() && prof.Armature != null && prof.Armature.IsVisible) { - prof.Armature.ApplyPiecewiseTransformation(obj); + prof.Armature.ApplyTransformation(obj); } } } diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 71f7902..9b4349f 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -1,8 +1,11 @@ // © Customize+. // Licensed under the MIT license. +using CustomizePlus.Extensions; + using System; using System.Collections.Generic; +using System.Numerics; using System.Linq; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; @@ -197,7 +200,7 @@ public void UpdateModel(BoneTransform newTransform, bool mirror = false, bool pr /// /// For each OTHER bone that shares the name of this one, direct - /// it to update its transform to match the one provided + /// it to update its transform to match the one provided. /// private void UpdateClones(BoneTransform newTransform) { @@ -212,7 +215,7 @@ private void UpdateClones(BoneTransform newTransform) /// Given a character base to which this model bone's master armature (presumably) applies, /// return the game's transform value for this model's in-game sibling within the given reference frame. /// - public hkQsTransformf GetGameTransform(CharacterBase* cBase, PoseType refFrame) + public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase, PoseType refFrame) { FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; @@ -231,15 +234,10 @@ public hkQsTransformf GetGameTransform(CharacterBase* cBase, PoseType refFrame) }; } - private void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) - { - SetGameTransform(cBase, transform, PartialSkeletonIndex, BoneIndex, refFrame); - } - - private static void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, int partialIndex, int boneIndex, PoseType refFrame) + protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) { FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; - FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[partialIndex]; + FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[PartialSkeletonIndex]; hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); //hkaPose* targetPose = cBase->Skeleton->PartialSkeletons[PartialSkeletonIndex].GetHavokPose(Constants.TruePoseIndex); @@ -248,11 +246,11 @@ private static void SetGameTransform(CharacterBase* cBase, hkQsTransformf transf switch (refFrame) { case PoseType.Local: - targetPose->LocalPose.Data[boneIndex] = transform; + targetPose->LocalPose.Data[BoneIndex] = transform; return; case PoseType.Model: - targetPose->ModelPose.Data[boneIndex] = transform; + targetPose->ModelPose.Data[BoneIndex] = transform; return; default: @@ -262,20 +260,69 @@ private static void SetGameTransform(CharacterBase* cBase, hkQsTransformf transf } } + private static void SetSnappedTransform(CharacterBase* cBase, hkQsTransformf transform, int partialIndex, int boneIndex, BoneTransform bt) + { + FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; + FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[partialIndex]; + hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); + + if (targetPose == null) return; + + //targetPose->AccessUnsyncedPoseLocalSpace()->Data[boneIndex] = transform; + + //Referencing from Ktisis, Function 'SetFromLocalPose' @ Line 39 in 'Havok.cs' + + var tRef = targetPose->Skeleton->ReferencePose[boneIndex]; + + var tParent = targetPose->ModelPose[targetPose->Skeleton->ParentIndices[boneIndex]]; + var tModel = + *targetPose->AccessBoneModelSpace(boneIndex, hkaPose.PropagateOrNot.Propagate); + + + var v1 = tParent.Translation.GetAsNumericsVector(); + var v2 = tRef.Translation.GetAsNumericsVector(); + var r1 = tParent.Rotation.ToQuaternion(); + + var o1 = Vector3.Transform(v2, r1); + var o2 = v1 + o1; + var t1 = o2.ToHavokVector(); + + tModel.Translation = + ( + tParent.Translation.GetAsNumericsVector() + + Vector3.Transform(tRef.Translation.GetAsNumericsVector(), + tParent.Rotation.ToQuaternion()) + ).ToHavokVector(); + + tModel.Rotation = + (tParent.Rotation.ToQuaternion() * tRef.Rotation.ToQuaternion()).ToHavokRotation(); + tModel.Scale = tRef.Scale; + + var t = tModel; + var tNew = bt.ModifyExistingTranslationWithRotation(bt.ModifyExistingRotation(bt.ModifyExistingScale(t))); + targetPose->ModelPose.Data[boneIndex] = tNew; + } + /// /// Apply this model bone's associated transformation to its in-game sibling within /// the skeleton of the given character base. /// - public void ApplyModelTransform(CharacterBase* cBase) + public virtual void ApplyModelTransform(CharacterBase* cBase) { if (cBase != null && CustomizedTransform.IsEdited() && GetGameTransform(cBase, PoseType.Model) is hkQsTransformf gameTransform - && !gameTransform.Equals(Constants.NullTransform) - && CustomizedTransform.ModifyExistingTransform(gameTransform) is hkQsTransformf modTransform - && !modTransform.Equals(Constants.NullTransform)) + && !gameTransform.Equals(Constants.NullTransform)) { - SetGameTransform(cBase, modTransform, PoseType.Model); + if (MasterArmature.SnapToReferencePose) + { + SetSnappedTransform(cBase, gameTransform, PartialSkeletonIndex, BoneIndex, CustomizedTransform); + } + else if (CustomizedTransform.ModifyExistingTransform(gameTransform) is hkQsTransformf modTransform + && !modTransform.Equals(Constants.NullTransform)) + { + SetGameTransform(cBase, modTransform, PoseType.Model); + } } } diff --git a/CustomizePlus/Data/BoneTransform.cs b/CustomizePlus/Data/BoneTransform.cs index d26d9eb..b5bff0f 100644 --- a/CustomizePlus/Data/BoneTransform.cs +++ b/CustomizePlus/Data/BoneTransform.cs @@ -216,9 +216,9 @@ public hkQsTransformf ModifyExistingTranslationWithRotation(hkQsTransformf tr) public hkQsTransformf ModifyExistingTranslation(hkQsTransformf tr) { - tr.Translation.X += Translation.X; - tr.Translation.Y += Translation.Y; - tr.Translation.Z += Translation.Z; + tr.Translation.X += MathF.Max(Translation.X, 0.01f); + tr.Translation.Y += MathF.Max(Translation.Y, 0.01f); + tr.Translation.Z += MathF.Max(Translation.Z, 0.01f); return tr; } diff --git a/CustomizePlus/Extensions/TransformExtensions.cs b/CustomizePlus/Extensions/TransformExtensions.cs index a945f81..360f180 100644 --- a/CustomizePlus/Extensions/TransformExtensions.cs +++ b/CustomizePlus/Extensions/TransformExtensions.cs @@ -28,9 +28,9 @@ public static hkQsTransformf ToHavokTransform(this BoneTransform bt) { return new hkQsTransformf { - Translation = bt.Translation.ToHavokTranslation(), + Translation = bt.Translation.ToHavokVector(), Rotation = bt.Rotation.ToQuaternion().ToHavokRotation(), - Scale = bt.Scaling.ToHavokScaling() + Scale = bt.Scaling.ToHavokVector() }; } diff --git a/CustomizePlus/Extensions/VectorExtensions.cs b/CustomizePlus/Extensions/VectorExtensions.cs index b58f2dc..11f106f 100644 --- a/CustomizePlus/Extensions/VectorExtensions.cs +++ b/CustomizePlus/Extensions/VectorExtensions.cs @@ -13,15 +13,23 @@ internal static class VectorExtensions public static bool IsApproximately(this hkVector4f vector, Vector3 other, float errorMargin = 0.001f) { return IsApproximately(vector.X, other.X, errorMargin) - && IsApproximately(vector.Y, other.Y, errorMargin) - && IsApproximately(vector.Z, other.Z, errorMargin); + && IsApproximately(vector.Y, other.Y, errorMargin) + && IsApproximately(vector.Z, other.Z, errorMargin); } public static bool IsApproximately(this Vector3 vector, Vector3 other, float errorMargin = 0.001f) { return IsApproximately(vector.X, other.X, errorMargin) - && IsApproximately(vector.Y, other.Y, errorMargin) - && IsApproximately(vector.Z, other.Z, errorMargin); + && IsApproximately(vector.Y, other.Y, errorMargin) + && IsApproximately(vector.Z, other.Z, errorMargin); + } + + public static bool IsApproximately(this Vector4 vector, Vector4 other, float errorMargin = 0.001f) + { + return IsApproximately(vector.X, other.X, errorMargin) + && IsApproximately(vector.Y, other.Y, errorMargin) + && IsApproximately(vector.Z, other.Z, errorMargin) + && IsApproximately(vector.W, other.W, errorMargin); } private static bool IsApproximately(float a, float b, float errorMargin) @@ -30,6 +38,14 @@ private static bool IsApproximately(float a, float b, float errorMargin) return d < errorMargin; } + private static bool IsApproximately(this Quaternion quat, Quaternion other, float errorMargin = 0.001f) + { + return IsApproximately(quat.X, other.X, errorMargin) + && IsApproximately(quat.Y, other.Y, errorMargin) + && IsApproximately(quat.Z, other.Z, errorMargin) + && IsApproximately(quat.W, other.W, errorMargin); + } + public static Quaternion ToQuaternion(this Vector3 rotation) { return Quaternion.CreateFromYawPitchRoll( @@ -59,49 +75,75 @@ public static Vector3 ToEulerAngles(this Quaternion q) public static Quaternion ToQuaternion(this Vector4 rotation) { - return new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); + if (new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W) is Quaternion q + && q.IsApproximately(Quaternion.Identity)) + { + return Quaternion.Identity; + } + return q; } public static Quaternion ToQuaternion(this hkQuaternionf rotation) { - return new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); + if (new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W) is Quaternion q + && q.IsApproximately(Quaternion.Identity)) + { + return Quaternion.Identity; + } + return q; } public static Quaternion ToQuaternion(this hkVector4f rotation) { - return new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); + if (new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W) is Quaternion q + && q.IsApproximately(Quaternion.Identity)) + { + return Quaternion.Identity; + } + return q; } - public static hkQuaternionf ToHavokRotation(this Quaternion rotation) + public static hkQuaternionf ToHavokRotation(this Quaternion rot) + { + return new hkQuaternionf + { + X = rot.X, + Y = rot.Y, + Z = rot.Z, + W = rot.W + }; + } + + public static hkQuaternionf ToHavokRotation(this FFXIVClientStructs.FFXIV.Common.Math.Quaternion rot) { return new hkQuaternionf { - X = rotation.X, - Y = rotation.Y, - Z = rotation.Z, - W = rotation.W + X = rot.X, + Y = rot.Y, + Z = rot.Z, + W = rot.W }; } - public static hkVector4f ToHavokTranslation(this Vector3 translation) + public static hkVector4f ToHavokVector(this Vector3 vec) { return new hkVector4f { - X = translation.X, - Y = translation.Y, - Z = translation.Z, - W = 0.0f + X = vec.X, + Y = vec.Y, + Z = vec.Z, + W = 1.0f }; } - public static hkVector4f ToHavokScaling(this Vector3 scaling) + public static hkVector4f ToHavokVector(this FFXIVClientStructs.FFXIV.Common.Math.Vector3 vec) { return new hkVector4f { - X = scaling.X, - Y = scaling.Y, - Z = scaling.Z, + X = vec.X, + Y = vec.Y, + Z = vec.Z, W = 1.0f }; } @@ -117,24 +159,60 @@ public static hkVector4f ToHavokVector(this Vector4 vec) }; } - public static Vector3 GetAsNumericsVector(this PoseFile.Vector vec) + public static hkVector4f ToHavokVector(this FFXIVClientStructs.FFXIV.Common.Math.Vector4 vec) { - return new Vector3(vec.X, vec.Y, vec.Z); + return new hkVector4f + { + X = vec.X, + Y = vec.Y, + Z = vec.Z, + W = 1.0f + }; } - public static Vector4 GetAsNumericsVector(this hkVector4f vec) + public static Vector3 GetAsNumericsVector(this PoseFile.Vector vec) { - return new Vector4(vec.X, vec.Y, vec.Z, vec.W); + Vector3 v = new Vector3(vec.X, vec.Y, vec.Z); + + if (v.IsApproximately(Vector3.Zero)) + { + return Vector3.Zero; + } + else if (v.IsApproximately(Vector3.One)) + { + return Vector3.One; + } + return v; } - public static Vector4 GetAsNumericsVector(this Quaternion q) + public static Vector3 GetAsNumericsVector(this hkVector4f vec) { - return new Vector4(q.X, q.Y, q.Z, q.W); + Vector3 v = new Vector3(vec.X, vec.Y, vec.Z); + + if (v.IsApproximately(Vector3.Zero)) + { + return Vector3.Zero; + } + else if (v.IsApproximately(Vector3.One)) + { + return Vector3.One; + } + return v; } - public static Vector3 RemoveWTerm(this Vector4 vec) + public static Vector4 GetAsNumericsVector(this Quaternion q) { - return new Vector3(vec.X, vec.Y, vec.Z); + Vector4 v = new Vector4(q.X, q.Y, q.Z, q.W); + + if (v.IsApproximately(Vector4.Zero)) + { + return Vector4.Zero; + } + else if (v.IsApproximately(Vector4.One)) + { + return Vector4.One; + } + return v; } public static bool Equals(this hkVector4f first, hkVector4f second) diff --git a/CustomizePlus/Plugin.cs b/CustomizePlus/Plugin.cs index 8e8e703..23c58d1 100644 --- a/CustomizePlus/Plugin.cs +++ b/CustomizePlus/Plugin.cs @@ -243,16 +243,16 @@ private static IntPtr OnRender(IntPtr a1, long a2, int a3, int a4) // if this gets disposed while running we crash calling Original's getter, so get it at start var original = _renderManagerHook.Original; - try - { - var activeProfiles = ProfileManager.GetEnabledProfiles(); - ArmatureManager.RenderCharacterProfiles(activeProfiles); - } - catch (Exception e) - { - PluginLog.Error($"Error in CustomizePlus render hook {e}"); - _renderManagerHook?.Disable(); - } + //try + //{ + // var activeProfiles = ProfileManager.GetEnabledProfiles(); + // ArmatureManager.RenderCharacterProfiles(activeProfiles); + //} + //catch (Exception e) + //{ + // PluginLog.Error($"Error in CustomizePlus render hook {e}"); + // _renderManagerHook?.Disable(); + //} return original(a1, a2, a3, a4); } @@ -264,30 +264,30 @@ private unsafe static void OnGameObjectMove(IntPtr gameObjectPtr) _gameObjectMovementHook.Original(gameObjectPtr); ////If GPose and a 3rd-party posing service are active simultneously, abort - if (GameStateHelper.GameInPosingMode()) - { - return; - } - - if (DalamudServices.ObjectTable.CreateObjectReference(gameObjectPtr) is var obj - && obj != null - && ProfileManager.GetProfilesByGameObject(obj) .FirstOrDefault(x => x.Enabled) is CharacterProfile prof - && prof != null - && prof.Armature != null) - { - prof.Armature.ApplyRootTranslation(obj.ToCharacterBase()); - - //var objIndex = obj.ObjectIndex; - - //var isForbiddenFiller = objIndex == Constants.ObjectTableFillerIndex; - //var isForbiddenCutsceneNPC = Constants.IsInObjectTableCutsceneNPCRange(objIndex) - // || !ConfigurationManager.Configuration.ApplyToNPCsInCutscenes; - - //if (!isForbiddenFiller && !isForbiddenCutsceneNPC) - //{ - // ArmatureManager.RenderArmatureByObject(obj); - //} - } + //if (GameStateHelper.GameInPosingMode()) + //{ + // return; + //} + + //if (DalamudServices.ObjectTable.CreateObjectReference(gameObjectPtr) is var obj + // && obj != null + // && ProfileManager.GetProfilesByGameObject(obj) .FirstOrDefault(x => x.Enabled) is CharacterProfile prof + // && prof != null + // && prof.Armature != null) + //{ + // prof.Armature.ApplyRootTranslation(obj.ToCharacterBase()); + + // //var objIndex = obj.ObjectIndex; + + // //var isForbiddenFiller = objIndex == Constants.ObjectTableFillerIndex; + // //var isForbiddenCutsceneNPC = Constants.IsInObjectTableCutsceneNPCRange(objIndex) + // // || !ConfigurationManager.Configuration.ApplyToNPCsInCutscenes; + + // //if (!isForbiddenFiller && !isForbiddenCutsceneNPC) + // //{ + // // ArmatureManager.RenderArmatureByObject(obj); + // //} + //} } } } \ No newline at end of file diff --git a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs index a6792d6..19c6ad8 100644 --- a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs +++ b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs @@ -220,7 +220,7 @@ private void RenderTransformationInfo(ModelBone bone, CharacterBase* cBase) { var displayName = bone.ToString(); - var rowVector = deform.GetAttribute(_targetAttribute).GetAsNumericsVector(); + var rowVector = deform.GetAttribute(_targetAttribute); ImGui.PushID(bone.BoneName.GetHashCode()); From 7a1fe6fa9f0f14a5a9da875baa203d8bb78ec5e7 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 17 Jun 2023 19:22:55 -0400 Subject: [PATCH 02/37] Add categories for main-hand and off-hand weapons, distinct from equipment --- CustomizePlus/Data/BoneData.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CustomizePlus/Data/BoneData.cs b/CustomizePlus/Data/BoneData.cs index e5e7716..06494fa 100644 --- a/CustomizePlus/Data/BoneData.cs +++ b/CustomizePlus/Data/BoneData.cs @@ -32,8 +32,10 @@ public enum BoneFamily Cape, Armor, Skirt, + MainHand, + OffHand, Equipment, - Unknown + Unknown, } //TODO move the csv data to an external (compressed?) file From d103feba0559d28e3a1d8e928271d550ffbc83c3 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 17 Jun 2023 19:26:18 -0400 Subject: [PATCH 03/37] Adjust aliased root bone to operate via getter/setter of skeleton transform instead of application through render object properties --- CustomizePlus/Data/Armature/AliasedBone.cs | 127 +++++++++++++++++---- 1 file changed, 102 insertions(+), 25 deletions(-) diff --git a/CustomizePlus/Data/Armature/AliasedBone.cs b/CustomizePlus/Data/Armature/AliasedBone.cs index f804bee..c15379c 100644 --- a/CustomizePlus/Data/Armature/AliasedBone.cs +++ b/CustomizePlus/Data/Armature/AliasedBone.cs @@ -16,7 +16,7 @@ namespace CustomizePlus.Data.Armature /// internal unsafe class AliasedBone : ModelBone { - private delegate hkQsTransformf TransformGetter(CharacterBase* cBase, ModelBone mb, PoseType refFrame); + private delegate hkQsTransformf TransformGetter(CharacterBase* cBase, PoseType refFrame); private delegate void TransformSetter(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame); private TransformGetter _getTransform; @@ -30,12 +30,17 @@ private AliasedBone(Armature arm, string codeName, TransformGetter tg, Transform public static AliasedBone CreateRootBone(Armature arm, string codename) { - return new AliasedBone(arm, codename, GetFakeRootTransform, SetFakeRootTransform); + return new AliasedBone(arm, codename, GetWholeskeletonTransform, SetWholeSkeletonTransform); + } + + public static AliasedBone CreateWeaponBone(Armature arm, string codename) + { + return new AliasedBone(arm, codename, GetChildObjectTransform, SetChildObjectTransform); } public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase, PoseType refFrame) { - return _getTransform(cBase, this, refFrame); + return _getTransform(cBase, refFrame); } protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) @@ -43,21 +48,50 @@ protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransf _setTransform(cBase, transform, refFrame); } - public override unsafe void ApplyModelTransform(CharacterBase* cBase) - { - if (cBase != null - && CustomizedTransform.IsEdited() - && GetGameTransform(cBase, PoseType.Model) is hkQsTransformf gameTransform - && !gameTransform.Equals(Constants.NullTransform)) - { + //public override unsafe void ApplyModelTransform(CharacterBase* cBase) + //{ + // if (cBase != null + // && CustomizedTransform.IsEdited()) + // { + // hkQsTransformf originalTransform = new hkQsTransformf() + // { + // Translation = CustomizedTransform.Translation.ToHavokVector(), + // Rotation = CustomizedTransform.Rotation.ToQuaternion().ToHavokRotation(), + // Scale = CustomizedTransform.Scaling.ToHavokVector() + // }; - } - } + // cBase->Skeleton->PartialSkeletons[0] + // .GetHavokPose(Constants.TruePoseIndex)->ModelPose.Data[0] + // .Translation.Y *= originalTransform.Scale.Y; + + // cBase->Skeleton->Transform.Position.X += CustomizedTransform.Translation.X; + // cBase->Skeleton->Transform.Position.Y += CustomizedTransform.Translation.Y; + // cBase->Skeleton->Transform.Position.Z += CustomizedTransform.Translation.Z; + + // Quaternion newRot = cBase->DrawObject.Object.Rotation.ToHavokRotation().ToQuaternion() + // * CustomizedTransform.Rotation.ToQuaternion(); + // cBase->Skeleton->Transform.Rotation.X = newRot.X; + // cBase->Skeleton->Transform.Rotation.Y = newRot.Y; + // cBase->Skeleton->Transform.Rotation.Z = newRot.Z; + // cBase->Skeleton->Transform.Rotation.W = newRot.W; + + // cBase->Skeleton->Transform.Scale.X = CustomizedTransform.Scaling.X; + // cBase->Skeleton->Transform.Scale.Y = CustomizedTransform.Scaling.Y; + // cBase->Skeleton->Transform.Scale.Z = CustomizedTransform.Scaling.Z; + + // //i.e. check to see if the scale has been modified externally + + // //Vector3 currentScale = cBase->DrawObject.Object.Scale; + // //Vector3 expectedScale = _cachedScale?.HadamardMultiply(CustomizedTransform.Scaling) ?? Vector3.NegativeInfinity; + + + // } + //} #region Stock accessor functions - private static hkQsTransformf GetFakeRootTransform(CharacterBase* cBase, ModelBone mb, PoseType refFrame) + private static hkQsTransformf GetWholeskeletonTransform(CharacterBase* cBase, PoseType refFrame) { return new hkQsTransformf() { @@ -67,22 +101,65 @@ private static hkQsTransformf GetFakeRootTransform(CharacterBase* cBase, ModelBo }; } - private static void SetFakeRootTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + private static void SetWholeSkeletonTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + { + BoneTransform original = new(GetWholeskeletonTransform(cBase, refFrame)); + BoneTransform modified = new(transform); + BoneTransform delta = modified - original; + + BoneTransform baseTransform = new BoneTransform() + { + Translation = cBase->DrawObject.Object.Position, + Rotation = cBase->DrawObject.Object.Rotation.EulerAngles, + Scaling = cBase->DrawObject.Object.Scale + }; + + cBase->Skeleton->Transform = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() + { + Position = transform.Translation.GetAsNumericsVector().ToClientVector(), + Rotation = transform.Rotation.ToQuaternion(), + Scale = transform.Scale.GetAsNumericsVector().ToClientVector() + }; + } + + private static hkQsTransformf GetChildObjectTransform(CharacterBase* cBase, PoseType refFrame) { - //move the camera upward to adjust for rescaling of the character's height...? - //cBase->Skeleton->PartialSkeletons[0].GetHavokPose(Constants.TruePoseIndex)->ModelPose.Data[0].Translation = - // new Vector3(transform.Scale.X, 1, 1).ToHavokVector(); + Object* obj = cBase->DrawObject.Object.ChildObject; + + if (obj->GetObjectType() != ObjectType.CharacterBase) + { + return Constants.NullTransform; + } + + Weapon* wBase = (Weapon*)obj->NextSiblingObject; - cBase->DrawObject.Object.Position = new Vector3() + if (wBase == null) return Constants.NullTransform; + + return new hkQsTransformf() { - X = transform.Translation.X, - Y = transform.Translation.Y, - Z = transform.Translation.Z, + Translation = wBase->CharacterBase.Skeleton->Transform.Position.ToHavokVector(), + Rotation = wBase->CharacterBase.Skeleton->Transform.Rotation.ToHavokRotation(), + Scale = wBase->CharacterBase.Skeleton->Transform.Scale.ToHavokVector() }; - //cBase->DrawObject.Object.Rotation = - // new Quaternion(transform.Rotation.X, transform.Rotation.Y, transform.Rotation.Z, transform.Rotation.W); - //cBase->DrawObject.Object.Scale = - // new Vector3(transform.Translation.X, transform.Translation.Y, transform.Translation.Z); + } + private static void SetChildObjectTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + { + Object* obj = cBase->DrawObject.Object.ChildObject; + + if (obj->GetObjectType() != ObjectType.CharacterBase) + return; + + Weapon* wBase = (Weapon*)obj; + + if (wBase != null) + { + wBase->CharacterBase.Skeleton->Transform = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() + { + Position = transform.Translation.GetAsNumericsVector().ToClientVector(), + Rotation = transform.Rotation.ToQuaternion(), + Scale = transform.Scale.GetAsNumericsVector().ToClientVector() + }; + } } #endregion From 75b73ba374ae2c515f1b43817a743f4a309ed6e7 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 17 Jun 2023 19:31:12 -0400 Subject: [PATCH 04/37] Revert section previously commented out for bugtesting. --- CustomizePlus/Plugin.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/CustomizePlus/Plugin.cs b/CustomizePlus/Plugin.cs index 23c58d1..b053303 100644 --- a/CustomizePlus/Plugin.cs +++ b/CustomizePlus/Plugin.cs @@ -243,16 +243,16 @@ private static IntPtr OnRender(IntPtr a1, long a2, int a3, int a4) // if this gets disposed while running we crash calling Original's getter, so get it at start var original = _renderManagerHook.Original; - //try - //{ - // var activeProfiles = ProfileManager.GetEnabledProfiles(); - // ArmatureManager.RenderCharacterProfiles(activeProfiles); - //} - //catch (Exception e) - //{ - // PluginLog.Error($"Error in CustomizePlus render hook {e}"); - // _renderManagerHook?.Disable(); - //} + try + { + var activeProfiles = ProfileManager.GetEnabledProfiles(); + ArmatureManager.RenderCharacterProfiles(activeProfiles); + } + catch (Exception e) + { + PluginLog.Error($"Error in CustomizePlus render hook {e}"); + _renderManagerHook?.Disable(); + } return original(a1, a2, a3, a4); } From b577d827179c5eeba14f50c0d396c5c3a1631839 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 17 Jun 2023 19:32:21 -0400 Subject: [PATCH 05/37] Explore options for including extraneous skeletons in bone edit list. --- CustomizePlus/UI/Windows/BoneEditWindow.cs | 44 +++++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index 5b956d3..6b93c42 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -299,10 +299,35 @@ protected unsafe override void DrawContents() if (_profileInProgress != null || _targetArmature != null) { IEnumerable relevantModelBones = _settings.ShowLiveBones && _targetArmature != null - ? _targetArmature.GetAllBones().DistinctBy(x => x.BoneName).Select(x => new EditRowParams(x)) + ? _targetArmature.GetBones().Select(x => new EditRowParams(x)) : _profileInProgress.Bones.Select(x => new EditRowParams(x.Key, x.Value)); - var groupedBones = relevantModelBones.GroupBy(x => BoneData.GetBoneFamily(x.BoneCodeName)); + var groupedBones = relevantModelBones.GroupBy(x => BoneData.GetBoneFamily(x.BoneCodeName)).ToList(); + + //TODO implement the method to pull out the bone parameters if not integrated naturally + //with the rest of the bones + + //IEnumerable mhGroup = _settings.ShowLiveBones && _targetArmature != null + // ? _targetArmature.GetMHBones().Select(x => new EditRowParams(x)) + // : _profileInProgress.Bones_MH.Select(x => new EditRowParams(x.Key, x.Value)); + + //if (mhGroup.GroupBy(x => BoneData.BoneFamily.MainHand).FirstOrDefault() is var mhg + // && mhg != null + // && mhg.Any()) + //{ + // groupedBones.Add(mhg); + //} + + //IEnumerable ohGroup = _settings.ShowLiveBones && _targetArmature != null + // ? _targetArmature.GetOHBones().Select(x => new EditRowParams(x)) + // : _profileInProgress.Bones_OH.Select(x => new EditRowParams(x.Key, x.Value)); + + //if (ohGroup.GroupBy(x => BoneData.BoneFamily.OffHand).FirstOrDefault() is var ohg + // && ohg != null + // && ohg.Any()) + //{ + // groupedBones.Add(ohg); + //} foreach (var boneGroup in groupedBones.OrderBy(x => (int)x.Key)) { @@ -584,6 +609,9 @@ private void CompleteBoneEditor(EditRowParams bone) ImGui.SameLine(); flagUpdate |= RevertBoneButton(codename, ref newVector); + //TODO the sliders need to cache their value at the instant they're clicked into + //then transforms can be adjusted using the delta in relation to that cached value + //---------------------------------- ImGui.TableNextColumn(); flagUpdate |= SingleValueSlider($"##{displayName}-X", ref newVector.X); @@ -640,10 +668,10 @@ private void CompleteBoneEditor(EditRowParams bone) { bone.Basis.UpdateModel(transform, _settings.MirrorModeEnabled, _settings.ParentingEnabled); } - else - { - _profileInProgress.Bones[codename].UpdateToMatch(transform); - } + //else + //{ + // _profileInProgress.Bones[codename].UpdateToMatch(transform); + //} } } @@ -678,6 +706,10 @@ internal struct EditRowParams public BoneTransform Transform; public ModelBone? Basis = null; + public float CachedX; + public float CachedY; + public float CachedZ; + public EditRowParams(ModelBone mb) { BoneCodeName = mb.BoneName; From 1035ed9cab650996d3b0cd8e71d29b8d9e490221 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 17 Jun 2023 19:33:05 -0400 Subject: [PATCH 06/37] Add new simple conversion functions, rework euler angle conversion to use native function. --- CustomizePlus/Extensions/VectorExtensions.cs | 52 ++++++++++++++------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/CustomizePlus/Extensions/VectorExtensions.cs b/CustomizePlus/Extensions/VectorExtensions.cs index 11f106f..4139e33 100644 --- a/CustomizePlus/Extensions/VectorExtensions.cs +++ b/CustomizePlus/Extensions/VectorExtensions.cs @@ -56,21 +56,15 @@ public static Quaternion ToQuaternion(this Vector3 rotation) public static Vector3 ToEulerAngles(this Quaternion q) { - var nq = Vector4.Normalize(q.GetAsNumericsVector()); - - var rollX = MathF.Atan2( - 2 * (nq.W * nq.X + nq.Y * nq.Z), - 1 - 2 * (nq.X * nq.X + nq.Y * nq.Y)); - - var pitchY = 2 * MathF.Atan2( - MathF.Sqrt(1 + 2 * (nq.W * nq.Y - nq.X * nq.Z)), - MathF.Sqrt(1 - 2 * (nq.W * nq.Y - nq.X * nq.Z))); - - var yawZ = MathF.Atan2( - 2 * (nq.W * nq.Z + nq.X * nq.Y), - 1 - 2 * (nq.Y * nq.Y + nq.Z * nq.Z)); + FFXIVClientStructs.FFXIV.Common.Math.Quaternion newQ = new FFXIVClientStructs.FFXIV.Common.Math.Quaternion() + { + X = q.X, + Y = q.Y, + Z = q.Z, + W = q.W + }; - return new Vector3(rollX, pitchY, yawZ); + return newQ.EulerAngles; } public static Quaternion ToQuaternion(this Vector4 rotation) @@ -93,6 +87,16 @@ public static Quaternion ToQuaternion(this hkQuaternionf rotation) return q; } + public static Quaternion ToQuaternion(this FFXIVClientStructs.FFXIV.Common.Math.Quaternion rotation) + { + if (new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W) is Quaternion q + && q.IsApproximately(Quaternion.Identity)) + { + return Quaternion.Identity; + } + return q; + } + public static Quaternion ToQuaternion(this hkVector4f rotation) { if (new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W) is Quaternion q @@ -170,6 +174,16 @@ public static hkVector4f ToHavokVector(this FFXIVClientStructs.FFXIV.Common.Math }; } + public static FFXIVClientStructs.FFXIV.Common.Math.Vector3 ToClientVector(this Vector3 vec) + { + return new FFXIVClientStructs.FFXIV.Common.Math.Vector3() + { + X = vec.X, + Y = vec.Y, + Z = vec.Z + }; + } + public static Vector3 GetAsNumericsVector(this PoseFile.Vector vec) { Vector3 v = new Vector3(vec.X, vec.Y, vec.Z); @@ -222,5 +236,15 @@ public static bool Equals(this hkVector4f first, hkVector4f second) && first.Z == second.Z && first.W == second.W; } + + public static FFXIVClientStructs.FFXIV.Common.Math.Vector3 HadamardMultiply(this FFXIVClientStructs.FFXIV.Common.Math.Vector3 orig, FFXIVClientStructs.FFXIV.Common.Math.Vector3 other) + { + return new FFXIVClientStructs.FFXIV.Common.Math.Vector3() + { + X = MathF.Max(MathF.Round(orig.X * other.X, 2), 0.01f), + Y = MathF.Max(MathF.Round(orig.Y * other.Y, 2), 0.01f), + Z = MathF.Max(MathF.Round(orig.Z * other.Z, 2), 0.01f) + }; + } } } \ No newline at end of file From 75a1275f195496c2b916512c694fd94a167b8833 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 17 Jun 2023 19:34:02 -0400 Subject: [PATCH 07/37] Add copy constructor that operates off of raw hkQsTransform --- CustomizePlus/Data/BoneTransform.cs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/CustomizePlus/Data/BoneTransform.cs b/CustomizePlus/Data/BoneTransform.cs index b5bff0f..b19f96b 100644 --- a/CustomizePlus/Data/BoneTransform.cs +++ b/CustomizePlus/Data/BoneTransform.cs @@ -33,11 +33,18 @@ public BoneTransform() Scaling = Vector3.One; } - public BoneTransform(BoneTransform original) + public BoneTransform(BoneTransform original) : this() { UpdateToMatch(original); } + public BoneTransform(hkQsTransformf original) : this() + { + Translation = original.Translation.GetAsNumericsVector(); + Rotation = original.Rotation.ToQuaternion().ToEulerAngles(); + Scaling = original.Scale.GetAsNumericsVector(); + } + private Vector3 _translation; public Vector3 Translation { @@ -216,9 +223,9 @@ public hkQsTransformf ModifyExistingTranslationWithRotation(hkQsTransformf tr) public hkQsTransformf ModifyExistingTranslation(hkQsTransformf tr) { - tr.Translation.X += MathF.Max(Translation.X, 0.01f); - tr.Translation.Y += MathF.Max(Translation.Y, 0.01f); - tr.Translation.Z += MathF.Max(Translation.Z, 0.01f); + tr.Translation.X += Translation.X; + tr.Translation.Y += Translation.Y; + tr.Translation.Z += Translation.Z; return tr; } @@ -234,5 +241,15 @@ private static Vector3 ClampToDefaultLimits(Vector3 vector) return vector; } + + public static BoneTransform operator -(BoneTransform left, BoneTransform right) + { + return new BoneTransform() + { + Translation = left.Translation - right.Translation, + Rotation = left.Rotation - right.Rotation, + Scaling = left.Scaling - right.Scaling + }; + } } } \ No newline at end of file From b67d594caac03648b42245409eda1e2359777875 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 17 Jun 2023 19:35:31 -0400 Subject: [PATCH 08/37] Adjust transformation functions --- CustomizePlus/Data/Armature/ModelBone.cs | 130 ++++++++++++++--------- 1 file changed, 77 insertions(+), 53 deletions(-) diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 9b4349f..39fef1e 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -10,6 +10,8 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.Havok; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header; +using System.Reflection; //using CustomizePlus.Memory; @@ -68,6 +70,9 @@ public enum PoseType /// public BoneTransform CustomizedTransform { get; } + internal bool MainHandBone { get; set; } = false; + internal bool OffHandBone { get; set; } = false; + public ModelBone(Armature arm, string codeName, int partialIdx, int boneIdx) { MasterArmature = arm; @@ -116,11 +121,22 @@ private void UpdateTransformation(BoneTransform newTransform) //update the transform locally CustomizedTransform.UpdateToMatch(newTransform); - //these should be connected by reference already, I think? - //but I suppose it doesn't hurt...? + //the model bones should(?) be the same, by reference + //but we still need to delete them if (newTransform.IsEdited()) { - MasterArmature.Profile.Bones[BoneName] = CustomizedTransform; + //if (MainHandBone) + //{ + // MasterArmature.Profile.Bones_MH[BoneName] = new(newTransform); + //} + //else if (OffHandBone) + //{ + // MasterArmature.Profile.Bones_OH[BoneName] = new(newTransform); + //} + //else + //{ + MasterArmature.Profile.Bones[BoneName] = new(newTransform); + //} } else { @@ -184,6 +200,11 @@ private IEnumerable GetDescendants(ModelBone? first) /// to the model bone's twin (in which case it will be appropriately mirrored) and/or children. /// public void UpdateModel(BoneTransform newTransform, bool mirror = false, bool propagate = false) + { + UpdateModel(newTransform, mirror, propagate, true); + } + + private void UpdateModel(BoneTransform newTransform, bool mirror, bool propagate, bool clone) { if (mirror && TwinBone is ModelBone mb && mb != null) { @@ -194,8 +215,38 @@ public void UpdateModel(BoneTransform newTransform, bool mirror = false, bool pr mb.UpdateModel(mirroredTransform, false, propagate); } + if (propagate && this is not AliasedBone) + { + BoneTransform delta = new BoneTransform() + { + Translation = newTransform.Translation - CustomizedTransform.Translation, + Rotation = newTransform.Rotation - CustomizedTransform.Rotation, + Scaling = newTransform.Scaling - CustomizedTransform.Scaling + }; + + PropagateModelUpdate(delta); + } + UpdateTransformation(newTransform); - UpdateClones(newTransform); + + if (clone) + { + UpdateClones(newTransform); + } + } + + private void PropagateModelUpdate(BoneTransform deltaTransform) + { + foreach(ModelBone mb in ChildBones) + { + BoneTransform modTransform = new(CustomizedTransform); + modTransform.Translation += deltaTransform.Translation; + modTransform.Rotation += deltaTransform.Rotation; + modTransform.Scaling += deltaTransform.Scaling; + + mb.UpdateTransformation(modTransform); + mb.PropagateModelUpdate(deltaTransform); + } } /// @@ -204,13 +255,33 @@ public void UpdateModel(BoneTransform newTransform, bool mirror = false, bool pr /// private void UpdateClones(BoneTransform newTransform) { - foreach(ModelBone mb in MasterArmature.GetAllBones() + foreach(ModelBone mb in MasterArmature.GetBones() .Where(x => x.BoneName == this.BoneName && x != this)) { mb.UpdateTransformation(newTransform); } } + private static hkQsTransformf Subtract(hkQsTransformf termLeft, hkQsTransformf termRight) + { + return new hkQsTransformf() + { + Translation = (termLeft.Translation.GetAsNumericsVector() - termRight.Translation.GetAsNumericsVector()).ToHavokVector(), + Rotation = Quaternion.Divide(termLeft.Rotation.ToQuaternion(), termRight.Rotation.ToQuaternion()).ToHavokRotation(), + Scale = (termLeft.Scale.GetAsNumericsVector() - termRight.Scale.GetAsNumericsVector()).ToHavokVector() + }; + } + + private static hkQsTransformf Add(hkQsTransformf term1, hkQsTransformf term2) + { + return new hkQsTransformf() + { + Translation = (term1.Translation.GetAsNumericsVector() + term2.Translation.GetAsNumericsVector()).ToHavokVector(), + Rotation = Quaternion.Multiply(term1.Rotation.ToQuaternion(), term2.Rotation.ToQuaternion()).ToHavokRotation(), + Scale = (term1.Scale.GetAsNumericsVector() + term2.Scale.GetAsNumericsVector()).ToHavokVector() + }; + } + /// /// Given a character base to which this model bone's master armature (presumably) applies, /// return the game's transform value for this model's in-game sibling within the given reference frame. @@ -260,49 +331,6 @@ protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf tra } } - private static void SetSnappedTransform(CharacterBase* cBase, hkQsTransformf transform, int partialIndex, int boneIndex, BoneTransform bt) - { - FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; - FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[partialIndex]; - hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); - - if (targetPose == null) return; - - //targetPose->AccessUnsyncedPoseLocalSpace()->Data[boneIndex] = transform; - - //Referencing from Ktisis, Function 'SetFromLocalPose' @ Line 39 in 'Havok.cs' - - var tRef = targetPose->Skeleton->ReferencePose[boneIndex]; - - var tParent = targetPose->ModelPose[targetPose->Skeleton->ParentIndices[boneIndex]]; - var tModel = - *targetPose->AccessBoneModelSpace(boneIndex, hkaPose.PropagateOrNot.Propagate); - - - var v1 = tParent.Translation.GetAsNumericsVector(); - var v2 = tRef.Translation.GetAsNumericsVector(); - var r1 = tParent.Rotation.ToQuaternion(); - - var o1 = Vector3.Transform(v2, r1); - var o2 = v1 + o1; - var t1 = o2.ToHavokVector(); - - tModel.Translation = - ( - tParent.Translation.GetAsNumericsVector() - + Vector3.Transform(tRef.Translation.GetAsNumericsVector(), - tParent.Rotation.ToQuaternion()) - ).ToHavokVector(); - - tModel.Rotation = - (tParent.Rotation.ToQuaternion() * tRef.Rotation.ToQuaternion()).ToHavokRotation(); - tModel.Scale = tRef.Scale; - - var t = tModel; - var tNew = bt.ModifyExistingTranslationWithRotation(bt.ModifyExistingRotation(bt.ModifyExistingScale(t))); - targetPose->ModelPose.Data[boneIndex] = tNew; - } - /// /// Apply this model bone's associated transformation to its in-game sibling within /// the skeleton of the given character base. @@ -314,11 +342,7 @@ public virtual void ApplyModelTransform(CharacterBase* cBase) && GetGameTransform(cBase, PoseType.Model) is hkQsTransformf gameTransform && !gameTransform.Equals(Constants.NullTransform)) { - if (MasterArmature.SnapToReferencePose) - { - SetSnappedTransform(cBase, gameTransform, PartialSkeletonIndex, BoneIndex, CustomizedTransform); - } - else if (CustomizedTransform.ModifyExistingTransform(gameTransform) is hkQsTransformf modTransform + if (CustomizedTransform.ModifyExistingTransform(gameTransform) is hkQsTransformf modTransform && !modTransform.Equals(Constants.NullTransform)) { SetGameTransform(cBase, modTransform, PoseType.Model); From edfbd58578bbbb25e9bccfdda619ed0b0c69f7de Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 17 Jun 2023 19:37:41 -0400 Subject: [PATCH 09/37] Explore options for containing additional skeletons. Adjust rebuilding algorithm slightly. --- CustomizePlus/Data/Armature/Armature.cs | 29 +++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index b180b92..c89f9c5 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -4,16 +4,16 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; + using CustomizePlus.Data.Profile; -using CustomizePlus.Extensions; using CustomizePlus.Helpers; + +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; + using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.Havok; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip; namespace CustomizePlus.Data.Armature { @@ -50,6 +50,8 @@ public unsafe class Armature /// The root bone of a partial skeleton may also be a regular bone in a different partial skeleton. /// private ModelBone[][] _partialSkeletons; + private ModelBone[][] _weaponPartialsRight; + private ModelBone[][] _weaponPartialsLeft; #region Bone Accessors ------------------------------------------------------------------------------- @@ -310,15 +312,24 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C if (currentPose->Skeleton->Bones[boneIndex].Name.String is string boneName && boneName != null) { + ModelBone newBone; + if (pSkeleIndex == 0 && boneIndex == 0) { - newPartials.Last().Add(AliasedBone.CreateRootBone(arm, boneName)); + newBone = AliasedBone.CreateRootBone(arm, boneName); } + //else if (boneName == "n_buki_r") + //{ + // newBone = AliasedBone.CreateWeaponBone(arm, boneName); + //} else { - //time to build a new bone - ModelBone newBone = new(arm, boneName, pSkeleIndex, boneIndex); + newBone = new ModelBone(arm, boneName, pSkeleIndex, boneIndex); + } + //skip adding parents/children/twins if it's the root bone + if (pSkeleIndex > 0 || boneIndex > 0) + { if (currentPose->Skeleton->ParentIndices[boneIndex] is short parentIndex && parentIndex >= 0) { @@ -341,9 +352,9 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C { newBone.UpdateModel(bt); } - - newPartials.Last().Add(newBone); } + + newPartials.Last().Add(newBone); } else { From f753cb4efa29ae4b2a6d066ed9e0663d01ddd57f Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sun, 18 Jun 2023 23:34:28 -0400 Subject: [PATCH 10/37] Fix references to renamed function, adjust output of bone monitor window. --- CustomizePlus/Data/Armature/ModelBone.cs | 2 +- CustomizePlus/UI/Windows/BoneEditWindow.cs | 2 +- CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs | 13 +++++++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 39fef1e..0f24894 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -255,7 +255,7 @@ private void PropagateModelUpdate(BoneTransform deltaTransform) /// private void UpdateClones(BoneTransform newTransform) { - foreach(ModelBone mb in MasterArmature.GetBones() + foreach(ModelBone mb in MasterArmature.GetAllBones() .Where(x => x.BoneName == this.BoneName && x != this)) { mb.UpdateTransformation(newTransform); diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index 6b93c42..a60a6ca 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -299,7 +299,7 @@ protected unsafe override void DrawContents() if (_profileInProgress != null || _targetArmature != null) { IEnumerable relevantModelBones = _settings.ShowLiveBones && _targetArmature != null - ? _targetArmature.GetBones().Select(x => new EditRowParams(x)) + ? _targetArmature.GetAllBones().Select(x => new EditRowParams(x)) : _profileInProgress.Bones.Select(x => new EditRowParams(x.Key, x.Value)); var groupedBones = relevantModelBones.GroupBy(x => BoneData.GetBoneFamily(x.BoneCodeName)).ToList(); diff --git a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs index 19c6ad8..9223a65 100644 --- a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs +++ b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs @@ -53,7 +53,8 @@ public static void Show(CharacterProfile prof) protected override void DrawContents() { - if (!GameDataHelper.TryLookupCharacterBase(_targetProfile.CharacterName, out CharacterBase* targetObject)) + if (!GameDataHelper.TryLookupCharacterBase(_targetProfile.CharacterName, out CharacterBase* targetObject) + && _targetProfile.Enabled) { _targetProfile.Enabled = false; DisplayNoLinkMsg(); @@ -126,7 +127,7 @@ protected override void DrawContents() ImGui.Separator(); - if (ImGui.BeginTable("Bones", 9, + if (ImGui.BeginTable("Bones", 10, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.ScrollY, new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 56))) { @@ -146,6 +147,9 @@ protected override void DrawContents() ImGui.TableSetupColumn("Bone Name", ImGuiTableColumnFlags.NoReorder | ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Parent Bone", + ImGuiTableColumnFlags.NoReorder | ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableHeadersRow(); @@ -259,6 +263,11 @@ private void RenderTransformationInfo(ModelBone bone, CharacterBase* cBase) ImGui.TableNextColumn(); CtrlHelper.StaticLabel(BoneData.GetBoneDisplayName(bone.BoneName)); + ImGui.TableNextColumn(); + CtrlHelper.StaticLabel(BoneData.GetBoneDisplayName(bone.ParentBone?.BoneName ?? "N/A"), + CtrlHelper.TextAlignment.Left, + bone.ParentBone?.ToString() ?? "N/A"); + ImGui.PopFont(); ImGui.PopID(); From e0da3add1480a764f82ac76d8e030ca0d250d157 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Mon, 19 Jun 2023 00:37:02 -0400 Subject: [PATCH 11/37] Remove "Enabled" checkbox from bone edit window. Perform some abstraction on the bone edit window and associated functions. --- CustomizePlus/Data/Armature/Armature.cs | 14 +- .../Data/Armature/ArmatureManager.cs | 4 +- CustomizePlus/Data/Profile/ProfileManager.cs | 81 +++++--- CustomizePlus/Helpers/GameDataHelper.cs | 18 +- CustomizePlus/Helpers/GameStateHelper.cs | 10 +- CustomizePlus/UI/Windows/BoneEditWindow.cs | 188 ++++++++---------- CustomizePlus/UI/Windows/MainWindow.cs | 12 +- 7 files changed, 164 insertions(+), 163 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 1266119..cc937ed 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -188,15 +188,13 @@ private void SetReferenceSnap(bool value) /// Returns whether or not a link can be established between the armature and an in-game object. /// If unbuilt, the armature will use this opportunity to rebuild itself. /// - public unsafe bool TryLinkSkeleton(bool forceRebuild = false) + public unsafe CharacterBase* TryLinkSkeleton(bool forceRebuild = false) { try { - if (DalamudServices.ObjectTable.FirstOrDefault(Profile.AppliesTo) is GameObject obj - && obj != null) - { - CharacterBase* cBase = obj.ToCharacterBase(); - + if (GameDataHelper.TryLookupCharacterBase(Profile.CharacterName, out CharacterBase* cBase) + && cBase != null) + { if (!Built || forceRebuild) { RebuildSkeleton(cBase); @@ -205,7 +203,7 @@ public unsafe bool TryLinkSkeleton(bool forceRebuild = false) { AugmentSkeleton(cBase); } - return true; + return cBase; } } catch @@ -213,7 +211,7 @@ public unsafe bool TryLinkSkeleton(bool forceRebuild = false) PluginLog.LogError($"Error occured while attempting to link skeleton: {this}"); } - return false; + return null; } private bool NewBonesAvailable(CharacterBase* cBase) diff --git a/CustomizePlus/Data/Armature/ArmatureManager.cs b/CustomizePlus/Data/Armature/ArmatureManager.cs index 86ca942..e142d67 100644 --- a/CustomizePlus/Data/Armature/ArmatureManager.cs +++ b/CustomizePlus/Data/Armature/ArmatureManager.cs @@ -59,11 +59,11 @@ private void RefreshActiveArmatures(params CharacterProfile[] profiles) } - private void RefreshArmatureVisibility() + private unsafe void RefreshArmatureVisibility() { foreach (var arm in _armatures) { - arm.IsVisible = arm.Profile.Enabled && arm.TryLinkSkeleton(); + arm.IsVisible = arm.Profile.Enabled && arm.TryLinkSkeleton() != null; } } diff --git a/CustomizePlus/Data/Profile/ProfileManager.cs b/CustomizePlus/Data/Profile/ProfileManager.cs index 95f206d..e8c6723 100644 --- a/CustomizePlus/Data/Profile/ProfileManager.cs +++ b/CustomizePlus/Data/Profile/ProfileManager.cs @@ -124,10 +124,31 @@ public void AddAndSaveProfile(CharacterProfile prof, bool forceNew = false) } } + public void DuplicateProfile(CharacterProfile prof, string? newCharName, string? newProfName) + { + if (Profiles.Contains(prof)) + { + CharacterProfile dupe = new CharacterProfile(prof); + + if (newCharName != null) + { + dupe.CharacterName = newCharName; + } + + if (newProfName != null) + { + dupe.CharacterName = newProfName; + } + + AddAndSaveProfile(dupe); + } + } + public void DeleteProfile(CharacterProfile prof) { if (Profiles.Remove(prof)) { + Dalamud.Logging.PluginLog.LogInformation($"{prof} deleted"); ProfileReaderWriter.DeleteProfile(prof); } } @@ -162,68 +183,68 @@ public void AssertEnabledProfile(CharacterProfile activeProfile) /// Mark the given profile (if any) as currently being edited, and return /// a copy that can be safely mangled without affecting the old one. /// - public bool GetWorkingCopy(CharacterProfile prof, out CharacterProfile? copy) + public CharacterProfile? GetWorkingCopy(CharacterProfile prof) { - if (prof != null && ProfileOpenInEditor != prof) + if (prof != null && ProfileOpenInEditor == null) { Dalamud.Logging.PluginLog.LogInformation($"Creating new copy of {prof} for editing..."); - copy = new CharacterProfile(prof); + ProfileOpenInEditor = new CharacterProfile(prof); - PruneIdempotentTransforms(copy); - ProfileOpenInEditor = copy; - return true; + PruneIdempotentTransforms(ProfileOpenInEditor); + return ProfileOpenInEditor; } - copy = null; - return false; + return null; } - public void SaveWorkingCopy(CharacterProfile prof, bool editingComplete = false) + public void SaveWorkingCopy(bool editingComplete = false) { - if (ProfileOpenInEditor == prof) + if (ProfileOpenInEditor != null) { - Dalamud.Logging.PluginLog.LogInformation($"Saving changes to {prof} to manager..."); + Dalamud.Logging.PluginLog.LogInformation($"Saving changes to {ProfileOpenInEditor} to manager..."); - AddAndSaveProfile(prof); + AddAndSaveProfile(ProfileOpenInEditor); if (editingComplete) { - StopEditing(prof); + StopEditing(); } //Send OnProfileUpdate if this is profile of the current player and it's enabled - if (prof.CharacterName == GameDataHelper.GetPlayerName() && prof.Enabled) + if (ProfileOpenInEditor.CharacterName == GameDataHelper.GetPlayerName() && ProfileOpenInEditor.Enabled) Plugin.IPCManager.OnLocalPlayerProfileUpdate(); } } - public void RevertWorkingCopy(CharacterProfile prof) + public void RevertWorkingCopy() { - var original = GetProfileByUniqueId(prof.UniqueId); - Dalamud.Logging.PluginLog.LogInformation($"Reverting {prof} to its original state..."); - - if (original != null - && Profiles.Contains(prof) - && ProfileOpenInEditor == prof) + if (ProfileOpenInEditor != null) { - foreach (var kvp in prof.Bones) + var original = GetProfileByUniqueId(ProfileOpenInEditor.UniqueId); + + if (original != null) { - if (original.Bones.TryGetValue(kvp.Key, out var bt) && bt != null) - { - prof.Bones[kvp.Key].UpdateToMatch(bt); - } - else + Dalamud.Logging.PluginLog.LogInformation($"Reverting {ProfileOpenInEditor} to its original state..."); + + foreach (var kvp in ProfileOpenInEditor.Bones) { - prof.Bones.Remove(kvp.Key); + if (original.Bones.TryGetValue(kvp.Key, out var bt) && bt != null) + { + ProfileOpenInEditor.Bones[kvp.Key].UpdateToMatch(bt); + } + else + { + ProfileOpenInEditor.Bones.Remove(kvp.Key); + } } } } } - public void StopEditing(CharacterProfile prof) + public void StopEditing() { - Dalamud.Logging.PluginLog.LogInformation($"{prof} deleted"); + Dalamud.Logging.PluginLog.LogInformation($"{ProfileOpenInEditor} deleted"); ProfileOpenInEditor = null; } diff --git a/CustomizePlus/Helpers/GameDataHelper.cs b/CustomizePlus/Helpers/GameDataHelper.cs index 1e54c81..4d0487e 100644 --- a/CustomizePlus/Helpers/GameDataHelper.cs +++ b/CustomizePlus/Helpers/GameDataHelper.cs @@ -53,8 +53,8 @@ public static unsafe bool TryLookupCharacterBase(string name, out CharacterBase* return true; } else if (FindModelByName(name) is DalamudObject obj - && obj.Address is IntPtr objPtr - && objPtr != IntPtr.Zero) + && obj.Address is nint objPtr + && objPtr != nint.Zero) { var clientObj = (FFXIVClientObject*)objPtr; @@ -187,7 +187,7 @@ public unsafe static string GetObjectName(DalamudObject obj) // // Check if in pvp intro sequence, which uses 240-244 for the 5 players, and only affect the first if so // // TODO: Ensure player side only. First group, where one of the node textures is blue. Alternately, look for hidden party list UI and get names from there. - // if (DalamudServices.GameGui.GetAddonByName("PvPMKSIntroduction", 1) == IntPtr.Zero) + // if (DalamudServices.GameGui.GetAddonByName("PvPMKSIntroduction", 1) == nint.Zero) // { // actualName = obj->ObjectIndex switch // { @@ -272,8 +272,8 @@ public unsafe static string GetObjectName(DalamudObject obj) var customize2 = ((FFXIVClientCharacter*)player.Address)->CustomizeData; for (var i = 0; i < 26; i++) { - var data1 = Marshal.ReadByte((IntPtr)customize1, i); - var data2 = Marshal.ReadByte((IntPtr)customize2, i); + var data1 = Marshal.ReadByte((nint)customize1, i); + var data2 = Marshal.ReadByte((nint)customize2, i); if (data1 != data2) { customizeEqual = false; @@ -287,7 +287,7 @@ public unsafe static string GetObjectName(DalamudObject obj) public static unsafe string? GetInspectName() { var addon = DalamudServices.GameGui.GetAddonByName("CharacterInspect"); - if (addon == IntPtr.Zero) + if (addon == nint.Zero) { return null; } @@ -332,7 +332,7 @@ public unsafe static string GetObjectName(DalamudObject obj) public static string? GetGlamourName() { var addon = DalamudServices.GameGui.GetAddonByName("MiragePrismMiragePlate"); - return addon == IntPtr.Zero ? null : GetPlayerName(); + return addon == nint.Zero ? null : GetPlayerName(); } public static string? GetPlayerName() @@ -348,8 +348,8 @@ public unsafe static string GetObjectName(DalamudObject obj) //and then make sure that the target in question is actually something with a skeleton if (target != null - && target.Address is IntPtr tgtPtr - && tgtPtr != IntPtr.Zero) + && target.Address is nint tgtPtr + && tgtPtr != nint.Zero) { var clientObj = (FFXIVClientObject*)tgtPtr; if (clientObj != null) diff --git a/CustomizePlus/Helpers/GameStateHelper.cs b/CustomizePlus/Helpers/GameStateHelper.cs index 1873170..adfcf8b 100644 --- a/CustomizePlus/Helpers/GameStateHelper.cs +++ b/CustomizePlus/Helpers/GameStateHelper.cs @@ -8,11 +8,11 @@ namespace CustomizePlus.Helpers { internal static class GameStateHelper { - //public static bool GameInPosingMode() - //{ - // return Services.GPoseService.Instance.GPoseState == Services.GPoseState.Inside - // || Services.PosingModeDetectService.Instance.IsInPosingMode; - //} + public static bool GameInPosingMode() + { + return Services.GPoseService.Instance.GPoseState == Services.GPoseState.Inside + || Services.PosingModeDetectService.Instance.IsInPosingMode; + } public static bool GameInPosingModeWithFrozenRotation() { diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index 66e96bc..43d93d7 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -23,23 +23,14 @@ namespace CustomizePlus.UI.Windows public class BoneEditWindow : WindowBase { private bool _dirty; - private string? _originalCharName; - private string? _originalProfName; private int _precision = 3; - private Armature _targetArmature => _profileInProgress.Armature; - - /// - /// The character profile being edited. - /// - private CharacterProfile _profileInProgress = null!; - /// /// User-selected settings for this instance of the bone edit window. /// private EditorSessionSettings _settings; /// - protected override string Title => $"Edit Profile: {_profileInProgress.ProfileName}"; + protected override string Title => $"Edit Profile: {_settings.ProfileInProgress.ProfileName}"; /// protected override bool SingleInstance => true; @@ -62,32 +53,21 @@ public static void Show(CharacterProfile prof) { var editWnd = Plugin.InterfaceManager.Show(); - editWnd._profileInProgress = prof; - editWnd._originalCharName = prof.CharacterName; - editWnd._originalProfName = prof.ProfileName; - - //By having the armature manager to do its checks on this profile, - // we force it to generate and track a new armature for it - Plugin.ArmatureManager.ConstructArmatureForProfile(prof); - - editWnd._settings = new EditorSessionSettings(prof.Armature); - - //editWnd.ConfirmSkeletonConnection(); + editWnd._settings = new EditorSessionSettings(prof); } /// protected unsafe override void DrawContents() { - CharacterBase* targetObject = null; - if (_profileInProgress.Enabled - && !GameDataHelper.TryLookupCharacterBase(_profileInProgress.CharacterName, out targetObject)) + CharacterBase* targetObject = _settings.SkeletonInProgress.TryLinkSkeleton(); + + if (targetObject == null && _settings.ShowLiveBones) { - _profileInProgress.Enabled = false; + _settings.ToggleLiveBones(false); DisplayNoLinkMsg(); } - if (_profileInProgress.CharacterName != _originalCharName - || _profileInProgress.ProfileName != _originalProfName) + if (_settings.ProfileRenamed()) { _dirty = true; } @@ -103,15 +83,15 @@ protected unsafe override void DrawContents() CtrlHelper.StaticLabel("Character Name", CtrlHelper.TextAlignment.Center); CtrlHelper.TextPropertyBox("##Character Name", - () => _profileInProgress.CharacterName, - (s) => _profileInProgress.CharacterName = s); + () => _settings.ProfileInProgress.CharacterName, + (s) => _settings.ProfileInProgress.CharacterName = s); ImGui.TableNextColumn(); CtrlHelper.StaticLabel("Profile Name", CtrlHelper.TextAlignment.Center); CtrlHelper.TextPropertyBox("##Profile Name", - () => _profileInProgress.ProfileName, - (s) => _profileInProgress.ProfileName = s); + () => _settings.ProfileInProgress.ProfileName, + (s) => _settings.ProfileInProgress.ProfileName = s); ImGui.TableNextColumn(); @@ -128,7 +108,7 @@ protected unsafe override void DrawContents() ImGui.Separator(); - int numColumns = Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled ? 5 : 3; + int numColumns = Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled ? 3 : 2; if (ImGui.BeginTable("Checkboxes", numColumns, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoClip)) { @@ -143,39 +123,30 @@ protected unsafe override void DrawContents() ImGui.TableNextRow(); ImGui.TableSetColumnIndex(0); - var tempEnabled = _profileInProgress.Enabled; - if (CtrlHelper.Checkbox("Enable Preview", ref tempEnabled)) - { - _profileInProgress.Enabled = tempEnabled; - ConfirmSkeletonConnection(); - } - CtrlHelper.AddHoverText($"Hook the editor into the game to edit and preview live bone data"); - - ImGui.TableNextColumn(); - - if (!_profileInProgress.Enabled) ImGui.BeginDisabled(); - if (CtrlHelper.Checkbox("Show Live Bones", ref _settings.ShowLiveBones)) { + _settings.ToggleLiveBones(_settings.ShowLiveBones); ConfirmSkeletonConnection(); } CtrlHelper.AddHoverText($"If selected, present for editing all bones found in the game data,\nelse show only bones for which the profile already contains edits."); - if (Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled) - { - ImGui.TableNextColumn(); - - var tempRefSnap = _targetArmature?.SnapToReferencePose ?? false; - if (_targetArmature != null && CtrlHelper.Checkbox("A-Pose", ref tempRefSnap)) - { - ConfirmSkeletonConnection(); - _targetArmature.SnapToReferencePose = tempRefSnap; - } - CtrlHelper.AddHoverText($"D: Force character into their default reference pose"); - } + //if (Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled) + //{ + // ImGui.TableNextColumn(); + + // var tempRefSnap = _targetArmature?.SnapToReferencePose ?? false; + // if (_targetArmature != null && CtrlHelper.Checkbox("A-Pose", ref tempRefSnap)) + // { + // ConfirmSkeletonConnection(); + // _targetArmature.SnapToReferencePose = tempRefSnap; + // } + // CtrlHelper.AddHoverText($"D: Force character into their default reference pose"); + //} ImGui.TableNextColumn(); + if (!_settings.ShowLiveBones) ImGui.BeginDisabled(); + if (CtrlHelper.Checkbox("Mirror Mode", ref _settings.MirrorModeEnabled)) { ConfirmSkeletonConnection(); @@ -193,7 +164,7 @@ protected unsafe override void DrawContents() CtrlHelper.AddHoverText($"D: Changes will propagate \"outward\" from edited bones"); } - if (!_profileInProgress.Enabled) ImGui.EndDisabled(); + if (!_settings.ShowLiveBones) ImGui.EndDisabled(); ImGui.EndTable(); } @@ -208,23 +179,21 @@ protected unsafe override void DrawContents() ImGui.TableNextRow(); ImGui.TableSetColumnIndex(0); - if (GameStateHelper.GameInPosingModeWithFrozenRotation()) ImGui.BeginDisabled(); + if (GameStateHelper.GameInPosingMode()) ImGui.BeginDisabled(); if (ImGui.RadioButton("Position", _settings.EditingAttribute == BoneAttribute.Position)) { _settings.EditingAttribute = BoneAttribute.Position; } CtrlHelper.AddHoverText($"May have unintended effects. Edit at your own risk!"); - if (GameStateHelper.GameInPosingModeWithFrozenRotation()) ImGui.EndDisabled(); ImGui.SameLine(); - if (GameStateHelper.GameInPosingModeWithFrozenPosition()) ImGui.BeginDisabled(); if (ImGui.RadioButton("Rotation", _settings.EditingAttribute == BoneAttribute.Rotation)) { _settings.EditingAttribute = BoneAttribute.Rotation; } CtrlHelper.AddHoverText($"May have unintended effects. Edit at your own risk!"); - if (GameStateHelper.GameInPosingModeWithFrozenPosition()) ImGui.EndDisabled(); + if (GameStateHelper.GameInPosingMode()) ImGui.EndDisabled(); ImGui.SameLine(); if (ImGui.RadioButton("Scale", _settings.EditingAttribute == BoneAttribute.Scale)) @@ -235,13 +204,13 @@ protected unsafe override void DrawContents() ImGui.TableNextColumn(); ImGui.TableNextColumn(); - if (!_profileInProgress.Enabled || targetObject == null) ImGui.BeginDisabled(); + if (!_settings.ShowLiveBones || targetObject == null) ImGui.BeginDisabled(); if (ImGui.Button("Reload Bone Data")) { - _targetArmature.RebuildSkeleton(targetObject); + _settings.SkeletonInProgress.RebuildSkeleton(targetObject); } CtrlHelper.AddHoverText("Refresh the skeleton data obtained from in-game"); - if (!_profileInProgress.Enabled || targetObject == null) ImGui.EndDisabled(); + if (!_settings.ShowLiveBones || targetObject == null) ImGui.EndDisabled(); ImGui.EndTable(); } @@ -299,11 +268,11 @@ protected unsafe override void DrawContents() ImGui.TableHeadersRow(); - if (_profileInProgress != null || _targetArmature != null) + if (_settings.SkeletonInProgress != null || _settings.ProfileInProgress != null) { - IEnumerable relevantModelBones = _settings.ShowLiveBones && _targetArmature != null - ? _targetArmature.GetAllBones().DistinctBy(x => x.BoneName).Select(x => new EditRowParams(x)) - : _profileInProgress.Bones.Select(x => new EditRowParams(x.Key, x.Value)); + IEnumerable relevantModelBones = _settings.ShowLiveBones && _settings.SkeletonInProgress != null + ? _settings.SkeletonInProgress.GetAllBones().DistinctBy(x => x.BoneName).Select(x => new EditRowParams(x)) + : _settings.ProfileInProgress.Bones.Select(x => new EditRowParams(x.Key, x.Value)); var groupedBones = relevantModelBones.GroupBy(x => BoneData.GetBoneFamily(x.BoneCodeName)); @@ -372,7 +341,7 @@ protected unsafe override void DrawContents() { if (_dirty) { - Plugin.ProfileManager.SaveWorkingCopy(_profileInProgress, false); + Plugin.ProfileManager.SaveWorkingCopy(false); _dirty = false; } } @@ -384,7 +353,7 @@ protected unsafe override void DrawContents() { if (_dirty) { - Plugin.ProfileManager.SaveWorkingCopy(_profileInProgress, true); + Plugin.ProfileManager.SaveWorkingCopy(true); _dirty = false; } @@ -402,7 +371,7 @@ protected unsafe override void DrawContents() ConfirmationDialog.Show("Revert all unsaved work?", () => { - Plugin.ProfileManager.RevertWorkingCopy(_profileInProgress); + Plugin.ProfileManager.RevertWorkingCopy(); _dirty = false; }); } @@ -418,16 +387,14 @@ protected unsafe override void DrawContents() ConfirmationDialog.Show("Close editor and abandon all unsaved work?", () => { - Plugin.ProfileManager.RevertWorkingCopy(_profileInProgress); - Plugin.ProfileManager.StopEditing(_profileInProgress); - _dirty = false; + Plugin.ProfileManager.StopEditing(); Close(); }); } else { //convenient data handling means we just drop it - Plugin.ProfileManager.StopEditing(_profileInProgress); + Plugin.ProfileManager.StopEditing(); Close(); } } @@ -445,27 +412,22 @@ protected unsafe override void DrawContents() /// public unsafe void ConfirmSkeletonConnection() { - if (_targetArmature == null || !_targetArmature.TryLinkSkeleton()) + if (_settings.SkeletonInProgress == null || _settings.SkeletonInProgress.TryLinkSkeleton() == null) { - _profileInProgress.Enabled = false; - - _settings.ShowLiveBones = false; - _settings.MirrorModeEnabled = false; - _settings.ParentingEnabled = false; - DisplayNoLinkMsg(); - } - else if (!_profileInProgress.Enabled) - { - _settings.ShowLiveBones = false; - _settings.MirrorModeEnabled = false; - _settings.ParentingEnabled = false; + if (_settings.ShowLiveBones) + { + _settings.ToggleLiveBones(false); + _settings.MirrorModeEnabled = false; + _settings.ParentingEnabled = false; + DisplayNoLinkMsg(); + } } } public void DisplayNoLinkMsg() { var msg = - $"The editor can't find {_profileInProgress.CharacterName} or their bone data in the game's memory.\nCertain editing features will be unavailable."; + $"The editor can't find {_settings.ProfileInProgress.CharacterName} or their bone data in the game's memory.\nAs a result, certain editing features will be unavailable."; MessageDialog.Show(msg); } @@ -499,21 +461,26 @@ private bool RevertBoneButton(string codename, ref Vector3 value) { //if the backup scale doesn't contain bone values to revert TO, then just reset it - value = Plugin.ProfileManager.Profiles.TryGetValue(_profileInProgress, out var oldProf) + if (Plugin.ProfileManager.GetProfileByUniqueId(_settings.ProfileInProgress.UniqueId) is CharacterProfile oldProf && oldProf != null && oldProf.Bones.TryGetValue(codename, out var bec) - && bec != null - ? _settings.EditingAttribute switch + && bec != null) + { + value = _settings.EditingAttribute switch { BoneAttribute.Position => bec.Translation, BoneAttribute.Rotation => bec.Rotation, _ => bec.Scaling - } - : _settings.EditingAttribute switch + }; + } + else + { + value = _settings.EditingAttribute switch { BoneAttribute.Scale => Vector3.One, _ => Vector3.Zero }; + } } return output; @@ -639,13 +606,13 @@ private void CompleteBoneEditor(EditRowParams bone) //as the bone information allows us to propagate them to siblings and children //otherwise access them through the profile directly - if (_profileInProgress.Enabled && _settings.ShowLiveBones) + if (_settings.ShowLiveBones) { bone.Basis.UpdateModel(transform, _settings.MirrorModeEnabled, _settings.ParentingEnabled); } else { - _profileInProgress.Bones[codename].UpdateToMatch(transform); + _settings.ProfileInProgress.Bones[codename].UpdateToMatch(transform); } } } @@ -655,6 +622,12 @@ private void CompleteBoneEditor(EditRowParams bone) public struct EditorSessionSettings { + public readonly CharacterProfile ProfileInProgress; + private string? _originalCharName; + private string? _originalProfName; + + public Armature SkeletonInProgress => ProfileInProgress.Armature; + public bool ShowLiveBones = false; public bool MirrorModeEnabled = false; public bool ParentingEnabled = false; @@ -662,10 +635,25 @@ public struct EditorSessionSettings public Dictionary GroupExpandedState = new(); - public EditorSessionSettings(Armature armRef) + public void ToggleLiveBones(bool setTo) { - //EditStack = new FrameStackManager(armRef); - ShowLiveBones = armRef.Profile.Enabled; + ShowLiveBones = setTo; + ProfileInProgress.Enabled = setTo; + } + + public bool ProfileRenamed() => _originalCharName != ProfileInProgress.CharacterName + || _originalProfName != ProfileInProgress.ProfileName; + + public EditorSessionSettings(CharacterProfile prof) + { + ProfileInProgress = prof; + Plugin.ArmatureManager.ConstructArmatureForProfile(prof); + + _originalCharName = prof.CharacterName; + _originalProfName = prof.ProfileName; + + ProfileInProgress.Enabled = true; + ShowLiveBones = true; } } @@ -678,7 +666,7 @@ internal struct EditRowParams public string BoneCodeName; public string BoneDisplayName => BoneData.GetBoneDisplayName(BoneCodeName); public BoneTransform Transform; - public ModelBone? Basis = null; + public ModelBone Basis; public EditRowParams(ModelBone mb) { diff --git a/CustomizePlus/UI/Windows/MainWindow.cs b/CustomizePlus/UI/Windows/MainWindow.cs index cb09828..0fd3f9d 100644 --- a/CustomizePlus/UI/Windows/MainWindow.cs +++ b/CustomizePlus/UI/Windows/MainWindow.cs @@ -307,7 +307,6 @@ protected override void DrawContents() } CtrlHelper.AddHoverText(string.Join('\n', - $"D:", $"Profile '{prof.ProfileName}'", $"for {prof.CharacterName}", $"with {prof.Bones.Count} modified bones", @@ -319,7 +318,7 @@ protected override void DrawContents() // Edit ImGui.TableNextColumn(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Pen) - && Plugin.ProfileManager.GetWorkingCopy(prof, out var profCopy) + && Plugin.ProfileManager.GetWorkingCopy(prof) is CharacterProfile profCopy && profCopy != null) { BoneEditWindow.Show(profCopy); @@ -332,15 +331,10 @@ protected override void DrawContents() // Dupe ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy) - && Plugin.ProfileManager.GetWorkingCopy(prof, out var dupe) - && dupe != null) + if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) { var newProfileName = ValidateProfileName(characterName, inputProfName); - dupe.ProfileName = newProfileName; - - Plugin.ProfileManager.StopEditing(dupe); - Plugin.ProfileManager.AddAndSaveProfile(dupe, true); + Plugin.ProfileManager.DuplicateProfile(prof, characterName, newProfileName); } CtrlHelper.AddHoverText("Duplicate Profile"); From 28b8e34295b79e59bd0e8b04aca7b582403f5106 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Mon, 19 Jun 2023 00:39:49 -0400 Subject: [PATCH 12/37] Fix some git weirdness. This commit should have already not been included? --- CustomizePlus/Data/Armature/Armature.cs | 90 +++++++++++++++++-- CustomizePlus/Data/Armature/ModelBone.cs | 8 +- CustomizePlus/Helpers/GameStateHelper.cs | 11 --- CustomizePlus/Plugin.cs | 28 ++++-- .../Services/PosingModeDetectService.cs | 20 ++--- 5 files changed, 115 insertions(+), 42 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index cc937ed..ae5b227 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -415,19 +415,22 @@ public unsafe void ApplyPiecewiseTransformation(GameObject obj) && mb != null && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String) { - if (mb != MainRootBone || obj.HasScalableRoot()) + if (mb == MainRootBone) { - mb.ApplyModelScale(cBase); - } + if (obj.HasScalableRoot()) + { + mb.ApplyModelScale(cBase); + } - if (!GameStateHelper.GameInPosingModeWithFrozenRotation()) - { mb.ApplyModelRotation(cBase); } - - if (!GameStateHelper.GameInPosingModeWithFrozenPosition()) + else if (GameStateHelper.GameInPosingMode()) { - mb.ApplyModelTranslationAtAngle(cBase); + mb.ApplyModelScale(cBase); + } + else + { + mb.ApplyModelTransform(cBase); } } } @@ -440,7 +443,7 @@ public void ApplyRootTranslation(CharacterBase* cBase) { if (cBase != null && _partialSkeletons.Any() && _partialSkeletons.First().Any()) { - _partialSkeletons[0][0].ApplyModelTranslationAsIs(cBase); + _partialSkeletons[0][0].ApplyStraightModelTranslation(cBase); } } @@ -450,5 +453,74 @@ private static bool AreTwinnedNames(string name1, string name2) && (name1[^1] == 'l' ^ name2[^1] == 'l') && (name1[0..^1] == name2[0..^1]); } + + //public void OverrideWithReferencePose() + //{ + // for (var pSkeleIndex = 0; pSkeleIndex < Skeleton->PartialSkeletonCount; ++pSkeleIndex) + // { + // for (var poseIndex = 0; poseIndex < 4; ++poseIndex) + // { + // var snapPose = Skeleton->PartialSkeletons[pSkeleIndex].GetHavokPose(poseIndex); + + // if (snapPose != null) + // { + // snapPose->SetToReferencePose(); + // } + // } + // } + //} + + //public void OverrideRootParenting() + //{ + // var pSkeleNot = Skeleton->PartialSkeletons[0]; + + // for (var pSkeleIndex = 1; pSkeleIndex < Skeleton->PartialSkeletonCount; ++pSkeleIndex) + // { + // var partialSkele = Skeleton->PartialSkeletons[pSkeleIndex]; + + // for (var poseIndex = 0; poseIndex < 4; ++poseIndex) + // { + // var currentPose = partialSkele.GetHavokPose(poseIndex); + + // if (currentPose != null && partialSkele.ConnectedBoneIndex >= 0) + // { + // int boneIdx = partialSkele.ConnectedBoneIndex; + // int parentBoneIdx = partialSkele.ConnectedParentBoneIndex; + + // var transA = currentPose->AccessBoneModelSpace(boneIdx, 0); + // var transB = pSkeleNot.GetHavokPose(0)->AccessBoneModelSpace(parentBoneIdx, 0); + + // //currentPose->AccessBoneModelSpace(parentBoneIdx, hkaPose.PropagateOrNot.DontPropagate); + + // for (var i = 0; i < currentPose->Skeleton->Bones.Length; ++i) + // { + // currentPose->ModelPose[i] = ApplyPropagatedTransform(currentPose->ModelPose[i], transB, + // transA->Translation, transB->Rotation); + // currentPose->ModelPose[i] = ApplyPropagatedTransform(currentPose->ModelPose[i], transB, + // transB->Translation, transA->Rotation); + // } + // } + // } + // } + //} + + //private hkQsTransformf ApplyPropagatedTransform(hkQsTransformf init, hkQsTransformf* propTrans, + // hkVector4f initialPos, hkQuaternionf initialRot) + //{ + // var sourcePosition = propTrans->Translation.GetAsNumericsVector().RemoveWTerm(); + // var deltaRot = propTrans->Rotation.ToQuaternion() / initialRot.ToQuaternion(); + // var deltaPos = sourcePosition - initialPos.GetAsNumericsVector().RemoveWTerm(); + + // hkQsTransformf output = new() + // { + // Translation = Vector3 + // .Transform(init.Translation.GetAsNumericsVector().RemoveWTerm() - sourcePosition, deltaRot) + // .ToHavokTranslation(), + // Rotation = deltaRot.ToHavokRotation(), + // Scale = init.Scale + // }; + + // return output; + //} } } \ No newline at end of file diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index c9b8cd8..71f7902 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -243,9 +243,7 @@ private static void SetGameTransform(CharacterBase* cBase, hkQsTransformf transf hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); //hkaPose* targetPose = cBase->Skeleton->PartialSkeletons[PartialSkeletonIndex].GetHavokPose(Constants.TruePoseIndex); - //The second condition here is apparently a check of whether the model has been updated since the last frame - if (targetPose == null || targetPose->ModelInSync == 0) - return; + if (targetPose == null) return; switch (refFrame) { @@ -283,8 +281,8 @@ public void ApplyModelTransform(CharacterBase* cBase) public void ApplyModelScale(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingScale); public void ApplyModelRotation(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingRotation); - public void ApplyModelTranslationAtAngle(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslationWithRotation); - public void ApplyModelTranslationAsIs(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslation); + public void ApplyModelFullTranslation(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslationWithRotation); + public void ApplyStraightModelTranslation(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslation); private void ApplyTransFunc(CharacterBase* cBase, Func modTrans) { diff --git a/CustomizePlus/Helpers/GameStateHelper.cs b/CustomizePlus/Helpers/GameStateHelper.cs index adfcf8b..b951472 100644 --- a/CustomizePlus/Helpers/GameStateHelper.cs +++ b/CustomizePlus/Helpers/GameStateHelper.cs @@ -14,16 +14,5 @@ public static bool GameInPosingMode() || Services.PosingModeDetectService.Instance.IsInPosingMode; } - public static bool GameInPosingModeWithFrozenRotation() - { - return Services.GPoseService.Instance.GPoseState == Services.GPoseState.Inside - && Services.PosingModeDetectService.IsAnamnesisRotationFrozen; - } - - public static bool GameInPosingModeWithFrozenPosition() - { - return Services.GPoseService.Instance.GPoseState == Services.GPoseState.Inside - && Services.PosingModeDetectService.IsAnamnesisPositionFrozen; - } } } diff --git a/CustomizePlus/Plugin.cs b/CustomizePlus/Plugin.cs index c801952..6925087 100644 --- a/CustomizePlus/Plugin.cs +++ b/CustomizePlus/Plugin.cs @@ -42,10 +42,10 @@ public sealed class Plugin : IDalamudPlugin private static Hook? _renderManagerHook; private static Hook? _gameObjectMovementHook; - private delegate nint RenderDelegate(nint a1, nint a2, int a3, int a4); + private delegate IntPtr RenderDelegate(IntPtr a1, long a2, int a3, int a4); [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate void GameObjectMovementDelegate(nint gameObject); + private delegate void GameObjectMovementDelegate(IntPtr gameObject); public Plugin(DalamudPluginInterface pluginInterface) @@ -225,13 +225,16 @@ private void ApplyByCommand(string args) } } - private static nint OnRender(nint a1, nint a2, int a3, int a4) + private static IntPtr OnRender(IntPtr a1, long a2, int a3, int a4) { if (_renderManagerHook == null) { throw new Exception(); } + // if this gets disposed while running we crash calling Original's getter, so get it at start + var original = _renderManagerHook.Original; + try { var activeProfiles = ProfileManager.GetEnabledProfiles(); @@ -243,17 +246,17 @@ private static nint OnRender(nint a1, nint a2, int a3, int a4) _renderManagerHook?.Disable(); } - return _renderManagerHook.Original(a1, a2, a3, a4); + return original(a1, a2, a3, a4); } //todo: doesn't work in cutscenes, something getting called after this and resets changes - private unsafe static void OnGameObjectMove(nint gameObjectPtr) + private unsafe static void OnGameObjectMove(IntPtr gameObjectPtr) { // Call the original function. - _gameObjectMovementHook?.Original(gameObjectPtr); + _gameObjectMovementHook.Original(gameObjectPtr); ////If GPose and a 3rd-party posing service are active simultneously, abort - if (GameStateHelper.GameInPosingModeWithFrozenPosition()) + if (GameStateHelper.GameInPosingMode()) { return; } @@ -265,6 +268,17 @@ private unsafe static void OnGameObjectMove(nint gameObjectPtr) && prof.Armature != null) { prof.Armature.ApplyRootTranslation(obj.ToCharacterBase()); + + //var objIndex = obj.ObjectIndex; + + //var isForbiddenFiller = objIndex == Constants.ObjectTableFillerIndex; + //var isForbiddenCutsceneNPC = Constants.IsInObjectTableCutsceneNPCRange(objIndex) + // || !ConfigurationManager.Configuration.ApplyToNPCsInCutscenes; + + //if (!isForbiddenFiller && !isForbiddenCutsceneNPC) + //{ + // ArmatureManager.RenderArmatureByObject(obj); + //} } } } diff --git a/CustomizePlus/Services/PosingModeDetectService.cs b/CustomizePlus/Services/PosingModeDetectService.cs index 9a84f22..5cfc003 100644 --- a/CustomizePlus/Services/PosingModeDetectService.cs +++ b/CustomizePlus/Services/PosingModeDetectService.cs @@ -12,23 +12,23 @@ internal class PosingModeDetectService : ServiceBase { // Borrowed from Ktisis: // If this is NOP'd, Anam posing is enabled. - private static unsafe byte* AnamnesisFreezePosition; - private static unsafe byte* AnamnesisFreezeRotation; - private static unsafe byte* AnamnesisFreezeScale; + internal static unsafe byte* AnamnesisFreezePosition; + internal static unsafe byte* AnamnesisFreezeRotation; + internal static unsafe byte* AnamnesisFreezeScale; internal static unsafe bool IsAnamnesisPositionFrozen => - (AnamnesisFreezePosition != null && *AnamnesisFreezePosition == 0x90) - || *AnamnesisFreezePosition == 0x00; + (AnamnesisFreezePosition != null && *AnamnesisFreezePosition == 0x90) || *AnamnesisFreezePosition == 0x00; internal static unsafe bool IsAnamnesisRotationFrozen => - (AnamnesisFreezeRotation != null && *AnamnesisFreezeRotation == 0x90) - || *AnamnesisFreezeRotation == 0x00; + (AnamnesisFreezeRotation != null && *AnamnesisFreezeRotation == 0x90) || *AnamnesisFreezeRotation == 0x00; internal static unsafe bool IsAnamnesisScalingFrozen => - (AnamnesisFreezeScale != null && *AnamnesisFreezeScale == 0x90) - || *AnamnesisFreezeScale == 0x00; + (AnamnesisFreezeScale != null && *AnamnesisFreezeScale == 0x90) || *AnamnesisFreezeScale == 0x00; - public bool IsInPosingMode => IsAnamnesisPositionFrozen || IsAnamnesisRotationFrozen || IsAnamnesisScalingFrozen; + internal static bool IsAnamnesis => + IsAnamnesisPositionFrozen || IsAnamnesisRotationFrozen || IsAnamnesisScalingFrozen; + + public bool IsInPosingMode => IsAnamnesis; //Can't detect Ktisis for now public override unsafe void Start() { From 23725b8c149c9cc7219d7374cd3e82890c0c4bc8 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Wed, 21 Jun 2023 04:51:35 -0400 Subject: [PATCH 13/37] Perform renames related to posing space enum --- CustomizePlus/Data/Armature/AliasedBone.cs | 16 ++++++------ CustomizePlus/Data/Armature/ModelBone.cs | 25 ++++++++++--------- .../UI/Windows/Debug/BoneMonitorWindow.cs | 10 ++++---- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/CustomizePlus/Data/Armature/AliasedBone.cs b/CustomizePlus/Data/Armature/AliasedBone.cs index c15379c..80127bd 100644 --- a/CustomizePlus/Data/Armature/AliasedBone.cs +++ b/CustomizePlus/Data/Armature/AliasedBone.cs @@ -16,8 +16,8 @@ namespace CustomizePlus.Data.Armature /// internal unsafe class AliasedBone : ModelBone { - private delegate hkQsTransformf TransformGetter(CharacterBase* cBase, PoseType refFrame); - private delegate void TransformSetter(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame); + private delegate hkQsTransformf TransformGetter(CharacterBase* cBase, PosingSpace refFrame); + private delegate void TransformSetter(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame); private TransformGetter _getTransform; private TransformSetter _setTransform; @@ -38,12 +38,12 @@ public static AliasedBone CreateWeaponBone(Armature arm, string codename) return new AliasedBone(arm, codename, GetChildObjectTransform, SetChildObjectTransform); } - public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase, PoseType refFrame) + public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase, PosingSpace refFrame) { return _getTransform(cBase, refFrame); } - protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame) { _setTransform(cBase, transform, refFrame); } @@ -91,7 +91,7 @@ protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransf #region Stock accessor functions - private static hkQsTransformf GetWholeskeletonTransform(CharacterBase* cBase, PoseType refFrame) + private static hkQsTransformf GetWholeskeletonTransform(CharacterBase* cBase, PosingSpace refFrame) { return new hkQsTransformf() { @@ -101,7 +101,7 @@ private static hkQsTransformf GetWholeskeletonTransform(CharacterBase* cBase, Po }; } - private static void SetWholeSkeletonTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + private static void SetWholeSkeletonTransform(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame) { BoneTransform original = new(GetWholeskeletonTransform(cBase, refFrame)); BoneTransform modified = new(transform); @@ -122,7 +122,7 @@ private static void SetWholeSkeletonTransform(CharacterBase* cBase, hkQsTransfor }; } - private static hkQsTransformf GetChildObjectTransform(CharacterBase* cBase, PoseType refFrame) + private static hkQsTransformf GetChildObjectTransform(CharacterBase* cBase, PosingSpace refFrame) { Object* obj = cBase->DrawObject.Object.ChildObject; @@ -142,7 +142,7 @@ private static hkQsTransformf GetChildObjectTransform(CharacterBase* cBase, Pose Scale = wBase->CharacterBase.Skeleton->Transform.Scale.ToHavokVector() }; } - private static void SetChildObjectTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + private static void SetChildObjectTransform(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame) { Object* obj = cBase->DrawObject.Object.ChildObject; diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 0f24894..a29a7ae 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -22,9 +22,9 @@ namespace CustomizePlus.Data.Armature /// public unsafe class ModelBone { - public enum PoseType + public enum PosingSpace { - Local, Model, BindPose, World + Self, Parent, Character } public readonly Armature MasterArmature; @@ -286,7 +286,7 @@ private static hkQsTransformf Add(hkQsTransformf term1, hkQsTransformf term2) /// Given a character base to which this model bone's master armature (presumably) applies, /// return the game's transform value for this model's in-game sibling within the given reference frame. /// - public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase, PoseType refFrame) + public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase, PosingSpace refFrame) { FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; @@ -298,14 +298,15 @@ public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase, PoseType re return refFrame switch { - PoseType.Local => targetPose->LocalPose[BoneIndex], - PoseType.Model => targetPose->ModelPose[BoneIndex], + PosingSpace.Self => targetPose->GetSyncedPoseLocalSpace()->Data[BoneIndex], + PosingSpace.Parent => localTransform, + PosingSpace.Character => targetPose->GetSyncedPoseModelSpace()->Data[BoneIndex], _ => Constants.NullTransform //TODO properly implement the other options }; } - protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PoseType refFrame) + protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame) { FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[PartialSkeletonIndex]; @@ -316,11 +317,11 @@ protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf tra switch (refFrame) { - case PoseType.Local: + case PosingSpace.Self: targetPose->LocalPose.Data[BoneIndex] = transform; return; - case PoseType.Model: + case PosingSpace.Parent: targetPose->ModelPose.Data[BoneIndex] = transform; return; @@ -339,13 +340,13 @@ public virtual void ApplyModelTransform(CharacterBase* cBase) { if (cBase != null && CustomizedTransform.IsEdited() - && GetGameTransform(cBase, PoseType.Model) is hkQsTransformf gameTransform + && GetGameTransform(cBase, PosingSpace.Parent) is hkQsTransformf gameTransform && !gameTransform.Equals(Constants.NullTransform)) { if (CustomizedTransform.ModifyExistingTransform(gameTransform) is hkQsTransformf modTransform && !modTransform.Equals(Constants.NullTransform)) { - SetGameTransform(cBase, modTransform, PoseType.Model); + SetGameTransform(cBase, modTransform, PosingSpace.Parent); } } } @@ -359,14 +360,14 @@ private void ApplyTransFunc(CharacterBase* cBase, Func _groupExpandedState = new(); private readonly bool _modelFrozen = false; - private ModelBone.PoseType _targetPose; + private ModelBone.PosingSpace _targetPose; private bool _aggregateDeforms; private BoneAttribute _targetAttribute; @@ -103,12 +103,12 @@ protected override void DrawContents() ImGui.Spacing(); ImGui.SameLine(); - if (ImGui.RadioButton("Local", _targetPose == ModelBone.PoseType.Local)) - _targetPose = ModelBone.PoseType.Local; + if (ImGui.RadioButton("Local", _targetPose == ModelBone.PosingSpace.Self)) + _targetPose = ModelBone.PosingSpace.Self; ImGui.SameLine(); - if (ImGui.RadioButton("Model", _targetPose == ModelBone.PoseType.Model)) - _targetPose = ModelBone.PoseType.Model; + if (ImGui.RadioButton("Model", _targetPose == ModelBone.PosingSpace.Parent)) + _targetPose = ModelBone.PosingSpace.Parent; //ImGui.SameLine(); //if (ImGui.RadioButton("Reference", _targetPose == ModelBone.PoseType.Reference)) From ad0621011e3e8a9671a925a9706e8ca9c54f2f0e Mon Sep 17 00:00:00 2001 From: Dendroid Date: Thu, 22 Jun 2023 23:44:41 -0400 Subject: [PATCH 14/37] Replace all instances of System.Numerics vectors with FFXIVClientStruct vectors for all cases relating to game geometry (ie everything but where required by Imgui) Update vector extension methods accordingly --- CustomizePlus/Data/Armature/Armature.cs | 12 ++-- CustomizePlus/Data/BoneTransform.cs | 37 ++++------ .../Version2/Version2BodyScale.cs | 3 +- .../Data/Profile/ProfileConverter.cs | 10 +-- .../Extensions/TransformExtensions.cs | 32 ++------- CustomizePlus/Extensions/VectorExtensions.cs | 72 +++++-------------- .../GPoseAmnesisKtisisWarningService.cs | 2 +- CustomizePlus/UI/Windows/BoneEditWindow.cs | 2 +- .../UI/Windows/Debug/BoneMonitorWindow.cs | 4 +- CustomizePlus/UI/Windows/MainWindow.cs | 2 +- 10 files changed, 53 insertions(+), 123 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index ae5b227..aab4b33 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -4,16 +4,16 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; + using CustomizePlus.Data.Profile; -using CustomizePlus.Extensions; using CustomizePlus.Helpers; + +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; + using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.Havok; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip; namespace CustomizePlus.Data.Armature { @@ -194,7 +194,7 @@ private void SetReferenceSnap(bool value) { if (GameDataHelper.TryLookupCharacterBase(Profile.CharacterName, out CharacterBase* cBase) && cBase != null) - { + { if (!Built || forceRebuild) { RebuildSkeleton(cBase); @@ -245,7 +245,7 @@ private bool NewBonesAvailable(CharacterBase* cBase) /// public void RebuildSkeleton(CharacterBase* cBase) { - if (cBase == null) + if (cBase == null) return; List> newPartials = ParseBonesFromObject(this, cBase); diff --git a/CustomizePlus/Data/BoneTransform.cs b/CustomizePlus/Data/BoneTransform.cs index d26d9eb..0c18530 100644 --- a/CustomizePlus/Data/BoneTransform.cs +++ b/CustomizePlus/Data/BoneTransform.cs @@ -2,10 +2,10 @@ // Licensed under the MIT license. using System; -using System.Numerics; using System.Runtime.Serialization; using CustomizePlus.Extensions; using FFXIVClientStructs.Havok; +using FFXIVClientStructs.FFXIV.Common.Math; namespace CustomizePlus.Data { @@ -63,9 +63,9 @@ public Vector3 Scaling internal void OnDeserialized(StreamingContext context) { //Sanitize all values on deserialization - _translation = ClampToDefaultLimits(_translation); + _translation = BoneTransform.ClampVector(_translation); _rotation = ClampAngles(_rotation); - _scaling = ClampToDefaultLimits(_scaling); + _scaling = BoneTransform.ClampVector(_scaling); } public bool IsEdited() @@ -149,7 +149,7 @@ private void Sanitize() /// /// Clamp all vector values to be within allowed limits. /// - private Vector3 ClampVector(Vector3 vector) + private static Vector3 ClampVector(Vector3 vector) { return new Vector3 { @@ -161,19 +161,20 @@ private Vector3 ClampVector(Vector3 vector) private static Vector3 ClampAngles(Vector3 rotVec) { - static float Clamp(float angle) + static float Clamp_Helper(float angle) { - if (angle > 180) + while (angle > 180) angle -= 360; - else if (angle < -180) + + while (angle < -180) angle += 360; return angle; } - rotVec.X = Clamp(rotVec.X); - rotVec.Y = Clamp(rotVec.Y); - rotVec.Z = Clamp(rotVec.Z); + rotVec.X = Clamp_Helper(rotVec.X); + rotVec.Y = Clamp_Helper(rotVec.Y); + rotVec.Z = Clamp_Helper(rotVec.Z); return rotVec; } @@ -194,7 +195,7 @@ public hkQsTransformf ModifyExistingScale(hkQsTransformf tr) public hkQsTransformf ModifyExistingRotation(hkQsTransformf tr) { - var newRotation = Quaternion.Multiply(tr.Rotation.ToQuaternion(), Rotation.ToQuaternion()); + Quaternion newRotation = tr.Rotation.ToClientQuaternion() * Rotation.ToQuaternion(); tr.Rotation.X = newRotation.X; tr.Rotation.Y = newRotation.Y; tr.Rotation.Z = newRotation.Z; @@ -205,7 +206,7 @@ public hkQsTransformf ModifyExistingRotation(hkQsTransformf tr) public hkQsTransformf ModifyExistingTranslationWithRotation(hkQsTransformf tr) { - var adjustedTranslation = Vector4.Transform(Translation, tr.Rotation.ToQuaternion()); + var adjustedTranslation = Vector4.Transform(Translation, tr.Rotation.ToClientQuaternion()); tr.Translation.X += adjustedTranslation.X; tr.Translation.Y += adjustedTranslation.Y; tr.Translation.Z += adjustedTranslation.Z; @@ -222,17 +223,5 @@ public hkQsTransformf ModifyExistingTranslation(hkQsTransformf tr) return tr; } - - /// - /// Clamp all vector values to be within allowed limits. - /// - private static Vector3 ClampToDefaultLimits(Vector3 vector) - { - vector.X = Math.Clamp(vector.X, Constants.MinVectorValueLimit, Constants.MaxVectorValueLimit); - vector.Y = Math.Clamp(vector.Y, Constants.MinVectorValueLimit, Constants.MaxVectorValueLimit); - vector.Z = Math.Clamp(vector.Z, Constants.MinVectorValueLimit, Constants.MaxVectorValueLimit); - - return vector; - } } } \ No newline at end of file diff --git a/CustomizePlus/Data/Configuration/Version2/Version2BodyScale.cs b/CustomizePlus/Data/Configuration/Version2/Version2BodyScale.cs index fd9f7e8..e23b801 100644 --- a/CustomizePlus/Data/Configuration/Version2/Version2BodyScale.cs +++ b/CustomizePlus/Data/Configuration/Version2/Version2BodyScale.cs @@ -3,7 +3,8 @@ using System; using System.Collections.Generic; -using System.Numerics; + +using FFXIVClientStructs.FFXIV.Common.Math; namespace CustomizePlus.Data.Configuration.Version2 { diff --git a/CustomizePlus/Data/Profile/ProfileConverter.cs b/CustomizePlus/Data/Profile/ProfileConverter.cs index dca047e..7db6cb2 100644 --- a/CustomizePlus/Data/Profile/ProfileConverter.cs +++ b/CustomizePlus/Data/Profile/ProfileConverter.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; +using FFXIVClientStructs.FFXIV.Common.Math; using CustomizePlus.Anamnesis; using CustomizePlus.Data.Configuration.Version0; using CustomizePlus.Data.Configuration.Version2; @@ -54,7 +54,7 @@ public static class ProfileConverter { var bt = new BoneTransform { - Scaling = kvp.Value.Scale!.GetAsNumericsVector() + Scaling = kvp.Value.Scale!.ToClientVector3() }; output.Bones[kvp.Key] = bt; @@ -65,14 +65,14 @@ public static class ProfileConverter var validRoot = pose.Bones.TryGetValue(Constants.RootBoneName, out var root) && root != null && root.Scale != null - && root.Scale.GetAsNumericsVector() != Vector3.Zero - && root.Scale.GetAsNumericsVector() != Vector3.One; + && root.Scale.ToClientVector3() != Vector3.Zero + && root.Scale.ToClientVector3() != Vector3.One; if (validRoot) { output.Bones[Constants.RootBoneName] = new BoneTransform { - Scaling = root.Scale!.GetAsNumericsVector() + Scaling = root.Scale!.ToClientVector3() }; } diff --git a/CustomizePlus/Extensions/TransformExtensions.cs b/CustomizePlus/Extensions/TransformExtensions.cs index a945f81..bc3e862 100644 --- a/CustomizePlus/Extensions/TransformExtensions.cs +++ b/CustomizePlus/Extensions/TransformExtensions.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System; -using System.Numerics; +using FFXIVClientStructs.FFXIV.Common.Math; using CustomizePlus.Data; using FFXIVClientStructs.Havok; @@ -24,35 +24,13 @@ public static bool IsNull(this hkQsTransformf t) return t.Equals(Constants.NullTransform); } - public static hkQsTransformf ToHavokTransform(this BoneTransform bt) - { - return new hkQsTransformf - { - Translation = bt.Translation.ToHavokTranslation(), - Rotation = bt.Rotation.ToQuaternion().ToHavokRotation(), - Scale = bt.Scaling.ToHavokScaling() - }; - } - - public static BoneTransform ToBoneTransform(this hkQsTransformf t) - { - var rotVec = Quaternion.Divide(t.Translation.ToQuaternion(), t.Rotation.ToQuaternion()); - - return new BoneTransform - { - Translation = new Vector3(rotVec.X / rotVec.W, rotVec.Y / rotVec.W, rotVec.Z / rotVec.W), - Rotation = t.Rotation.ToQuaternion().ToEulerAngles(), - Scaling = new Vector3(t.Scale.X, t.Scale.Y, t.Scale.Z) - }; - } - - public static hkVector4f GetAttribute(this hkQsTransformf t, BoneAttribute att) + public static Vector4 GetAttribute(this hkQsTransformf t, BoneAttribute att) { return att switch { - BoneAttribute.Position => t.Translation, - BoneAttribute.Rotation => t.Rotation.ToQuaternion().GetAsNumericsVector().ToHavokVector(), - BoneAttribute.Scale => t.Scale, + BoneAttribute.Position => t.Translation.ToClientVector4(), + BoneAttribute.Rotation => t.Rotation.ToClientQuaternion().ToClientVector4(), + BoneAttribute.Scale => t.Scale.ToClientVector4(), _ => throw new NotImplementedException() }; } diff --git a/CustomizePlus/Extensions/VectorExtensions.cs b/CustomizePlus/Extensions/VectorExtensions.cs index b58f2dc..e338629 100644 --- a/CustomizePlus/Extensions/VectorExtensions.cs +++ b/CustomizePlus/Extensions/VectorExtensions.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System; -using System.Numerics; +using FFXIVClientStructs.FFXIV.Common.Math; using CustomizePlus.Anamnesis; using FFXIVClientStructs.Havok; @@ -10,13 +10,6 @@ namespace CustomizePlus.Extensions { internal static class VectorExtensions { - public static bool IsApproximately(this hkVector4f vector, Vector3 other, float errorMargin = 0.001f) - { - return IsApproximately(vector.X, other.X, errorMargin) - && IsApproximately(vector.Y, other.Y, errorMargin) - && IsApproximately(vector.Z, other.Z, errorMargin); - } - public static bool IsApproximately(this Vector3 vector, Vector3 other, float errorMargin = 0.001f) { return IsApproximately(vector.X, other.X, errorMargin) @@ -40,40 +33,15 @@ public static Quaternion ToQuaternion(this Vector3 rotation) public static Vector3 ToEulerAngles(this Quaternion q) { - var nq = Vector4.Normalize(q.GetAsNumericsVector()); - - var rollX = MathF.Atan2( - 2 * (nq.W * nq.X + nq.Y * nq.Z), - 1 - 2 * (nq.X * nq.X + nq.Y * nq.Y)); - - var pitchY = 2 * MathF.Atan2( - MathF.Sqrt(1 + 2 * (nq.W * nq.Y - nq.X * nq.Z)), - MathF.Sqrt(1 - 2 * (nq.W * nq.Y - nq.X * nq.Z))); - - var yawZ = MathF.Atan2( - 2 * (nq.W * nq.Z + nq.X * nq.Y), - 1 - 2 * (nq.Y * nq.Y + nq.Z * nq.Z)); - - return new Vector3(rollX, pitchY, yawZ); - } - - public static Quaternion ToQuaternion(this Vector4 rotation) - { - return new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); + return q.EulerAngles; } - public static Quaternion ToQuaternion(this hkQuaternionf rotation) + public static Quaternion ToClientQuaternion(this hkQuaternionf rotation) { return new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); } - public static Quaternion ToQuaternion(this hkVector4f rotation) - { - return new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); - } - - - public static hkQuaternionf ToHavokRotation(this Quaternion rotation) + public static hkQuaternionf ToHavokQuaternion(this Quaternion rotation) { return new hkQuaternionf { @@ -84,24 +52,18 @@ public static hkQuaternionf ToHavokRotation(this Quaternion rotation) }; } - public static hkVector4f ToHavokTranslation(this Vector3 translation) + public static Vector4 ToClientVector(this Quaternion quat) { - return new hkVector4f - { - X = translation.X, - Y = translation.Y, - Z = translation.Z, - W = 0.0f - }; + return new Vector4(quat.X, quat.Y, quat.Z, quat.W); } - public static hkVector4f ToHavokScaling(this Vector3 scaling) + public static hkVector4f ToHavokVector(this Vector3 vec) { return new hkVector4f { - X = scaling.X, - Y = scaling.Y, - Z = scaling.Z, + X = vec.X, + Y = vec.Y, + Z = vec.Z, W = 1.0f }; } @@ -117,24 +79,24 @@ public static hkVector4f ToHavokVector(this Vector4 vec) }; } - public static Vector3 GetAsNumericsVector(this PoseFile.Vector vec) + public static Vector3 ToClientVector3(this PoseFile.Vector vec) { return new Vector3(vec.X, vec.Y, vec.Z); } - public static Vector4 GetAsNumericsVector(this hkVector4f vec) + public static Vector3 ToClientVector3(this hkVector4f vec) { - return new Vector4(vec.X, vec.Y, vec.Z, vec.W); + return new Vector3(vec.X, vec.Y, vec.Z); } - public static Vector4 GetAsNumericsVector(this Quaternion q) + public static Vector4 ToClientVector4(this hkVector4f vec) { - return new Vector4(q.X, q.Y, q.Z, q.W); + return new Vector4(vec.X, vec.Y, vec.Z, vec.W); } - public static Vector3 RemoveWTerm(this Vector4 vec) + public static Vector4 ToClientVector4(this Quaternion q) { - return new Vector3(vec.X, vec.Y, vec.Z); + return new Vector4(q.X, q.Y, q.Z, q.W); } public static bool Equals(this hkVector4f first, hkVector4f second) diff --git a/CustomizePlus/Services/GPoseAmnesisKtisisWarningService.cs b/CustomizePlus/Services/GPoseAmnesisKtisisWarningService.cs index 9356607..3bfbd6d 100644 --- a/CustomizePlus/Services/GPoseAmnesisKtisisWarningService.cs +++ b/CustomizePlus/Services/GPoseAmnesisKtisisWarningService.cs @@ -1,7 +1,7 @@ // © Customize+. // Licensed under the MIT license. -using System.Numerics; +using FFXIVClientStructs.FFXIV.Common.Math; using CustomizePlus.Core; using CustomizePlus.UI.Dialogs; diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index 43d93d7..f707211 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; -using System.Numerics; +using FFXIVClientStructs.FFXIV.Common.Math; using CustomizePlus.Data; using CustomizePlus.Data.Armature; using CustomizePlus.Data.Profile; diff --git a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs index a6792d6..d93ada6 100644 --- a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs +++ b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; +using FFXIVClientStructs.FFXIV.Common.Math; using CustomizePlus.Data; using CustomizePlus.Data.Armature; using CustomizePlus.Data.Profile; @@ -220,7 +220,7 @@ private void RenderTransformationInfo(ModelBone bone, CharacterBase* cBase) { var displayName = bone.ToString(); - var rowVector = deform.GetAttribute(_targetAttribute).GetAsNumericsVector(); + Vector4 rowVector = deform.GetAttribute(_targetAttribute); ImGui.PushID(bone.BoneName.GetHashCode()); diff --git a/CustomizePlus/UI/Windows/MainWindow.cs b/CustomizePlus/UI/Windows/MainWindow.cs index 0fd3f9d..949476b 100644 --- a/CustomizePlus/UI/Windows/MainWindow.cs +++ b/CustomizePlus/UI/Windows/MainWindow.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Linq; -using System.Numerics; +using FFXIVClientStructs.FFXIV.Common.Math; using System.Windows.Forms; using CustomizePlus.Data; using CustomizePlus.Data.Profile; From 88d43f6275523b017ccaa9d0cad9a950b80dd723 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Thu, 22 Jun 2023 23:50:15 -0400 Subject: [PATCH 15/37] Remove unecessary lineage functions. --- CustomizePlus/Data/Armature/ModelBone.cs | 51 +++--------------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 71f7902..bc08310 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -65,6 +65,8 @@ public enum PoseType /// public BoneTransform CustomizedTransform { get; } + #region Model Bone Construction + public ModelBone(Armature arm, string codeName, int partialIdx, int boneIdx) { MasterArmature = arm; @@ -100,7 +102,7 @@ public void AddChild(int childPartialIdx, int childBoneIdx) } /// - /// Indicate a bone that acts as this model bone's mirror image, or "twin". + /// Indicate a bone that acts as this model bone's mirror image /// public void AddTwin(int twinPartialIdx, int twinBoneIdx) { @@ -108,6 +110,8 @@ public void AddTwin(int twinPartialIdx, int twinBoneIdx) _twinBoneIndex = twinBoneIdx; } + #endregion + private void UpdateTransformation(BoneTransform newTransform) { //update the transform locally @@ -131,51 +135,6 @@ public override string ToString() return $"{BoneName} ({BoneData.GetBoneDisplayName(BoneName)}) @ <{PartialSkeletonIndex}, {BoneIndex}>"; } - /// - /// Get the lineage of this model bone, going back to the skeleton's root bone. - /// - public IEnumerable GetAncestors(bool includeSelf = true) => includeSelf - ? GetAncestors(new List() { this }) - : GetAncestors(new List()); - - private IEnumerable GetAncestors(List tail) - { - tail.Add(this); - if (ParentBone is ModelBone mb && mb != null) - { - return mb.GetAncestors(tail); - } - else - { - return tail; - } - } - - /// - /// Gets all model bones with a lineage that contains this one. - /// - public IEnumerable GetDescendants(bool includeSelf = false) => includeSelf - ? GetDescendants(this) - : GetDescendants(null); - - private IEnumerable GetDescendants(ModelBone? first) - { - List output = first != null - ? new List() { first } - : new List(); - - output.AddRange(ChildBones); - - using (var iter = output.GetEnumerator()) - { - while (iter.MoveNext()) - { - output.AddRange(iter.Current.ChildBones); - yield return iter.Current; - } - } - } - /// /// Update the transformation associated with this model bone. Optionally extend the transformation /// to the model bone's twin (in which case it will be appropriately mirrored) and/or children. From c9700d54944ae73917bc882a601c9797c4441fa9 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Fri, 23 Jun 2023 03:22:13 -0400 Subject: [PATCH 16/37] Abstract a bunch of data in preparation for future operational features. Split aliased bones into two separate kinds of subbone that can be checked for individually via pattern matching. --- CustomizePlus/Data/Armature/AliasedBone.cs | 167 ------------------ CustomizePlus/Data/Armature/Armature.cs | 118 ++++--------- CustomizePlus/Data/Armature/ModelBone.cs | 95 ++++------ CustomizePlus/Data/Armature/ModelRootBone.cs | 49 +++++ .../Data/Armature/PartialRootBone.cs | 26 +++ CustomizePlus/Data/BoneTransform.cs | 13 +- CustomizePlus/Data/IBoneContainer.cs | 24 +++ .../Data/Profile/CharacterProfile.cs | 28 ++- CustomizePlus/Data/TransformInfo.cs | 76 ++++++++ CustomizePlus/Extensions/VectorExtensions.cs | 85 ++------- CustomizePlus/UI/Windows/BoneEditWindow.cs | 128 +++----------- .../UI/Windows/Debug/BoneMonitorWindow.cs | 12 +- 12 files changed, 323 insertions(+), 498 deletions(-) delete mode 100644 CustomizePlus/Data/Armature/AliasedBone.cs create mode 100644 CustomizePlus/Data/Armature/ModelRootBone.cs create mode 100644 CustomizePlus/Data/Armature/PartialRootBone.cs create mode 100644 CustomizePlus/Data/IBoneContainer.cs create mode 100644 CustomizePlus/Data/TransformInfo.cs diff --git a/CustomizePlus/Data/Armature/AliasedBone.cs b/CustomizePlus/Data/Armature/AliasedBone.cs deleted file mode 100644 index 80127bd..0000000 --- a/CustomizePlus/Data/Armature/AliasedBone.cs +++ /dev/null @@ -1,167 +0,0 @@ -// © Customize+. -// Licensed under the MIT license. - -using CustomizePlus.Extensions; - -using FFXIVClientStructs.FFXIV.Common.Math; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using FFXIVClientStructs.Havok; -using System.Transactions; - -namespace CustomizePlus.Data.Armature -{ - /// - /// A fake model bone that doesn't actually correspond to a bone within a skeleton, - /// but instead some other data that can be nonetheless be transformed LIKE a bone. - /// - internal unsafe class AliasedBone : ModelBone - { - private delegate hkQsTransformf TransformGetter(CharacterBase* cBase, PosingSpace refFrame); - private delegate void TransformSetter(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame); - - private TransformGetter _getTransform; - private TransformSetter _setTransform; - - private AliasedBone(Armature arm, string codeName, TransformGetter tg, TransformSetter ts) : base(arm, codeName, 0, 0) - { - _getTransform = tg; - _setTransform = ts; - } - - public static AliasedBone CreateRootBone(Armature arm, string codename) - { - return new AliasedBone(arm, codename, GetWholeskeletonTransform, SetWholeSkeletonTransform); - } - - public static AliasedBone CreateWeaponBone(Armature arm, string codename) - { - return new AliasedBone(arm, codename, GetChildObjectTransform, SetChildObjectTransform); - } - - public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase, PosingSpace refFrame) - { - return _getTransform(cBase, refFrame); - } - - protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame) - { - _setTransform(cBase, transform, refFrame); - } - - //public override unsafe void ApplyModelTransform(CharacterBase* cBase) - //{ - // if (cBase != null - // && CustomizedTransform.IsEdited()) - // { - // hkQsTransformf originalTransform = new hkQsTransformf() - // { - // Translation = CustomizedTransform.Translation.ToHavokVector(), - // Rotation = CustomizedTransform.Rotation.ToQuaternion().ToHavokRotation(), - // Scale = CustomizedTransform.Scaling.ToHavokVector() - // }; - - // cBase->Skeleton->PartialSkeletons[0] - // .GetHavokPose(Constants.TruePoseIndex)->ModelPose.Data[0] - // .Translation.Y *= originalTransform.Scale.Y; - - // cBase->Skeleton->Transform.Position.X += CustomizedTransform.Translation.X; - // cBase->Skeleton->Transform.Position.Y += CustomizedTransform.Translation.Y; - // cBase->Skeleton->Transform.Position.Z += CustomizedTransform.Translation.Z; - - // Quaternion newRot = cBase->DrawObject.Object.Rotation.ToHavokRotation().ToQuaternion() - // * CustomizedTransform.Rotation.ToQuaternion(); - - // cBase->Skeleton->Transform.Rotation.X = newRot.X; - // cBase->Skeleton->Transform.Rotation.Y = newRot.Y; - // cBase->Skeleton->Transform.Rotation.Z = newRot.Z; - // cBase->Skeleton->Transform.Rotation.W = newRot.W; - - // cBase->Skeleton->Transform.Scale.X = CustomizedTransform.Scaling.X; - // cBase->Skeleton->Transform.Scale.Y = CustomizedTransform.Scaling.Y; - // cBase->Skeleton->Transform.Scale.Z = CustomizedTransform.Scaling.Z; - - // //i.e. check to see if the scale has been modified externally - - // //Vector3 currentScale = cBase->DrawObject.Object.Scale; - // //Vector3 expectedScale = _cachedScale?.HadamardMultiply(CustomizedTransform.Scaling) ?? Vector3.NegativeInfinity; - - - // } - //} - - #region Stock accessor functions - - private static hkQsTransformf GetWholeskeletonTransform(CharacterBase* cBase, PosingSpace refFrame) - { - return new hkQsTransformf() - { - Translation = cBase->Skeleton->Transform.Position.ToHavokVector(), - Rotation = cBase->Skeleton->Transform.Rotation.ToHavokRotation(), - Scale = cBase->Skeleton->Transform.Scale.ToHavokVector() - }; - } - - private static void SetWholeSkeletonTransform(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame) - { - BoneTransform original = new(GetWholeskeletonTransform(cBase, refFrame)); - BoneTransform modified = new(transform); - BoneTransform delta = modified - original; - - BoneTransform baseTransform = new BoneTransform() - { - Translation = cBase->DrawObject.Object.Position, - Rotation = cBase->DrawObject.Object.Rotation.EulerAngles, - Scaling = cBase->DrawObject.Object.Scale - }; - - cBase->Skeleton->Transform = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() - { - Position = transform.Translation.GetAsNumericsVector().ToClientVector(), - Rotation = transform.Rotation.ToQuaternion(), - Scale = transform.Scale.GetAsNumericsVector().ToClientVector() - }; - } - - private static hkQsTransformf GetChildObjectTransform(CharacterBase* cBase, PosingSpace refFrame) - { - Object* obj = cBase->DrawObject.Object.ChildObject; - - if (obj->GetObjectType() != ObjectType.CharacterBase) - { - return Constants.NullTransform; - } - - Weapon* wBase = (Weapon*)obj->NextSiblingObject; - - if (wBase == null) return Constants.NullTransform; - - return new hkQsTransformf() - { - Translation = wBase->CharacterBase.Skeleton->Transform.Position.ToHavokVector(), - Rotation = wBase->CharacterBase.Skeleton->Transform.Rotation.ToHavokRotation(), - Scale = wBase->CharacterBase.Skeleton->Transform.Scale.ToHavokVector() - }; - } - private static void SetChildObjectTransform(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame) - { - Object* obj = cBase->DrawObject.Object.ChildObject; - - if (obj->GetObjectType() != ObjectType.CharacterBase) - return; - - Weapon* wBase = (Weapon*)obj; - - if (wBase != null) - { - wBase->CharacterBase.Skeleton->Transform = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() - { - Position = transform.Translation.GetAsNumericsVector().ToClientVector(), - Rotation = transform.Rotation.ToQuaternion(), - Scale = transform.Scale.GetAsNumericsVector().ToClientVector() - }; - } - } - - #endregion - } -} diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index bb7508f..a727258 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime; using CustomizePlus.Data.Profile; using CustomizePlus.Helpers; @@ -21,7 +22,7 @@ namespace CustomizePlus.Data.Armature /// Represents a "copy" of the ingame skeleton upon which the linked character profile is meant to operate. /// Acts as an interface by which the in-game skeleton can be manipulated on a bone-by-bone basis. /// - public unsafe class Armature + public unsafe class Armature : IBoneContainer { /// /// Gets the Customize+ profile for which this mockup applies transformations. @@ -109,6 +110,9 @@ public ModelBone[] this[int i] // For that reason we must subtract the number of duplicate bones public int TotalBoneCount => _partialSkeletons.Sum(x => x.Length); + /// + /// Get all individual model bones making up this armature + /// public IEnumerable GetAllBones() { for (int i = 0; i < _partialSkeletons.Length; ++i) @@ -120,6 +124,11 @@ public IEnumerable GetAllBones() } } + /// + /// Get all individual model bones making up this armature EXCEPT for partial root bones + /// + public IEnumerable GetAllEditableBones() => GetAllBones().Where(x => x is not PartialRootBone); + /// /// Gets a value indicating whether this armature has yet built its skeleton. /// @@ -314,12 +323,13 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C if (pSkeleIndex == 0 && boneIndex == 0) { - newBone = AliasedBone.CreateRootBone(arm, boneName); + newBone = new ModelRootBone(arm, boneName); + } + else if (boneIndex == 0) + { + ModelBone cloneOf = newPartials[0][currentPartial.ConnectedBoneIndex]; + newBone = new PartialRootBone(arm, cloneOf, boneName, pSkeleIndex); } - //else if (boneName == "n_buki_r") - //{ - // newBone = AliasedBone.CreateWeaponBone(arm, boneName); - //} else { newBone = new ModelBone(arm, boneName, pSkeleIndex, boneIndex); @@ -371,11 +381,6 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C return newPartials; } - public void UpdateBoneTransform(int partialIdx, int boneIdx, BoneTransform bt, bool mirror = false, bool propagate = false) - { - this[partialIdx, boneIdx].UpdateModel(bt, mirror, propagate); - } - /// /// Iterate through this armature's model bones and apply their associated transformations /// to all of their in-game siblings. @@ -433,73 +438,28 @@ private static bool AreTwinnedNames(string name1, string name2) && (name1[0..^1] == name2[0..^1]); } - //public void OverrideWithReferencePose() - //{ - // for (var pSkeleIndex = 0; pSkeleIndex < Skeleton->PartialSkeletonCount; ++pSkeleIndex) - // { - // for (var poseIndex = 0; poseIndex < 4; ++poseIndex) - // { - // var snapPose = Skeleton->PartialSkeletons[pSkeleIndex].GetHavokPose(poseIndex); - - // if (snapPose != null) - // { - // snapPose->SetToReferencePose(); - // } - // } - // } - //} - - //public void OverrideRootParenting() - //{ - // var pSkeleNot = Skeleton->PartialSkeletons[0]; - - // for (var pSkeleIndex = 1; pSkeleIndex < Skeleton->PartialSkeletonCount; ++pSkeleIndex) - // { - // var partialSkele = Skeleton->PartialSkeletons[pSkeleIndex]; - - // for (var poseIndex = 0; poseIndex < 4; ++poseIndex) - // { - // var currentPose = partialSkele.GetHavokPose(poseIndex); - - // if (currentPose != null && partialSkele.ConnectedBoneIndex >= 0) - // { - // int boneIdx = partialSkele.ConnectedBoneIndex; - // int parentBoneIdx = partialSkele.ConnectedParentBoneIndex; - - // var transA = currentPose->AccessBoneModelSpace(boneIdx, 0); - // var transB = pSkeleNot.GetHavokPose(0)->AccessBoneModelSpace(parentBoneIdx, 0); - - // //currentPose->AccessBoneModelSpace(parentBoneIdx, hkaPose.PropagateOrNot.DontPropagate); - - // for (var i = 0; i < currentPose->Skeleton->Bones.Length; ++i) - // { - // currentPose->ModelPose[i] = ApplyPropagatedTransform(currentPose->ModelPose[i], transB, - // transA->Translation, transB->Rotation); - // currentPose->ModelPose[i] = ApplyPropagatedTransform(currentPose->ModelPose[i], transB, - // transB->Translation, transA->Rotation); - // } - // } - // } - // } - //} - - //private hkQsTransformf ApplyPropagatedTransform(hkQsTransformf init, hkQsTransformf* propTrans, - // hkVector4f initialPos, hkQuaternionf initialRot) - //{ - // var sourcePosition = propTrans->Translation.GetAsNumericsVector().RemoveWTerm(); - // var deltaRot = propTrans->Rotation.ToQuaternion() / initialRot.ToQuaternion(); - // var deltaPos = sourcePosition - initialPos.GetAsNumericsVector().RemoveWTerm(); - - // hkQsTransformf output = new() - // { - // Translation = Vector3 - // .Transform(init.Translation.GetAsNumericsVector().RemoveWTerm() - sourcePosition, deltaRot) - // .ToHavokTranslation(), - // Rotation = deltaRot.ToHavokRotation(), - // Scale = init.Scale - // }; - - // return output; - //} + public IEnumerable GetBoneTransformValues(BoneAttribute attribute, PosingSpace space) + { + return GetAllEditableBones().Select(x => new TransformInfo(this, x, attribute, space)); + } + + public void UpdateBoneTransformValue(TransformInfo newTransform, BoneUpdateMode mode, bool mirrorChanges, bool propagateChanges) + { + if (GetAllBones().FirstOrDefault(x => x.BoneName == newTransform.BoneCodeName) is ModelBone mb + && mb != null) + { + BoneTransform oldTransform = mb.GetTransformation(); + + BoneAttribute att = mode switch + { + BoneUpdateMode.Position => BoneAttribute.Position, + BoneUpdateMode.Rotation => BoneAttribute.Rotation, + _ => BoneAttribute.Scale + }; + + oldTransform.UpdateAttribute(att, newTransform.TransformationValue); + mb.UpdateModel(oldTransform, mirrorChanges, propagateChanges); + } + } } } \ No newline at end of file diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 2721c58..91df950 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -1,32 +1,30 @@ // © Customize+. // Licensed under the MIT license. -using CustomizePlus.Extensions; - using System; using System.Collections.Generic; -using System.Numerics; using System.Linq; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.Havok; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header; -using System.Reflection; //using CustomizePlus.Memory; namespace CustomizePlus.Data.Armature { + /// + /// Represents a frame of reference in which a model bone's transformations are being modified. + /// + public enum PosingSpace + { + Self, Parent, Character + } + /// /// Represents a single bone of an ingame character's skeleton. /// public unsafe class ModelBone { - public enum PosingSpace - { - Self, Parent, Character - } - public readonly Armature MasterArmature; public readonly int PartialSkeletonIndex; @@ -68,7 +66,7 @@ public enum PosingSpace /// The transform that this model bone will impart upon its in-game sibling when the master armature /// is applied to the in-game skeleton. /// - public BoneTransform CustomizedTransform { get; } + protected virtual BoneTransform CustomizedTransform { get; } #region Model Bone Construction @@ -107,7 +105,7 @@ public void AddChild(int childPartialIdx, int childBoneIdx) } /// - /// Indicate a bone that acts as this model bone's mirror image + /// Indicate a bone that acts as this model bone's mirror image. /// public void AddTwin(int twinPartialIdx, int twinBoneIdx) { @@ -123,21 +121,10 @@ private void UpdateTransformation(BoneTransform newTransform) CustomizedTransform.UpdateToMatch(newTransform); //the model bones should(?) be the same, by reference - //but we still need to delete them + //but we still may need to delete them if (newTransform.IsEdited()) { - //if (MainHandBone) - //{ - // MasterArmature.Profile.Bones_MH[BoneName] = new(newTransform); - //} - //else if (OffHandBone) - //{ - // MasterArmature.Profile.Bones_OH[BoneName] = new(newTransform); - //} - //else - //{ MasterArmature.Profile.Bones[BoneName] = new(newTransform); - //} } else { @@ -151,16 +138,13 @@ public override string ToString() return $"{BoneName} ({BoneData.GetBoneDisplayName(BoneName)}) @ <{PartialSkeletonIndex}, {BoneIndex}>"; } + public BoneTransform GetTransformation() => new(CustomizedTransform); + /// /// Update the transformation associated with this model bone. Optionally extend the transformation /// to the model bone's twin (in which case it will be appropriately mirrored) and/or children. /// public void UpdateModel(BoneTransform newTransform, bool mirror = false, bool propagate = false) - { - UpdateModel(newTransform, mirror, propagate, true); - } - - private void UpdateModel(BoneTransform newTransform, bool mirror, bool propagate, bool clone) { if (mirror && TwinBone is ModelBone mb && mb != null) { @@ -171,7 +155,7 @@ private void UpdateModel(BoneTransform newTransform, bool mirror, bool propagate mb.UpdateModel(mirroredTransform, false, propagate); } - if (propagate && this is not AliasedBone) + if (propagate && this is not ModelRootBone) { BoneTransform delta = new BoneTransform() { @@ -185,15 +169,18 @@ private void UpdateModel(BoneTransform newTransform, bool mirror, bool propagate UpdateTransformation(newTransform); - if (clone) + IEnumerable clones = MasterArmature.GetAllBones() + .Where(x => x.BoneName == BoneName && !ReferenceEquals(x, this)); + + foreach (ModelBone clone in clones) { - UpdateClones(newTransform); + clone.UpdateModel(newTransform, mirror, propagate); } } private void PropagateModelUpdate(BoneTransform deltaTransform) { - foreach(ModelBone mb in ChildBones) + foreach (ModelBone mb in ChildBones) { BoneTransform modTransform = new(CustomizedTransform); modTransform.Translation += deltaTransform.Translation; @@ -205,22 +192,9 @@ private void PropagateModelUpdate(BoneTransform deltaTransform) } } - /// - /// For each OTHER bone that shares the name of this one, direct - /// it to update its transform to match the one provided. - /// - private void UpdateClones(BoneTransform newTransform) - { - foreach(ModelBone mb in MasterArmature.GetAllBones() - .Where(x => x.BoneName == this.BoneName && x != this)) - { - mb.UpdateTransformation(newTransform); - } - } - /// /// Given a character base to which this model bone's master armature (presumably) applies, - /// return the game's transform value for this model's in-game sibling within the given reference frame. + /// return the game's current transform value for the bone corresponding to this model bone (in model space). /// public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase) { @@ -235,7 +209,11 @@ public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase) return targetPose->GetSyncedPoseModelSpace()->Data[BoneIndex]; } - protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, PosingSpace refFrame) + /// + /// Given a character base to which this model bone's master armature (presumably) applies, + /// change to the given transform value the value for the bone corresponding to this model bone. + /// + protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform) { FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[PartialSkeletonIndex]; @@ -244,21 +222,8 @@ protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf tra if (targetPose == null) return; - switch (refFrame) - { - case PosingSpace.Self: - targetPose->LocalPose.Data[BoneIndex] = transform; - return; - - case PosingSpace.Parent: - targetPose->ModelPose.Data[BoneIndex] = transform; - return; - - default: - return; - - //TODO properly implement the other options - } + targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex] = transform; + targetPose->BoneFlags[BoneIndex] = 1; } /// @@ -275,7 +240,7 @@ public virtual void ApplyModelTransform(CharacterBase* cBase) if (CustomizedTransform.ModifyExistingTransform(gameTransform) is hkQsTransformf modTransform && !modTransform.Equals(Constants.NullTransform)) { - SetGameTransform(cBase, modTransform, PosingSpace.Parent); + SetGameTransform(cBase, modTransform); } } } @@ -296,7 +261,7 @@ private void ApplyTransFunc(CharacterBase* cBase, Func + /// A fake model bone that doesn't actually correspond to a bone within a skeleton, + /// but instead some other data that can be nonetheless be transformed LIKE a bone. + /// + internal unsafe class ModelRootBone : ModelBone + { + public ModelRootBone(Armature arm, string codeName) : base(arm, codeName, 0, 0) { } + + public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase) + { + //return new hkQsTransformf() + //{ + // Translation = cBase->DrawObject.Object.Position.ToHavokVector(), + // Rotation = cBase->DrawObject.Object.Rotation.ToHavokQuaternion(), + // Scale = cBase->DrawObject.Object.Scale.ToHavokVector() + //}; + + return new hkQsTransformf() + { + Translation = cBase->Skeleton->Transform.Position.ToHavokVector(), + Rotation = cBase->Skeleton->Transform.Rotation.ToHavokQuaternion(), + Scale = cBase->Skeleton->Transform.Scale.ToHavokVector() + }; + } + + protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform) + { + //cBase->DrawObject.Object.Position = transform.Translation.ToClientVector3(); + //cBase->DrawObject.Object.Rotation = transform.Rotation.ToClientQuaternion(); + //cBase->DrawObject.Object.Scale = transform.Scale.ToClientVector3(); + + cBase->Skeleton->Transform = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() + { + Position = transform.Translation.ToClientVector3(), + Rotation = transform.Rotation.ToClientQuaternion(), + Scale = transform.Scale.ToClientVector3() + }; + } + } +} diff --git a/CustomizePlus/Data/Armature/PartialRootBone.cs b/CustomizePlus/Data/Armature/PartialRootBone.cs new file mode 100644 index 0000000..8bf4b75 --- /dev/null +++ b/CustomizePlus/Data/Armature/PartialRootBone.cs @@ -0,0 +1,26 @@ +// © Customize+. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.Havok; + +namespace CustomizePlus.Data.Armature +{ + internal unsafe class PartialRootBone : ModelBone + { + private ModelBone PrimaryPartialBone; + + public PartialRootBone(Armature arm, ModelBone primaryBone, string codeName, int partialIdx) : base(arm, codeName, partialIdx, 0) + { + PrimaryPartialBone = primaryBone; + } + + protected override BoneTransform CustomizedTransform { get => PrimaryPartialBone.GetTransformation(); } + } +} diff --git a/CustomizePlus/Data/BoneTransform.cs b/CustomizePlus/Data/BoneTransform.cs index 6a0cd19..84935ea 100644 --- a/CustomizePlus/Data/BoneTransform.cs +++ b/CustomizePlus/Data/BoneTransform.cs @@ -18,6 +18,12 @@ public enum BoneAttribute Scale = 2 } + public enum BoneUpdateMode + { + Position, Rotation, Scale, + //PositionIncludingScale, ScaleIncludingPosition + } + [Serializable] public class BoneTransform { @@ -38,13 +44,6 @@ public BoneTransform(BoneTransform original) : this() UpdateToMatch(original); } - public BoneTransform(hkQsTransformf original) : this() - { - Translation = original.Translation.GetAsNumericsVector(); - Rotation = original.Rotation.ToQuaternion().ToEulerAngles(); - Scaling = original.Scale.GetAsNumericsVector(); - } - private Vector3 _translation; public Vector3 Translation { diff --git a/CustomizePlus/Data/IBoneContainer.cs b/CustomizePlus/Data/IBoneContainer.cs new file mode 100644 index 0000000..5c5913e --- /dev/null +++ b/CustomizePlus/Data/IBoneContainer.cs @@ -0,0 +1,24 @@ +// © Customize+. +// Licensed under the MIT license. + +using System.Collections.Generic; + +namespace CustomizePlus.Data +{ + /// + /// Represents a container of editable bones. + /// + public interface IBoneContainer + { + /// + /// For each bone in the container, retrieve the selected attribute within the given posing space. + /// + public IEnumerable GetBoneTransformValues(BoneAttribute attribute, Armature.PosingSpace space); + + /// + /// Given updated transformation info for a given bone (for the specific attribute, in the given posing space), + /// update that bone's transformation values to reflect the updated info. + /// + public void UpdateBoneTransformValue(TransformInfo newValue, BoneUpdateMode mode, bool mirrorChanges, bool propagateChanges = false); + } +} diff --git a/CustomizePlus/Data/Profile/CharacterProfile.cs b/CustomizePlus/Data/Profile/CharacterProfile.cs index f916358..0c7f3de 100644 --- a/CustomizePlus/Data/Profile/CharacterProfile.cs +++ b/CustomizePlus/Data/Profile/CharacterProfile.cs @@ -2,7 +2,11 @@ // Licensed under the MIT license. using System; +using System.Linq; using System.Collections.Generic; + +using CustomizePlus.Data.Armature; + using Newtonsoft.Json; namespace CustomizePlus.Data.Profile @@ -12,7 +16,7 @@ namespace CustomizePlus.Data.Profile /// the information that gets saved to disk by the plugin. /// [Serializable] - public sealed class CharacterProfile + public sealed class CharacterProfile : IBoneContainer { [NonSerialized] private static int _nextGlobalId; @@ -93,5 +97,27 @@ public override int GetHashCode() { return UniqueId; } + + public IEnumerable GetBoneTransformValues(BoneAttribute attribute, PosingSpace space) + { + return Bones.Select(x => new TransformInfo(this, x.Key, x.Value, attribute, space)); + } + + public void UpdateBoneTransformValue(TransformInfo newTransform, BoneUpdateMode mode, bool mirrorChanges, bool propagateChanges = false) + { + if (!Bones.ContainsKey(newTransform.BoneCodeName)) + { + Bones[newTransform.BoneCodeName] = new BoneTransform(); + } + + BoneAttribute att = mode switch + { + BoneUpdateMode.Position => BoneAttribute.Position, + BoneUpdateMode.Rotation => BoneAttribute.Rotation, + _ => BoneAttribute.Scale + }; + + Bones[newTransform.BoneCodeName].UpdateAttribute(att, newTransform.TransformationValue); + } } } \ No newline at end of file diff --git a/CustomizePlus/Data/TransformInfo.cs b/CustomizePlus/Data/TransformInfo.cs new file mode 100644 index 0000000..e77459e --- /dev/null +++ b/CustomizePlus/Data/TransformInfo.cs @@ -0,0 +1,76 @@ +// © Customize+. +// Licensed under the MIT license. + +using CustomizePlus.Data.Armature; + +using FFXIVClientStructs.FFXIV.Common.Math; + +namespace CustomizePlus.Data +{ + /// + /// Represents a chunk of editable information about a bone. + /// + public class TransformInfo + { + /// + /// The container from which this transformation information was retrieved. + /// + private IBoneContainer _sourceContainer; + + public string BoneCodeName { get; } + public string BoneDisplayName => BoneData.GetBoneDisplayName(BoneCodeName); + + public Vector3 TransformationValue { get; set; } + public BoneAttribute Attribute { get; } + public PosingSpace ReferenceFrame { get; } + + private TransformInfo(IBoneContainer container, string codename, BoneAttribute att, PosingSpace ps) + { + _sourceContainer = container; + BoneCodeName = codename; + Attribute = att; + ReferenceFrame = ps; + } + + /// + /// Initializes a new instance of the class + /// by referencing values from a model bone. (i.e. instantiating from an armature). + /// + public TransformInfo(IBoneContainer container, ModelBone mb, BoneAttribute att, PosingSpace ps) + : this(container, mb.BoneName, att, ps) + { + BoneTransform bt = mb.GetTransformation(); + + TransformationValue = att switch + { + BoneAttribute.Position => bt.Translation, + BoneAttribute.Rotation => bt.Rotation, + _ => bt.Scaling + }; + } + + /// + /// Initializes a new instance of the class + /// using raw transformation values and a given codename. (i.e. instantiating from a plain CharacterProfile). + /// + public TransformInfo(IBoneContainer container, string codename, BoneTransform tr, BoneAttribute att, PosingSpace ps) + : this(container, codename, att, ps) + { + TransformationValue = att switch + { + BoneAttribute.Position => tr.Translation, + BoneAttribute.Rotation => tr.Rotation, + _ => tr.Scaling + }; + } + + /// + /// Push this transformation info back to its source container, updating it with any changes made + /// to the information since it was first retrieved. + /// + public void PushChanges(BoneUpdateMode mode, bool mirrorChanges, bool propagateChanges = false) + { + _sourceContainer.UpdateBoneTransformValue(this, mode, mirrorChanges, propagateChanges); + } + } +} diff --git a/CustomizePlus/Extensions/VectorExtensions.cs b/CustomizePlus/Extensions/VectorExtensions.cs index 70b8b60..e338629 100644 --- a/CustomizePlus/Extensions/VectorExtensions.cs +++ b/CustomizePlus/Extensions/VectorExtensions.cs @@ -13,16 +13,8 @@ internal static class VectorExtensions public static bool IsApproximately(this Vector3 vector, Vector3 other, float errorMargin = 0.001f) { return IsApproximately(vector.X, other.X, errorMargin) - && IsApproximately(vector.Y, other.Y, errorMargin) - && IsApproximately(vector.Z, other.Z, errorMargin); - } - - public static bool IsApproximately(this Vector4 vector, Vector4 other, float errorMargin = 0.001f) - { - return IsApproximately(vector.X, other.X, errorMargin) - && IsApproximately(vector.Y, other.Y, errorMargin) - && IsApproximately(vector.Z, other.Z, errorMargin) - && IsApproximately(vector.W, other.W, errorMargin); + && IsApproximately(vector.Y, other.Y, errorMargin) + && IsApproximately(vector.Z, other.Z, errorMargin); } private static bool IsApproximately(float a, float b, float errorMargin) @@ -31,14 +23,6 @@ private static bool IsApproximately(float a, float b, float errorMargin) return d < errorMargin; } - private static bool IsApproximately(this Quaternion quat, Quaternion other, float errorMargin = 0.001f) - { - return IsApproximately(quat.X, other.X, errorMargin) - && IsApproximately(quat.Y, other.Y, errorMargin) - && IsApproximately(quat.Z, other.Z, errorMargin) - && IsApproximately(quat.W, other.W, errorMargin); - } - public static Quaternion ToQuaternion(this Vector3 rotation) { return Quaternion.CreateFromYawPitchRoll( @@ -51,50 +35,29 @@ public static Vector3 ToEulerAngles(this Quaternion q) { return q.EulerAngles; } - } - } - } - } - } public static Quaternion ToClientQuaternion(this hkQuaternionf rotation) { - if (new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W) is Quaternion q - && q.IsApproximately(Quaternion.Identity)) - { - public static hkQuaternionf ToHavokQuaternion(this Quaternion rotation) return new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); } - - public static hkQuaternionf ToHavokRotation(this Quaternion rotation) + public static hkQuaternionf ToHavokQuaternion(this Quaternion rotation) { return new hkQuaternionf { - X = rot.X, - Y = rot.Y, - Z = rot.Z, - W = rot.W + X = rotation.X, + Y = rotation.Y, + Z = rotation.Z, + W = rotation.W }; } - public static hkQuaternionf ToHavokRotation(this FFXIVClientStructs.FFXIV.Common.Math.Quaternion rot) - { - return new hkQuaternionf - { - X = rot.X, - Y = rot.Y, public static Vector4 ToClientVector(this Quaternion quat) - W = rot.W + { return new Vector4(quat.X, quat.Y, quat.Z, quat.W); - X = translation.X, - Y = translation.Y, - public static hkVector4f ToHavokVector(this Vector3 vec) - W = 0.0f - }; } - public static hkVector4f ToHavokScaling(this Vector3 scaling) + public static hkVector4f ToHavokVector(this Vector3 vec) { return new hkVector4f { @@ -111,27 +74,22 @@ public static hkVector4f ToHavokVector(this Vector4 vec) { X = vec.X, Y = vec.Y, - public static Vector3 ToClientVector3(this PoseFile.Vector vec) + Z = vec.Z, W = vec.W }; } - public static Vector3 GetAsNumericsVector(this PoseFile.Vector vec) + public static Vector3 ToClientVector3(this PoseFile.Vector vec) { - Vector3 v = new Vector3(vec.X, vec.Y, vec.Z); + return new Vector3(vec.X, vec.Y, vec.Z); + } - if (v.IsApproximately(Vector3.Zero)) - { - return Vector3.Zero; - } - else if (v.IsApproximately(Vector3.One)) - { public static Vector3 ToClientVector3(this hkVector4f vec) { return new Vector3(vec.X, vec.Y, vec.Z); } - public static Vector4 GetAsNumericsVector(this hkVector4f vec) + public static Vector4 ToClientVector4(this hkVector4f vec) { return new Vector4(vec.X, vec.Y, vec.Z, vec.W); } @@ -141,11 +99,6 @@ public static Vector4 ToClientVector4(this Quaternion q) return new Vector4(q.X, q.Y, q.Z, q.W); } - public static Vector3 RemoveWTerm(this Vector4 vec) - { - return new Vector3(vec.X, vec.Y, vec.Z); - } - public static bool Equals(this hkVector4f first, hkVector4f second) { return first.X == second.X @@ -153,15 +106,5 @@ public static bool Equals(this hkVector4f first, hkVector4f second) && first.Z == second.Z && first.W == second.W; } - - public static FFXIVClientStructs.FFXIV.Common.Math.Vector3 HadamardMultiply(this FFXIVClientStructs.FFXIV.Common.Math.Vector3 orig, FFXIVClientStructs.FFXIV.Common.Math.Vector3 other) - { - return new FFXIVClientStructs.FFXIV.Common.Math.Vector3() - { - X = MathF.Max(MathF.Round(orig.X * other.X, 2), 0.01f), - Y = MathF.Max(MathF.Round(orig.Y * other.Y, 2), 0.01f), - Z = MathF.Max(MathF.Round(orig.Z * other.Z, 2), 0.01f) - }; - } } } \ No newline at end of file diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index d8da15f..7313bca 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -59,7 +59,7 @@ public static void Show(CharacterProfile prof) /// protected unsafe override void DrawContents() { - CharacterBase* targetObject = _settings.SkeletonInProgress.TryLinkSkeleton(); + CharacterBase* targetObject = _settings.ArmatureInProgress.TryLinkSkeleton(); if (targetObject == null && _settings.ShowLiveBones) { @@ -207,7 +207,7 @@ protected unsafe override void DrawContents() if (!_settings.ShowLiveBones || targetObject == null) ImGui.BeginDisabled(); if (ImGui.Button("Reload Bone Data")) { - _settings.SkeletonInProgress.RebuildSkeleton(targetObject); + _settings.ArmatureInProgress.RebuildSkeleton(targetObject); } CtrlHelper.AddHoverText("Refresh the skeleton data obtained from in-game"); if (!_settings.ShowLiveBones || targetObject == null) ImGui.EndDisabled(); @@ -268,42 +268,18 @@ protected unsafe override void DrawContents() ImGui.TableHeadersRow(); - if (_settings.SkeletonInProgress != null || _settings.ProfileInProgress != null) + if (_settings.ArmatureInProgress != null || _settings.ProfileInProgress != null) { - IEnumerable relevantModelBones = _settings.ShowLiveBones && _settings.SkeletonInProgress != null - ? _settings.SkeletonInProgress.GetAllBones().DistinctBy(x => x.BoneName).Select(x => new EditRowParams(x)) - : _settings.ProfileInProgress.Bones.Select(x => new EditRowParams(x.Key, x.Value)); + IBoneContainer container = _settings.ShowLiveBones && _settings.ArmatureInProgress != null + ? _settings.ArmatureInProgress + : _settings.ProfileInProgress; - var groupedBones = relevantModelBones.GroupBy(x => BoneData.GetBoneFamily(x.BoneCodeName)).ToList(); - - //TODO implement the method to pull out the bone parameters if not integrated naturally - //with the rest of the bones - - //IEnumerable mhGroup = _settings.ShowLiveBones && _targetArmature != null - // ? _targetArmature.GetMHBones().Select(x => new EditRowParams(x)) - // : _profileInProgress.Bones_MH.Select(x => new EditRowParams(x.Key, x.Value)); - - //if (mhGroup.GroupBy(x => BoneData.BoneFamily.MainHand).FirstOrDefault() is var mhg - // && mhg != null - // && mhg.Any()) - //{ - // groupedBones.Add(mhg); - //} - - //IEnumerable ohGroup = _settings.ShowLiveBones && _targetArmature != null - // ? _targetArmature.GetOHBones().Select(x => new EditRowParams(x)) - // : _profileInProgress.Bones_OH.Select(x => new EditRowParams(x.Key, x.Value)); - - //if (ohGroup.GroupBy(x => BoneData.BoneFamily.OffHand).FirstOrDefault() is var ohg - // && ohg != null - // && ohg.Any()) - //{ - // groupedBones.Add(ohg); - //} + var groupedBones = container.GetBoneTransformValues(_settings.EditingAttribute, _settings.ReferenceFrame) + .GroupBy(x => BoneData.GetBoneFamily(x.BoneCodeName)).ToList(); foreach (var boneGroup in groupedBones.OrderBy(x => (int)x.Key)) { - //Hide root bone if it's not enabled in settings + //Hide root bone group if it's not enabled in settings if (boneGroup.Key == BoneData.BoneFamily.Root && !Plugin.ConfigurationManager.Configuration.RootPositionEditingEnabled) continue; @@ -335,9 +311,9 @@ protected unsafe override void DrawContents() if (expanded) { - foreach (EditRowParams erp in boneGroup.OrderBy(x => BoneData.GetBoneRanking(x.BoneCodeName))) + foreach (TransformInfo trInfo in boneGroup.OrderBy(x => BoneData.GetBoneRanking(x.BoneCodeName))) { - CompleteBoneEditor(erp); + CompleteBoneEditor(trInfo); } } @@ -437,7 +413,7 @@ protected unsafe override void DrawContents() /// public unsafe void ConfirmSkeletonConnection() { - if (_settings.SkeletonInProgress == null || _settings.SkeletonInProgress.TryLinkSkeleton() == null) + if (_settings.ArmatureInProgress == null || _settings.ArmatureInProgress.TryLinkSkeleton() == null) { if (_settings.ShowLiveBones) { @@ -552,20 +528,14 @@ private bool SingleValueSlider(string label, ref float value) return false; } - private void CompleteBoneEditor(EditRowParams bone) + private void CompleteBoneEditor(TransformInfo trInfo) { - string codename = bone.BoneCodeName; - string displayName = bone.BoneDisplayName; - BoneTransform transform = new BoneTransform(bone.Transform); + string codename = trInfo.BoneCodeName; + string displayName = trInfo.BoneDisplayName; bool flagUpdate = false; - Vector3 newVector = _settings.EditingAttribute switch - { - BoneAttribute.Position => transform.Translation, - BoneAttribute.Rotation => transform.Rotation, - _ => transform.Scaling - }; + Vector3 newVector = trInfo.TransformationValue; ImGui.PushID(codename); @@ -615,33 +585,16 @@ private void CompleteBoneEditor(EditRowParams bone) { _dirty = true; - //var whichValue = FrameStackManager.Axis.X; - //if (originalVector.Y != newVector.Y) - //{ - // whichValue = FrameStackManager.Axis.Y; - //} + trInfo.TransformationValue = newVector; - //if (originalVector.Z != newVector.Z) - //{ - // whichValue = FrameStackManager.Axis.Z; - //} - - //_settings.EditStack.Do(codename, Settings.EditingAttribute, whichValue, originalVector, newVector); - - transform.UpdateAttribute(_settings.EditingAttribute, newVector); - - //if we have access to the armature, then use it to push the values through - //as the bone information allows us to propagate them to siblings and children - //otherwise access them through the profile directly - - if (_settings.ShowLiveBones) - { - bone.Basis.UpdateModel(transform, _settings.MirrorModeEnabled, _settings.ParentingEnabled); - } - else + BoneUpdateMode mode = _settings.EditingAttribute switch { - _settings.ProfileInProgress.Bones[codename].UpdateToMatch(transform); - } + BoneAttribute.Position => BoneUpdateMode.Position, + BoneAttribute.Rotation => BoneUpdateMode.Rotation, + _ => BoneUpdateMode.Scale + }; + + trInfo.PushChanges(mode, _settings.MirrorModeEnabled, _settings.ParentingEnabled); } } @@ -654,12 +607,13 @@ public struct EditorSessionSettings private string? _originalCharName; private string? _originalProfName; - public Armature SkeletonInProgress => ProfileInProgress.Armature; + public Armature ArmatureInProgress => ProfileInProgress.Armature; public bool ShowLiveBones = false; public bool MirrorModeEnabled = false; public bool ParentingEnabled = false; public BoneAttribute EditingAttribute = BoneAttribute.Scale; + public PosingSpace ReferenceFrame = PosingSpace.Self; public Dictionary GroupExpandedState = new(); @@ -684,34 +638,4 @@ public EditorSessionSettings(CharacterProfile prof) ShowLiveBones = true; } } - - /// - /// Simple structure for representing arguments to the editor table. - /// Can be constructed with or without access to a live armature. - /// - internal struct EditRowParams - { - public string BoneCodeName; - public string BoneDisplayName => BoneData.GetBoneDisplayName(BoneCodeName); - public BoneTransform Transform; - public ModelBone Basis; - - public float CachedX; - public float CachedY; - public float CachedZ; - - public EditRowParams(ModelBone mb) - { - BoneCodeName = mb.BoneName; - Transform = mb.CustomizedTransform; - Basis = mb; - } - - public EditRowParams(string codename, BoneTransform tr) - { - BoneCodeName = codename; - Transform = tr; - Basis = null; - } - } } \ No newline at end of file diff --git a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs index 7e37470..1f55104 100644 --- a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs +++ b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs @@ -23,7 +23,7 @@ internal unsafe class BoneMonitorWindow : WindowBase { private readonly Dictionary _groupExpandedState = new(); private readonly bool _modelFrozen = false; - private ModelBone.PosingSpace _targetPose; + private PosingSpace _targetPose; private bool _aggregateDeforms; private BoneAttribute _targetAttribute; @@ -103,12 +103,12 @@ protected override void DrawContents() ImGui.Spacing(); ImGui.SameLine(); - if (ImGui.RadioButton("Local", _targetPose == ModelBone.PosingSpace.Self)) - _targetPose = ModelBone.PosingSpace.Self; + if (ImGui.RadioButton("Local", _targetPose == PosingSpace.Self)) + _targetPose = PosingSpace.Self; ImGui.SameLine(); - if (ImGui.RadioButton("Model", _targetPose == ModelBone.PosingSpace.Parent)) - _targetPose = ModelBone.PosingSpace.Parent; + if (ImGui.RadioButton("Model", _targetPose == PosingSpace.Parent)) + _targetPose = PosingSpace.Parent; //ImGui.SameLine(); //if (ImGui.RadioButton("Reference", _targetPose == ModelBone.PoseType.Reference)) @@ -220,7 +220,7 @@ private bool MysteryButton(string codename, ref Vector4 value) private void RenderTransformationInfo(ModelBone bone, CharacterBase* cBase) { - if (bone.GetGameTransform(cBase, _targetPose) is FFXIVClientStructs.Havok.hkQsTransformf deform) + if (bone.GetGameTransform(cBase) is FFXIVClientStructs.Havok.hkQsTransformf deform) { var displayName = bone.ToString(); From 054dac001ee72c7ff8ef85e476dd0e01e2f40d6b Mon Sep 17 00:00:00 2001 From: Dendroid Date: Fri, 23 Jun 2023 03:23:50 -0400 Subject: [PATCH 17/37] Switch root bone back to using object properties. --- CustomizePlus/Data/Armature/ModelRootBone.cs | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/CustomizePlus/Data/Armature/ModelRootBone.cs b/CustomizePlus/Data/Armature/ModelRootBone.cs index b5bbbfc..76bfc96 100644 --- a/CustomizePlus/Data/Armature/ModelRootBone.cs +++ b/CustomizePlus/Data/Armature/ModelRootBone.cs @@ -17,33 +17,33 @@ public ModelRootBone(Armature arm, string codeName) : base(arm, codeName, 0, 0) public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase) { - //return new hkQsTransformf() - //{ - // Translation = cBase->DrawObject.Object.Position.ToHavokVector(), - // Rotation = cBase->DrawObject.Object.Rotation.ToHavokQuaternion(), - // Scale = cBase->DrawObject.Object.Scale.ToHavokVector() - //}; - return new hkQsTransformf() { - Translation = cBase->Skeleton->Transform.Position.ToHavokVector(), - Rotation = cBase->Skeleton->Transform.Rotation.ToHavokQuaternion(), - Scale = cBase->Skeleton->Transform.Scale.ToHavokVector() + Translation = cBase->DrawObject.Object.Position.ToHavokVector(), + Rotation = cBase->DrawObject.Object.Rotation.ToHavokQuaternion(), + Scale = cBase->DrawObject.Object.Scale.ToHavokVector() }; + + //return new hkQsTransformf() + //{ + // Translation = cBase->Skeleton->Transform.Position.ToHavokVector(), + // Rotation = cBase->Skeleton->Transform.Rotation.ToHavokQuaternion(), + // Scale = cBase->Skeleton->Transform.Scale.ToHavokVector() + //}; } protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform) { - //cBase->DrawObject.Object.Position = transform.Translation.ToClientVector3(); - //cBase->DrawObject.Object.Rotation = transform.Rotation.ToClientQuaternion(); - //cBase->DrawObject.Object.Scale = transform.Scale.ToClientVector3(); + cBase->DrawObject.Object.Position = transform.Translation.ToClientVector3(); + cBase->DrawObject.Object.Rotation = transform.Rotation.ToClientQuaternion(); + cBase->DrawObject.Object.Scale = transform.Scale.ToClientVector3(); - cBase->Skeleton->Transform = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() - { - Position = transform.Translation.ToClientVector3(), - Rotation = transform.Rotation.ToClientQuaternion(), - Scale = transform.Scale.ToClientVector3() - }; + //cBase->Skeleton->Transform = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() + //{ + // Position = transform.Translation.ToClientVector3(), + // Rotation = transform.Rotation.ToClientQuaternion(), + // Scale = transform.Scale.ToClientVector3() + //}; } } } From 754323f0586cad0965da1851e3142ceb3d8040b6 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Fri, 23 Jun 2023 03:26:06 -0400 Subject: [PATCH 18/37] Amend some function names that were bugging me. --- CustomizePlus/Data/Armature/Armature.cs | 2 +- CustomizePlus/Data/Armature/ModelBone.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index a727258..645585d 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -427,7 +427,7 @@ public void ApplyRootTranslation(CharacterBase* cBase) { if (cBase != null && _partialSkeletons.Any() && _partialSkeletons.First().Any()) { - _partialSkeletons[0][0].ApplyStraightModelTranslation(cBase); + _partialSkeletons[0][0].ApplyModelTranslationAsIs(cBase); } } diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 91df950..6b338fb 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -247,8 +247,8 @@ public virtual void ApplyModelTransform(CharacterBase* cBase) public void ApplyModelScale(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingScale); public void ApplyModelRotation(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingRotation); - public void ApplyModelFullTranslation(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslationWithRotation); - public void ApplyStraightModelTranslation(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslation); + public void ApplyModelTranslationAtAngle(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslationWithRotation); + public void ApplyModelTranslationAsIs(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslation); private void ApplyTransFunc(CharacterBase* cBase, Func modTrans) { From b9e385689491cd1a58ba03cd7b2e6b694b19883c Mon Sep 17 00:00:00 2001 From: Dendroid Date: Fri, 23 Jun 2023 04:14:50 -0400 Subject: [PATCH 19/37] Effect the important parts of PR #139 Co-authored-by: chirp --- CustomizePlus/Data/Armature/Armature.cs | 16 ++++++++++++---- CustomizePlus/Helpers/GameStateHelper.cs | 11 +++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 645585d..835366f 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -395,7 +395,9 @@ public unsafe void ApplyTransformation(GameObject obj) { hkaPose* currentPose = cBase->Skeleton->PartialSkeletons[pSkeleIndex].GetHavokPose(Constants.TruePoseIndex); - if (currentPose != null) + //the right side of this condition is apparently a havok flag incidating + //whether the model has been updated since the last frame + if (currentPose != null && currentPose->ModelInSync != 0) { //if (SnapToReferencePose) //{ @@ -408,13 +410,19 @@ public unsafe void ApplyTransformation(GameObject obj) && mb != null && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String) { - if (GameStateHelper.GameInPosingMode()) + if (mb != MainRootBone || obj.HasScalableRoot()) { mb.ApplyModelScale(cBase); } - else + + if (!GameStateHelper.GameInPosingModeWithFrozenRotation()) + { + mb.ApplyModelRotation(cBase); + } + + if (!GameStateHelper.GameInPosingModeWithFrozenPosition()) { - mb.ApplyModelTransform(cBase); + mb.ApplyModelTranslationAtAngle(cBase); } } } diff --git a/CustomizePlus/Helpers/GameStateHelper.cs b/CustomizePlus/Helpers/GameStateHelper.cs index b951472..004bf4d 100644 --- a/CustomizePlus/Helpers/GameStateHelper.cs +++ b/CustomizePlus/Helpers/GameStateHelper.cs @@ -14,5 +14,16 @@ public static bool GameInPosingMode() || Services.PosingModeDetectService.Instance.IsInPosingMode; } + public static bool GameInPosingModeWithFrozenRotation() + { + return Services.GPoseService.Instance.GPoseState == Services.GPoseState.Inside + || Services.PosingModeDetectService.IsAnamnesisRotationFrozen; + } + + public static bool GameInPosingModeWithFrozenPosition() + { + return Services.GPoseService.Instance.GPoseState == Services.GPoseState.Inside + || Services.PosingModeDetectService.IsAnamnesisPositionFrozen; + } } } From 0e9cbc1783c043cb011909660ec3bd957bd95f1f Mon Sep 17 00:00:00 2001 From: Dendroid Date: Fri, 23 Jun 2023 05:10:11 -0400 Subject: [PATCH 20/37] Centralize profile serialization into a single function, create a contract resolver to keep the serialized information from blowing up with redundant data from the ffxivclientstruct vector3 class --- CustomizePlus/Api/CustomizePlusIpc.cs | 2 +- CustomizePlus/Api/VectorContractResolver.cs | 30 +++++++++++++++++++ .../Data/Profile/CharacterProfile.cs | 9 ++++++ .../Data/Profile/ProfileReaderWriter.cs | 2 +- 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 CustomizePlus/Api/VectorContractResolver.cs diff --git a/CustomizePlus/Api/CustomizePlusIpc.cs b/CustomizePlus/Api/CustomizePlusIpc.cs index 5caafda..f74985f 100644 --- a/CustomizePlus/Api/CustomizePlusIpc.cs +++ b/CustomizePlus/Api/CustomizePlusIpc.cs @@ -226,7 +226,7 @@ public void OnLocalPlayerProfileUpdate() CharacterProfile? profile = Plugin.ProfileManager.GetProfileByCharacterName(name, true); PluginLog.Debug($"Sending local player update message: {profile?.ProfileName ?? "no profile"} - {profile?.CharacterName ?? "no profile"}"); - ProviderOnLocalPlayerProfileUpdate?.SendMessage(profile != null ? JsonConvert.SerializeObject(profile) : null); + ProviderOnLocalPlayerProfileUpdate?.SendMessage(profile != null ? profile.SerializeToJSON() : null); } } diff --git a/CustomizePlus/Api/VectorContractResolver.cs b/CustomizePlus/Api/VectorContractResolver.cs new file mode 100644 index 0000000..818e60d --- /dev/null +++ b/CustomizePlus/Api/VectorContractResolver.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace CustomizePlus.Api +{ + public class VectorContractResolver : DefaultContractResolver + { + public static VectorContractResolver Instance { get; } = new VectorContractResolver(); + + protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization) + { + JsonProperty property = base.CreateProperty(member, memberSerialization); + if (typeof(FFXIVClientStructs.FFXIV.Common.Math.Vector3).IsAssignableFrom(member.DeclaringType) + && member.Name != nameof(FFXIVClientStructs.FFXIV.Common.Math.Vector3.X) + && member.Name != nameof(FFXIVClientStructs.FFXIV.Common.Math.Vector3.Y) + && member.Name != nameof(FFXIVClientStructs.FFXIV.Common.Math.Vector3.Z)) + { + property.Ignored = true; + } + return property; + } + } +} diff --git a/CustomizePlus/Data/Profile/CharacterProfile.cs b/CustomizePlus/Data/Profile/CharacterProfile.cs index 0c7f3de..1649a25 100644 --- a/CustomizePlus/Data/Profile/CharacterProfile.cs +++ b/CustomizePlus/Data/Profile/CharacterProfile.cs @@ -119,5 +119,14 @@ public void UpdateBoneTransformValue(TransformInfo newTransform, BoneUpdateMode Bones[newTransform.BoneCodeName].UpdateAttribute(att, newTransform.TransformationValue); } + + public string SerializeToJSON() + { + return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + ContractResolver = Api.VectorContractResolver.Instance + }); + } } } \ No newline at end of file diff --git a/CustomizePlus/Data/Profile/ProfileReaderWriter.cs b/CustomizePlus/Data/Profile/ProfileReaderWriter.cs index 0903d78..66e8f67 100644 --- a/CustomizePlus/Data/Profile/ProfileReaderWriter.cs +++ b/CustomizePlus/Data/Profile/ProfileReaderWriter.cs @@ -45,7 +45,7 @@ public static void SaveProfile(CharacterProfile prof, bool archival = false) if (!archival) { - var json = JsonConvert.SerializeObject(prof, Formatting.Indented); + string json = prof.SerializeToJSON(); File.WriteAllText(newFilePath, json); } From 292db0bb0d32184995bc73ea82151530881d1c3b Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 24 Jun 2023 03:37:12 -0400 Subject: [PATCH 21/37] More tweaks to the armature system for root bones --- CustomizePlus/Data/Armature/Armature.cs | 29 +++---- CustomizePlus/Data/Armature/ModelBone.cs | 48 +++++------- CustomizePlus/Data/Armature/ModelRootBone.cs | 76 ++++++++++++++----- .../Data/Armature/PartialRootBone.cs | 10 +++ CustomizePlus/Extensions/VectorExtensions.cs | 8 ++ 5 files changed, 102 insertions(+), 69 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 835366f..0f5ae85 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -51,8 +51,6 @@ public unsafe class Armature : IBoneContainer /// The root bone of a partial skeleton may also be a regular bone in a different partial skeleton. /// private ModelBone[][] _partialSkeletons; - private ModelBone[][] _weaponPartialsRight; - private ModelBone[][] _weaponPartialsLeft; #region Bone Accessors ------------------------------------------------------------------------------- @@ -206,20 +204,20 @@ private void SetReferenceSnap(bool value) if (GameDataHelper.TryLookupCharacterBase(Profile.CharacterName, out CharacterBase* cBase) && cBase != null) { - if (!Built || forceRebuild) + if (!Built || forceRebuild || NewBonesAvailable(cBase)) { RebuildSkeleton(cBase); } - else if (NewBonesAvailable(cBase)) - { - AugmentSkeleton(cBase); - } + //else if (NewBonesAvailable(cBase)) + //{ + // AugmentSkeleton(cBase); + //} return cBase; } } - catch + catch (Exception ex) { - PluginLog.LogError($"Error occured while attempting to link skeleton: {this}"); + PluginLog.LogError($"Error occured while attempting to link skeleton '{this}': {ex}"); } return null; @@ -327,7 +325,7 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C } else if (boneIndex == 0) { - ModelBone cloneOf = newPartials[0][currentPartial.ConnectedBoneIndex]; + ModelBone cloneOf = newPartials[0][currentPartial.ConnectedParentBoneIndex]; newBone = new PartialRootBone(arm, cloneOf, boneName, pSkeleIndex); } else @@ -397,7 +395,7 @@ public unsafe void ApplyTransformation(GameObject obj) //the right side of this condition is apparently a havok flag incidating //whether the model has been updated since the last frame - if (currentPose != null && currentPose->ModelInSync != 0) + if (currentPose != null) { //if (SnapToReferencePose) //{ @@ -410,7 +408,7 @@ public unsafe void ApplyTransformation(GameObject obj) && mb != null && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String) { - if (mb != MainRootBone || obj.HasScalableRoot()) + if (obj.HasScalableRoot()) { mb.ApplyModelScale(cBase); } @@ -431,13 +429,6 @@ public unsafe void ApplyTransformation(GameObject obj) } } - public void ApplyRootTranslation(CharacterBase* cBase) - { - if (cBase != null && _partialSkeletons.Any() && _partialSkeletons.First().Any()) - { - _partialSkeletons[0][0].ApplyModelTranslationAsIs(cBase); - } - } private static bool AreTwinnedNames(string name1, string name2) { diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 6b338fb..ece8016 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -34,7 +34,8 @@ public unsafe class ModelBone /// Gets the model bone corresponding to this model bone's parent, if it exists. /// (It should in all cases but the root of the skeleton) /// - public ModelBone? ParentBone => (_parentPartialIndex >= 0 && _parentBoneIndex >= 0) + public ModelBone? ParentBone => (_parentPartialIndex >= 0 && _parentPartialIndex < MasterArmature.PartialSkeletonCount + && _parentBoneIndex >= 0 && _parentBoneIndex < MasterArmature.GetBoneCountOfPartial(_parentPartialIndex)) ? MasterArmature[_parentPartialIndex, _parentBoneIndex] : null; private int _parentPartialIndex = -1; @@ -115,7 +116,7 @@ public void AddTwin(int twinPartialIdx, int twinBoneIdx) #endregion - private void UpdateTransformation(BoneTransform newTransform) + protected virtual void UpdateTransformation(BoneTransform newTransform) { //update the transform locally CustomizedTransform.UpdateToMatch(newTransform); @@ -144,7 +145,7 @@ public override string ToString() /// Update the transformation associated with this model bone. Optionally extend the transformation /// to the model bone's twin (in which case it will be appropriately mirrored) and/or children. /// - public void UpdateModel(BoneTransform newTransform, bool mirror = false, bool propagate = false) + public virtual void UpdateModel(BoneTransform newTransform, bool mirror = false, bool propagate = false, bool extendToClones = true) { if (mirror && TwinBone is ModelBone mb && mb != null) { @@ -169,12 +170,16 @@ public void UpdateModel(BoneTransform newTransform, bool mirror = false, bool pr UpdateTransformation(newTransform); - IEnumerable clones = MasterArmature.GetAllBones() - .Where(x => x.BoneName == BoneName && !ReferenceEquals(x, this)); - - foreach (ModelBone clone in clones) + if (extendToClones) { - clone.UpdateModel(newTransform, mirror, propagate); + IEnumerable clones = MasterArmature.GetAllBones() + .Where(x => x is not ModelRootBone) + .Where(x => x.BoneName == BoneName && !ReferenceEquals(x, this)); + + foreach (ModelBone clone in clones) + { + clone.UpdateModel(newTransform, mirror, propagate, false); + } } } @@ -206,7 +211,7 @@ public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase) if (targetPose == null) return Constants.NullTransform; - return targetPose->GetSyncedPoseModelSpace()->Data[BoneIndex]; + return targetPose->ModelPose.Data[BoneIndex]; } /// @@ -220,37 +225,18 @@ protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf tra hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); //hkaPose* targetPose = cBase->Skeleton->PartialSkeletons[PartialSkeletonIndex].GetHavokPose(Constants.TruePoseIndex); - if (targetPose == null) return; + if (targetPose == null || targetPose->ModelInSync == 0) return; - targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex] = transform; - targetPose->BoneFlags[BoneIndex] = 1; + targetPose->ModelPose.Data[BoneIndex] = transform; } - /// - /// Apply this model bone's associated transformation to its in-game sibling within - /// the skeleton of the given character base. - /// - public virtual void ApplyModelTransform(CharacterBase* cBase) - { - if (cBase != null - && CustomizedTransform.IsEdited() - && GetGameTransform(cBase) is hkQsTransformf gameTransform - && !gameTransform.Equals(Constants.NullTransform)) - { - if (CustomizedTransform.ModifyExistingTransform(gameTransform) is hkQsTransformf modTransform - && !modTransform.Equals(Constants.NullTransform)) - { - SetGameTransform(cBase, modTransform); - } - } - } public void ApplyModelScale(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingScale); public void ApplyModelRotation(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingRotation); public void ApplyModelTranslationAtAngle(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslationWithRotation); public void ApplyModelTranslationAsIs(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslation); - private void ApplyTransFunc(CharacterBase* cBase, Func modTrans) + protected virtual void ApplyTransFunc(CharacterBase* cBase, Func modTrans) { if (cBase != null && CustomizedTransform.IsEdited() diff --git a/CustomizePlus/Data/Armature/ModelRootBone.cs b/CustomizePlus/Data/Armature/ModelRootBone.cs index 76bfc96..14594b7 100644 --- a/CustomizePlus/Data/Armature/ModelRootBone.cs +++ b/CustomizePlus/Data/Armature/ModelRootBone.cs @@ -1,8 +1,10 @@ // © Customize+. // Licensed under the MIT license. +using System; using CustomizePlus.Extensions; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Common.Math; using FFXIVClientStructs.Havok; namespace CustomizePlus.Data.Armature @@ -13,37 +15,73 @@ namespace CustomizePlus.Data.Armature /// internal unsafe class ModelRootBone : ModelBone { - public ModelRootBone(Armature arm, string codeName) : base(arm, codeName, 0, 0) { } + //private Vector3 _cachedGamePosition = Vector3.Zero; + //private Quaternion _cachedGameRotation = Quaternion.Identity; + //private Vector3 _cachedGameScale = Vector3.One; - public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase) + //private Vector3 _moddedPosition; + //private Quaternion _moddedRotation; + //private Vector3 _moddedScale; + + public ModelRootBone(Armature arm, string codeName) : base(arm, codeName, 0, 0) { - return new hkQsTransformf() - { - Translation = cBase->DrawObject.Object.Position.ToHavokVector(), - Rotation = cBase->DrawObject.Object.Rotation.ToHavokQuaternion(), - Scale = cBase->DrawObject.Object.Scale.ToHavokVector() - }; + //_moddedPosition = _cachedGamePosition; + //_moddedRotation = _cachedGameRotation; + //_moddedScale = _cachedGameScale; + } + public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase) + { //return new hkQsTransformf() //{ - // Translation = cBase->Skeleton->Transform.Position.ToHavokVector(), - // Rotation = cBase->Skeleton->Transform.Rotation.ToHavokQuaternion(), - // Scale = cBase->Skeleton->Transform.Scale.ToHavokVector() + // Translation = objPosition.ToHavokVector(), + // Rotation = objRotation.ToHavokQuaternion(), + // Scale = objScale.ToHavokVector() //}; + + return new hkQsTransformf() + { + Translation = cBase->Skeleton->Transform.Position.ToHavokVector(), + Rotation = cBase->Skeleton->Transform.Rotation.ToHavokQuaternion(), + Scale = cBase->Skeleton->Transform.Scale.ToHavokVector() + }; } protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform) { - cBase->DrawObject.Object.Position = transform.Translation.ToClientVector3(); - cBase->DrawObject.Object.Rotation = transform.Rotation.ToClientQuaternion(); - cBase->DrawObject.Object.Scale = transform.Scale.ToClientVector3(); + //if (_moddedPosition != transform.Translation.ToClientVector3()) + //{ + // _moddedPosition = transform.Translation.ToClientVector3(); + // cBase->DrawObject.Object.Position = _moddedPosition; + //} + + //cBase->DrawObject.Object.Position = transform.Translation.ToClientVector3(); + //cBase->DrawObject.Object.Rotation = transform.Rotation.ToClientQuaternion(); + //cBase->DrawObject.Object.Scale = transform.Scale.ToClientVector3(); + + FFXIVClientStructs.FFXIV.Client.Graphics.Transform tr = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() + { + Position = transform.Translation.ToClientVector3(), + Rotation = transform.Rotation.ToClientQuaternion(), + Scale = transform.Scale.ToClientVector3() + }; + + cBase->Skeleton->Transform = tr; - //cBase->Skeleton->Transform = new FFXIVClientStructs.FFXIV.Client.Graphics.Transform() + //CharacterBase* child1 = (CharacterBase*)cBase->DrawObject.Object.ChildObject; + //if (child1 != null && child1->GetModelType() == CharacterBase.ModelType.Weapon) //{ - // Position = transform.Translation.ToClientVector3(), - // Rotation = transform.Rotation.ToClientQuaternion(), - // Scale = transform.Scale.ToClientVector3() - //}; + // child1->Skeleton->Transform = tr; + + // CharacterBase* child2 = (CharacterBase*)child1->DrawObject.Object.NextSiblingObject; + // if (child2 != child1 && child2 != null && child2->GetModelType() == CharacterBase.ModelType.Weapon) + // { + // child2->Skeleton->Transform = tr; + // } + //} + + //? + //cBase->VfxScale = MathF.Max(MathF.Max(transform.Scale.X, transform.Scale.Y), transform.Scale.Z); } } } diff --git a/CustomizePlus/Data/Armature/PartialRootBone.cs b/CustomizePlus/Data/Armature/PartialRootBone.cs index 8bf4b75..a6508fd 100644 --- a/CustomizePlus/Data/Armature/PartialRootBone.cs +++ b/CustomizePlus/Data/Armature/PartialRootBone.cs @@ -19,6 +19,16 @@ internal unsafe class PartialRootBone : ModelBone public PartialRootBone(Armature arm, ModelBone primaryBone, string codeName, int partialIdx) : base(arm, codeName, partialIdx, 0) { PrimaryPartialBone = primaryBone; + + if (PrimaryPartialBone.ParentBone is ModelBone pBone && pBone != null) + { + AddParent(pBone.PartialSkeletonIndex, pBone.BoneIndex); + } + + //if (PrimaryPartialBone.TwinBone is ModelBone tBone && tBone != null) + //{ + // AddTwin(tBone.PartialSkeletonIndex, tBone.BoneIndex); + //} } protected override BoneTransform CustomizedTransform { get => PrimaryPartialBone.GetTransformation(); } diff --git a/CustomizePlus/Extensions/VectorExtensions.cs b/CustomizePlus/Extensions/VectorExtensions.cs index e338629..fb56712 100644 --- a/CustomizePlus/Extensions/VectorExtensions.cs +++ b/CustomizePlus/Extensions/VectorExtensions.cs @@ -17,6 +17,14 @@ public static bool IsApproximately(this Vector3 vector, Vector3 other, float err && IsApproximately(vector.Z, other.Z, errorMargin); } + public static bool IsApproximately(this Quaternion quat, Quaternion other, float errorMargin =0.001f) + { + return IsApproximately(quat.X, other.X, errorMargin) + && IsApproximately(quat.Y, other.Y, errorMargin) + && IsApproximately(quat.Z, other.Z, errorMargin) + && IsApproximately(quat.W, other.W, errorMargin); + } + private static bool IsApproximately(float a, float b, float errorMargin) { var d = MathF.Abs(a - b); From b71d679aa88c87344cd79eeaf0cecf75898db479 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 24 Jun 2023 03:38:13 -0400 Subject: [PATCH 22/37] Shuffle around and adjust some parts of the editing lifecycle to avoid null references --- CustomizePlus/Data/Profile/ProfileManager.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CustomizePlus/Data/Profile/ProfileManager.cs b/CustomizePlus/Data/Profile/ProfileManager.cs index e8c6723..088caa8 100644 --- a/CustomizePlus/Data/Profile/ProfileManager.cs +++ b/CustomizePlus/Data/Profile/ProfileManager.cs @@ -92,10 +92,14 @@ public void CheckForNewProfiles() /// saves it to disk. If the profile already exists (and it is not forced to be new) /// the given profile will overwrite the old one. /// - public void AddAndSaveProfile(CharacterProfile prof, bool forceNew = false) + public unsafe void AddAndSaveProfile(CharacterProfile prof, bool forceNew = false) { PruneIdempotentTransforms(prof); - prof.Armature = null; + if (prof.Armature != null + && prof.Armature.TryLinkSkeleton() == null) + { + prof.Armature = null; + } //if the profile is already in the list, simply replace it if (!forceNew && Profiles.Remove(prof)) @@ -206,14 +210,14 @@ public void SaveWorkingCopy(bool editingComplete = false) AddAndSaveProfile(ProfileOpenInEditor); + //Send OnProfileUpdate if this is profile of the current player and it's enabled + if (ProfileOpenInEditor.CharacterName == GameDataHelper.GetPlayerName() && ProfileOpenInEditor.Enabled) + Plugin.IPCManager.OnLocalPlayerProfileUpdate(); + if (editingComplete) { StopEditing(); } - - //Send OnProfileUpdate if this is profile of the current player and it's enabled - if (ProfileOpenInEditor.CharacterName == GameDataHelper.GetPlayerName() && ProfileOpenInEditor.Enabled) - Plugin.IPCManager.OnLocalPlayerProfileUpdate(); } } From bbd6a347c8e4d1c7fbb0d9838fc7496bcc52ed91 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 24 Jun 2023 03:38:51 -0400 Subject: [PATCH 23/37] Add a null guard and fix a misnamed table. --- CustomizePlus/UI/Windows/BoneEditWindow.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index 7313bca..d049e28 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -59,7 +59,12 @@ public static void Show(CharacterProfile prof) /// protected unsafe override void DrawContents() { - CharacterBase* targetObject = _settings.ArmatureInProgress.TryLinkSkeleton(); + CharacterBase* targetObject = null; + + if (_settings.ArmatureInProgress != null) + { + targetObject = _settings.ArmatureInProgress.TryLinkSkeleton(); + } if (targetObject == null && _settings.ShowLiveBones) { @@ -72,7 +77,7 @@ protected unsafe override void DrawContents() _dirty = true; } - if (ImGui.BeginTable("##Save/Close", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoClip)) + if (ImGui.BeginTable("##ProfileSettings", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoClip)) { ImGui.TableSetupColumn("##CharName", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("##ProfName", ImGuiTableColumnFlags.WidthStretch); From 437a7db5fa14e3da3d8e9f2be60289c78ca7f1e9 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 24 Jun 2023 03:40:01 -0400 Subject: [PATCH 24/37] Remove most of the game movement hook since it's unneeded now. --- CustomizePlus/Plugin.cs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/CustomizePlus/Plugin.cs b/CustomizePlus/Plugin.cs index bbf6e18..2861da3 100644 --- a/CustomizePlus/Plugin.cs +++ b/CustomizePlus/Plugin.cs @@ -250,36 +250,12 @@ private static IntPtr OnRender(IntPtr a1, long a2, int a3, int a4) } //todo: doesn't work in cutscenes, something getting called after this and resets changes + //TODO: fully remove, later? private unsafe static void OnGameObjectMove(IntPtr gameObjectPtr) { // Call the original function. _gameObjectMovementHook.Original(gameObjectPtr); - ////If GPose and a 3rd-party posing service are active simultneously, abort - //if (GameStateHelper.GameInPosingMode()) - //{ - // return; - //} - - //if (DalamudServices.ObjectTable.CreateObjectReference(gameObjectPtr) is var obj - // && obj != null - // && ProfileManager.GetProfilesByGameObject(obj) .FirstOrDefault(x => x.Enabled) is CharacterProfile prof - // && prof != null - // && prof.Armature != null) - //{ - // prof.Armature.ApplyRootTranslation(obj.ToCharacterBase()); - - // //var objIndex = obj.ObjectIndex; - - // //var isForbiddenFiller = objIndex == Constants.ObjectTableFillerIndex; - // //var isForbiddenCutsceneNPC = Constants.IsInObjectTableCutsceneNPCRange(objIndex) - // // || !ConfigurationManager.Configuration.ApplyToNPCsInCutscenes; - - // //if (!isForbiddenFiller && !isForbiddenCutsceneNPC) - // //{ - // // ArmatureManager.RenderArmatureByObject(obj); - // //} - //} } } } \ No newline at end of file From 11d0239f78ded7d47a8ecaf99d076e01330622a2 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Sat, 24 Jun 2023 04:01:25 -0400 Subject: [PATCH 25/37] Fix a bug where the editor window wasn't properly reverting changes. --- CustomizePlus/Data/Armature/ArmatureManager.cs | 9 ++++++++- CustomizePlus/UI/Windows/BoneEditWindow.cs | 5 +++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CustomizePlus/Data/Armature/ArmatureManager.cs b/CustomizePlus/Data/Armature/ArmatureManager.cs index 19a4258..5e70b13 100644 --- a/CustomizePlus/Data/Armature/ArmatureManager.cs +++ b/CustomizePlus/Data/Armature/ArmatureManager.cs @@ -32,8 +32,15 @@ public void RenderCharacterProfiles(params CharacterProfile[] profiles) } } - public void ConstructArmatureForProfile(CharacterProfile newProfile) + public void ConstructArmatureForProfile(CharacterProfile newProfile, bool forceNew = false) { + if (forceNew + && _armatures.FirstOrDefault(x => x.Profile == newProfile) is Armature arm + && arm != null) + { + _armatures.Remove(arm); + } + if (!_armatures.Any(x => x.Profile == newProfile)) { var newArm = new Armature(newProfile); diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index d049e28..2e221f6 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -378,6 +378,7 @@ protected unsafe override void DrawContents() () => { Plugin.ProfileManager.RevertWorkingCopy(); + Plugin.ArmatureManager.ConstructArmatureForProfile(_settings.ProfileInProgress, true); _dirty = false; }); } @@ -608,7 +609,7 @@ private void CompleteBoneEditor(TransformInfo trInfo) public struct EditorSessionSettings { - public readonly CharacterProfile ProfileInProgress; + public CharacterProfile ProfileInProgress { get; private set; } private string? _originalCharName; private string? _originalProfName; @@ -640,7 +641,7 @@ public EditorSessionSettings(CharacterProfile prof) _originalProfName = prof.ProfileName; ProfileInProgress.Enabled = true; - ShowLiveBones = true; + ShowLiveBones = ProfileInProgress.Enabled; } } } \ No newline at end of file From d3f436b1f76b74d3b1fe8ba70c5ac22208126a74 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Tue, 27 Jun 2023 03:57:43 -0400 Subject: [PATCH 26/37] Add bone transform paramters for parented edits. --- CustomizePlus/Data/BoneTransform.cs | 189 +++++++++++++++++++--------- 1 file changed, 129 insertions(+), 60 deletions(-) diff --git a/CustomizePlus/Data/BoneTransform.cs b/CustomizePlus/Data/BoneTransform.cs index 84935ea..248e12b 100644 --- a/CustomizePlus/Data/BoneTransform.cs +++ b/CustomizePlus/Data/BoneTransform.cs @@ -13,15 +13,11 @@ namespace CustomizePlus.Data public enum BoneAttribute { //hard-coding the backing values for legacy purposes - Position = 0, - Rotation = 1, - Scale = 2 - } - - public enum BoneUpdateMode - { - Position, Rotation, Scale, - //PositionIncludingScale, ScaleIncludingPosition + BonePosition = 0, + BoneRotation = 1, + Scale = 2, + LimbPosition = 3, + LimbRotation = 4 } [Serializable] @@ -34,8 +30,8 @@ public class BoneTransform public BoneTransform() { - Translation = Vector3.Zero; - Rotation = Vector3.Zero; + BoneTranslation = Vector3.Zero; + BoneRotation = Vector3.Zero; Scaling = Vector3.One; } @@ -44,18 +40,18 @@ public BoneTransform(BoneTransform original) : this() UpdateToMatch(original); } - private Vector3 _translation; - public Vector3 Translation + private Vector3 _boneTranslation; + public Vector3 BoneTranslation { - get => _translation; - set => _translation = ClampVector(value); + get => _boneTranslation; + set => _boneTranslation = ClampVector(value); } - private Vector3 _rotation; - public Vector3 Rotation + private Vector3 _boneRotation; + public Vector3 BoneRotation { - get => _rotation; - set => _rotation = ClampAngles(value); + get => _boneRotation; + set => _boneRotation = ClampAngles(value); } private Vector3 _scaling; @@ -65,52 +61,93 @@ public Vector3 Scaling set => _scaling = ClampVector(value); } + private Vector3 _limbTranslation; + public Vector3 LimbTranslation + { + get => _limbTranslation; + set => _limbTranslation = ClampVector(value); + } + + private Vector3 _limbRotation; + public Vector3 LimbRotation + { + get => _limbRotation; + set => _limbRotation = value; + } + [OnDeserialized] internal void OnDeserialized(StreamingContext context) { //Sanitize all values on deserialization - _translation = BoneTransform.ClampVector(_translation); - _rotation = ClampAngles(_rotation); - _scaling = BoneTransform.ClampVector(_scaling); + _boneTranslation = ClampVector(_boneTranslation); + _boneRotation = ClampAngles(_boneRotation); + _scaling = ClampVector(_scaling); + + _limbTranslation = ClampVector(_limbTranslation); + _limbRotation = ClampAngles(_limbRotation); } + private const float VectorUnitEpsilon = 0.00001f; + private const float AngleUnitEpsilon = 0.1f; + public bool IsEdited() { - return !Translation.IsApproximately(Vector3.Zero, 0.00001f) - || !Rotation.IsApproximately(Vector3.Zero, 0.1f) - || !Scaling.IsApproximately(Vector3.One, 0.00001f); + return !BoneTranslation.IsApproximately(Vector3.Zero, VectorUnitEpsilon) + || !BoneRotation.IsApproximately(Vector3.Zero, AngleUnitEpsilon) + || !Scaling.IsApproximately(Vector3.One, VectorUnitEpsilon) + || !LimbTranslation.IsApproximately(Vector3.Zero, VectorUnitEpsilon) + || !LimbRotation.IsApproximately(Vector3.Zero, AngleUnitEpsilon); } public BoneTransform DeepCopy() { return new BoneTransform { - Translation = Translation, - Rotation = Rotation, - Scaling = Scaling + BoneTranslation = BoneTranslation, + BoneRotation = BoneRotation, + Scaling = Scaling, + LimbTranslation = LimbTranslation, + LimbRotation = LimbRotation }; } public void UpdateAttribute(BoneAttribute which, Vector3 newValue) { - if (which == BoneAttribute.Position) - { - Translation = newValue; - } - else if (which == BoneAttribute.Rotation) + switch (which) { - Rotation = newValue; - } - else - { - Scaling = newValue; + case BoneAttribute.BonePosition: + BoneTranslation = newValue; + break; + + case BoneAttribute.LimbPosition: + LimbTranslation = newValue; + break; + + case BoneAttribute.BoneRotation: + BoneRotation = newValue; + break; + + case BoneAttribute.LimbRotation: + LimbRotation = newValue; + break; + + case BoneAttribute.Scale: + Scaling = newValue; + break; + + default: + throw new Exception("Invalid bone attribute!?"); } } public void UpdateToMatch(BoneTransform newValues) { - Translation = newValues.Translation; - Rotation = newValues.Rotation; + BoneTranslation = newValues.BoneTranslation; + LimbTranslation = newValues.LimbTranslation; + + BoneRotation = newValues.BoneRotation; + LimbRotation = newValues.LimbRotation; + Scaling = newValues.Scaling; } @@ -122,8 +159,8 @@ public BoneTransform GetStandardReflection() { return new BoneTransform { - Translation = new Vector3(Translation.X, Translation.Y, -1 * Translation.Z), - Rotation = new Vector3(-1 * Rotation.X, -1 * Rotation.Y, Rotation.Z), + BoneTranslation = new Vector3(BoneTranslation.X, BoneTranslation.Y, -1 * BoneTranslation.Z), + BoneRotation = new Vector3(-1 * BoneRotation.X, -1 * BoneRotation.Y, BoneRotation.Z), Scaling = Scaling }; } @@ -136,8 +173,8 @@ public BoneTransform GetSpecialReflection() { return new BoneTransform { - Translation = new Vector3(Translation.X, -1 * Translation.Y, Translation.Z), - Rotation = new Vector3(Rotation.X, -1 * Rotation.Y, -1 * Rotation.Z), + BoneTranslation = new Vector3(BoneTranslation.X, -1 * BoneTranslation.Y, BoneTranslation.Z), + BoneRotation = new Vector3(BoneRotation.X, -1 * BoneRotation.Y, -1 * BoneRotation.Z), Scaling = Scaling }; } @@ -147,8 +184,12 @@ public BoneTransform GetSpecialReflection() /// private void Sanitize() { - _translation = ClampVector(_translation); - _rotation = ClampAngles(_rotation); + _boneTranslation = ClampVector(_boneTranslation); + _limbTranslation = ClampVector(_limbTranslation); + + _boneRotation = ClampAngles(_boneRotation); + _limbRotation = ClampAngles(_limbRotation); + _scaling = ClampVector(_scaling); } @@ -185,12 +226,7 @@ static float Clamp_Helper(float angle) return rotVec; } - public hkQsTransformf ModifyExistingTransform(hkQsTransformf tr) - { - return ModifyExistingTranslationWithRotation(ModifyExistingRotation(ModifyExistingScale(tr))); - } - - public hkQsTransformf ModifyExistingScale(hkQsTransformf tr) + public hkQsTransformf ModifyScale(hkQsTransformf tr) { tr.Scale.X *= Scaling.X; tr.Scale.Y *= Scaling.Y; @@ -199,9 +235,9 @@ public hkQsTransformf ModifyExistingScale(hkQsTransformf tr) return tr; } - public hkQsTransformf ModifyExistingRotation(hkQsTransformf tr) + public hkQsTransformf ModifyBoneRotation(hkQsTransformf tr) { - Quaternion newRotation = tr.Rotation.ToClientQuaternion() * Rotation.ToQuaternion(); + Quaternion newRotation = tr.Rotation.ToClientQuaternion() * BoneRotation.ToQuaternion(); tr.Rotation.X = newRotation.X; tr.Rotation.Y = newRotation.Y; tr.Rotation.Z = newRotation.Z; @@ -210,9 +246,33 @@ public hkQsTransformf ModifyExistingRotation(hkQsTransformf tr) return tr; } - public hkQsTransformf ModifyExistingTranslationWithRotation(hkQsTransformf tr) + public hkQsTransformf TransformLimbRotation(hkQsTransformf tr) { - var adjustedTranslation = Vector4.Transform(Translation, tr.Rotation.ToClientQuaternion()); + Quaternion newRotation = tr.Rotation.ToClientQuaternion() * LimbRotation.ToQuaternion(); + tr.Rotation.X = newRotation.X; + tr.Rotation.Y = newRotation.Y; + tr.Rotation.Z = newRotation.Z; + tr.Rotation.W = newRotation.W; + + return tr; + } + + //public hkQsTransformf ModifyExistingRotationWithOffset(hkQsTransformf tr) + //{ + // Vector3 offset = BoneTranslation; + // tr.Translation = (tr.Translation.ToClientVector3() - offset).ToHavokVector(); + + // tr = ModifyBoneRotation(tr); + + // Vector3 modifiedOffset = Vector3.Transform(offset, BoneRotation.ToQuaternion()); + // tr.Translation = (tr.Translation.ToClientVector3() + modifiedOffset).ToHavokVector(); + + // return tr; + //} + + public hkQsTransformf ModifyBoneTranslationWithRotation(hkQsTransformf tr) + { + var adjustedTranslation = Vector4.Transform(BoneTranslation, tr.Rotation.ToClientQuaternion()); tr.Translation.X += adjustedTranslation.X; tr.Translation.Y += adjustedTranslation.Y; tr.Translation.Z += adjustedTranslation.Z; @@ -221,11 +281,20 @@ public hkQsTransformf ModifyExistingTranslationWithRotation(hkQsTransformf tr) return tr; } - public hkQsTransformf ModifyExistingTranslation(hkQsTransformf tr) + public hkQsTransformf ModifyLimbTranslation(hkQsTransformf tr) + { + tr.Translation.X += LimbTranslation.X; + tr.Translation.Y += LimbTranslation.Y; + tr.Translation.Z += LimbTranslation.Z; + + return tr; + } + + public hkQsTransformf ModifyBoneTranslation(hkQsTransformf tr) { - tr.Translation.X += Translation.X; - tr.Translation.Y += Translation.Y; - tr.Translation.Z += Translation.Z; + tr.Translation.X += BoneTranslation.X; + tr.Translation.Y += BoneTranslation.Y; + tr.Translation.Z += BoneTranslation.Z; return tr; } From c99bcd33e0aa2fc171f57fc531f337dae5fc2ead Mon Sep 17 00:00:00 2001 From: Dendroid Date: Tue, 27 Jun 2023 04:19:45 -0400 Subject: [PATCH 27/37] Update standard model bone methods to support new degrees of freedom. --- CustomizePlus/Data/Armature/ModelBone.cs | 130 ++++++++++++++++------- 1 file changed, 92 insertions(+), 38 deletions(-) diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index ece8016..84bc267 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -5,7 +5,10 @@ using System.Collections.Generic; using System.Linq; +using CustomizePlus.Extensions; + using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Common.Math; using FFXIVClientStructs.Havok; //using CustomizePlus.Memory; @@ -145,7 +148,7 @@ public override string ToString() /// Update the transformation associated with this model bone. Optionally extend the transformation /// to the model bone's twin (in which case it will be appropriately mirrored) and/or children. /// - public virtual void UpdateModel(BoneTransform newTransform, bool mirror = false, bool propagate = false, bool extendToClones = true) + public virtual void UpdateModel(BoneTransform newTransform, bool mirror = false, bool extendToClones = true) { if (mirror && TwinBone is ModelBone mb && mb != null) { @@ -153,19 +156,7 @@ public virtual void UpdateModel(BoneTransform newTransform, bool mirror = false, ? newTransform.GetSpecialReflection() : newTransform.GetStandardReflection(); - mb.UpdateModel(mirroredTransform, false, propagate); - } - - if (propagate && this is not ModelRootBone) - { - BoneTransform delta = new BoneTransform() - { - Translation = newTransform.Translation - CustomizedTransform.Translation, - Rotation = newTransform.Rotation - CustomizedTransform.Rotation, - Scaling = newTransform.Scaling - CustomizedTransform.Scaling - }; - - PropagateModelUpdate(delta); + mb.UpdateModel(mirroredTransform, false, false); } UpdateTransformation(newTransform); @@ -178,30 +169,49 @@ public virtual void UpdateModel(BoneTransform newTransform, bool mirror = false, foreach (ModelBone clone in clones) { - clone.UpdateModel(newTransform, mirror, propagate, false); + clone.UpdateModel(newTransform, mirror, false); } } } - private void PropagateModelUpdate(BoneTransform deltaTransform) - { - foreach (ModelBone mb in ChildBones) - { - BoneTransform modTransform = new(CustomizedTransform); - modTransform.Translation += deltaTransform.Translation; - modTransform.Rotation += deltaTransform.Rotation; - modTransform.Scaling += deltaTransform.Scaling; + //private void PropagateModelUpdate(bool mirror, + // Vector3 translationDelta, Vector3 rotationDelta, + // Vector3 accumulatedTranslation, Vector3 accumulatedRotation) + //{ + // foreach (ModelBone mb in ChildBones) + // { + // Vector3 accRotate = accumulatedRotation + mb.CustomizedTransform.BoneRotation; + // Vector3 accTranslate = accumulatedTranslation + mb.CustomizedTransform.BoneTranslation; - mb.UpdateTransformation(modTransform); - mb.PropagateModelUpdate(deltaTransform); - } - } + // BoneTransform modTransform = new(mb.CustomizedTransform); + + // //slide and rotate this bone back into the reference frame of the progenitor of the update + // modTransform.BoneRotation -= accRotate; + // modTransform.BoneTranslation -= Vector3.Transform(accTranslate, accRotate.ToQuaternion()); + + // //apply the delta value + // //remember, since the user can only update one attribute at a time + // //only one of these wll have non-zero values + // modTransform.BoneRotation += rotationDelta; + // modTransform.BoneTranslation += translationDelta; + + // //reapply the accumulated translation in the updated direction + // modTransform.BoneTranslation += Vector3.Transform(accTranslate, (accRotate + rotationDelta).ToQuaternion()); + + // //reapply the accumulated rotation + // modTransform.BoneRotation += accRotate; + + + // mb.UpdateModel(modTransform, mirror, false, true); + // mb.PropagateModelUpdate(mirror, translationDelta, rotationDelta, accTranslate, accRotate); + // } + //} /// /// Given a character base to which this model bone's master armature (presumably) applies, /// return the game's current transform value for the bone corresponding to this model bone (in model space). /// - public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase) + public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase, bool? modelSpace = null) { FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; @@ -211,32 +221,76 @@ public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase) if (targetPose == null) return Constants.NullTransform; - return targetPose->ModelPose.Data[BoneIndex]; + if (modelSpace == null) + { + return targetPose->AccessUnsyncedPoseLocalSpace()->Data[BoneIndex]; + } + else if (modelSpace == true) + { + return targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex]; + } + else + { + return targetPose->AccessSyncedPoseLocalSpace()->Data[BoneIndex]; + } } /// /// Given a character base to which this model bone's master armature (presumably) applies, /// change to the given transform value the value for the bone corresponding to this model bone. /// - protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform) + protected virtual void SetLocalTransform(CharacterBase* cBase, hkQsTransformf transform) { FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[PartialSkeletonIndex]; hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); //hkaPose* targetPose = cBase->Skeleton->PartialSkeletons[PartialSkeletonIndex].GetHavokPose(Constants.TruePoseIndex); - if (targetPose == null || targetPose->ModelInSync == 0) return; + if (targetPose == null) return; + + targetPose->AccessUnsyncedPoseLocalSpace()->Data[BoneIndex] = transform; - targetPose->ModelPose.Data[BoneIndex] = transform; + //targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex] = *targetPose->CalculateBoneModelSpace(BoneIndex); } + protected virtual void SetModelTransform(CharacterBase* cBase, hkQsTransformf transform) + { + FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; + FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[PartialSkeletonIndex]; + hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); + + if (targetPose == null) return; - public void ApplyModelScale(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingScale); - public void ApplyModelRotation(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingRotation); - public void ApplyModelTranslationAtAngle(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslationWithRotation); - public void ApplyModelTranslationAsIs(CharacterBase* cBase) => ApplyTransFunc(cBase, CustomizedTransform.ModifyExistingTranslation); + targetPose->AccessSyncedPoseLocalSpace()->Data[BoneIndex] = transform; + } + + public void ApplyScale(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyScale); + + public void ApplyLocalRotation(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyBoneRotation); + public void ApplyModelRotation(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyBoneRotation); + + public void ApplyLocalTranslationAtAngle(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyBoneTranslationWithRotation); + public void ApplyModelTranslationAtAngle(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyBoneTranslationWithRotation); + + //public void ApplyModelTranslationAsIs(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyBoneTranslation); + + protected virtual void ApplyLocalTransform(CharacterBase* cBase, Func modTrans) + { + if (cBase != null + && CustomizedTransform.IsEdited() + && GetGameTransform(cBase) is hkQsTransformf gameTransform + && !gameTransform.Equals(Constants.NullTransform)) + { + hkQsTransformf modTransform = modTrans(gameTransform); - protected virtual void ApplyTransFunc(CharacterBase* cBase, Func modTrans) + if (!modTransform.Equals(gameTransform) && !modTransform.Equals(Constants.NullTransform)) + { + SetLocalTransform(cBase, modTransform); + } + } + } + + protected virtual void ApplyModelTransform(CharacterBase* cBase, Func modTrans) { if (cBase != null && CustomizedTransform.IsEdited() @@ -247,7 +301,7 @@ protected virtual void ApplyTransFunc(CharacterBase* cBase, Func Date: Tue, 27 Jun 2023 12:00:57 -0400 Subject: [PATCH 28/37] More updates for the new degrees of freedom --- CustomizePlus/Data/Armature/Armature.cs | 68 +++++--- CustomizePlus/Data/Armature/ModelBone.cs | 39 ++--- CustomizePlus/Data/Armature/ModelRootBone.cs | 4 +- CustomizePlus/Data/BoneTransform.cs | 155 ++++++++++-------- CustomizePlus/Data/IBoneContainer.cs | 2 +- .../Data/Profile/CharacterProfile.cs | 11 +- CustomizePlus/Data/TransformInfo.cs | 8 +- 7 files changed, 161 insertions(+), 126 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 0f5ae85..77fa4bb 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -393,35 +393,61 @@ public unsafe void ApplyTransformation(GameObject obj) { hkaPose* currentPose = cBase->Skeleton->PartialSkeletons[pSkeleIndex].GetHavokPose(Constants.TruePoseIndex); - //the right side of this condition is apparently a havok flag incidating - //whether the model has been updated since the last frame if (currentPose != null) { - //if (SnapToReferencePose) - //{ - // currentPose->SetToReferencePose(); - //} + if (SnapToReferencePose) + { + currentPose->SetToReferencePose(); + currentPose->SyncModelSpace(); + } for (int boneIndex = 0; boneIndex < currentPose->Skeleton->Bones.Length; ++boneIndex) { if (GetBoneAt(pSkeleIndex, boneIndex) is ModelBone mb && mb != null - && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String) + && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String + && mb.HasActiveTransform) { + if (obj.HasScalableRoot()) { - mb.ApplyModelScale(cBase); + mb.ApplyIndividualScale(cBase); + } + + if (!GameStateHelper.GameInPosingModeWithFrozenRotation()) + { + mb.ApplyIndividualRotation(cBase); } + if (!GameStateHelper.GameInPosingModeWithFrozenPosition()) + { + mb.ApplyIndividualTranslationAtAngle(cBase); + } + + } + } + + currentPose->SyncModelSpace(); + currentPose->SyncLocalSpace(); + + for (int boneIndex = 0; boneIndex < currentPose->Skeleton->Bones.Length; ++boneIndex) + { + if (GetBoneAt(pSkeleIndex, boneIndex) is ModelBone mb + && mb != null + && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String + && mb.HasActiveTransform) + { + if (!GameStateHelper.GameInPosingModeWithFrozenRotation()) { - mb.ApplyModelRotation(cBase); + mb.ApplyInheritableRotation(cBase); } if (!GameStateHelper.GameInPosingModeWithFrozenPosition()) { - mb.ApplyModelTranslationAtAngle(cBase); + mb.ApplyInheritableTranslationAtAngle(cBase); } + } } } @@ -442,22 +468,26 @@ public IEnumerable GetBoneTransformValues(BoneAttribute attribute return GetAllEditableBones().Select(x => new TransformInfo(this, x, attribute, space)); } - public void UpdateBoneTransformValue(TransformInfo newTransform, BoneUpdateMode mode, bool mirrorChanges, bool propagateChanges) + public void UpdateBoneTransformValue(TransformInfo newTransform, BoneAttribute attribute, bool mirrorChanges) { if (GetAllBones().FirstOrDefault(x => x.BoneName == newTransform.BoneCodeName) is ModelBone mb && mb != null) { BoneTransform oldTransform = mb.GetTransformation(); + oldTransform.UpdateAttribute(attribute, newTransform.TransformationValue); + mb.UpdateModel(oldTransform); - BoneAttribute att = mode switch + if (mirrorChanges && mb.TwinBone is ModelBone twin && twin != null) { - BoneUpdateMode.Position => BoneAttribute.Position, - BoneUpdateMode.Rotation => BoneAttribute.Rotation, - _ => BoneAttribute.Scale - }; - - oldTransform.UpdateAttribute(att, newTransform.TransformationValue); - mb.UpdateModel(oldTransform, mirrorChanges, propagateChanges); + if (BoneData.IsIVCSBone(twin.BoneName)) + { + twin.UpdateModel(oldTransform); + } + else + { + twin.UpdateModel(oldTransform); + } + } } } } diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 84bc267..d11376a 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -72,6 +72,8 @@ public unsafe class ModelBone /// protected virtual BoneTransform CustomizedTransform { get; } + public virtual bool HasActiveTransform { get => CustomizedTransform.IsEdited(); } + #region Model Bone Construction public ModelBone(Armature arm, string codeName, int partialIdx, int boneIdx) @@ -148,17 +150,8 @@ public override string ToString() /// Update the transformation associated with this model bone. Optionally extend the transformation /// to the model bone's twin (in which case it will be appropriately mirrored) and/or children. /// - public virtual void UpdateModel(BoneTransform newTransform, bool mirror = false, bool extendToClones = true) + public virtual void UpdateModel(BoneTransform newTransform, bool extendToClones = true) { - if (mirror && TwinBone is ModelBone mb && mb != null) - { - BoneTransform mirroredTransform = BoneData.IsIVCSBone(BoneName) - ? newTransform.GetSpecialReflection() - : newTransform.GetStandardReflection(); - - mb.UpdateModel(mirroredTransform, false, false); - } - UpdateTransformation(newTransform); if (extendToClones) @@ -169,7 +162,7 @@ public virtual void UpdateModel(BoneTransform newTransform, bool mirror = false, foreach (ModelBone clone in clones) { - clone.UpdateModel(newTransform, mirror, false); + clone.UpdateModel(newTransform, false); } } } @@ -213,7 +206,6 @@ public virtual void UpdateModel(BoneTransform newTransform, bool mirror = false, /// public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase, bool? modelSpace = null) { - FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[PartialSkeletonIndex]; hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); @@ -248,7 +240,7 @@ protected virtual void SetLocalTransform(CharacterBase* cBase, hkQsTransformf tr if (targetPose == null) return; - targetPose->AccessUnsyncedPoseLocalSpace()->Data[BoneIndex] = transform; + targetPose->AccessSyncedPoseLocalSpace()->Data[BoneIndex] = transform; //targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex] = *targetPose->CalculateBoneModelSpace(BoneIndex); } @@ -261,24 +253,25 @@ protected virtual void SetModelTransform(CharacterBase* cBase, hkQsTransformf tr if (targetPose == null) return; - targetPose->AccessSyncedPoseLocalSpace()->Data[BoneIndex] = transform; + targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex] = transform; } - public void ApplyScale(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyScale); + public void ApplyIndividualScale(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyScale); - public void ApplyLocalRotation(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyBoneRotation); - public void ApplyModelRotation(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyBoneRotation); + public void ApplyInheritableRotation(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyKinematicRotation); + public void ApplyIndividualRotation(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyRotation); - public void ApplyLocalTranslationAtAngle(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyBoneTranslationWithRotation); - public void ApplyModelTranslationAtAngle(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyBoneTranslationWithRotation); - - //public void ApplyModelTranslationAsIs(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyBoneTranslation); + public void ApplyInheritableTranslationAtAngle(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyKineTranslationWithRotation); + public void ApplyIndividualTranslationAtAngle(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyTranslationWithRotation); + + public void ApplyInheritableTranslationAsIs(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyKineTranslation); + public void ApplyIndividualTranslationAsIs(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyTranslationAsIs); protected virtual void ApplyLocalTransform(CharacterBase* cBase, Func modTrans) { if (cBase != null && CustomizedTransform.IsEdited() - && GetGameTransform(cBase) is hkQsTransformf gameTransform + && GetGameTransform(cBase, false) is hkQsTransformf gameTransform && !gameTransform.Equals(Constants.NullTransform)) { hkQsTransformf modTransform = modTrans(gameTransform); @@ -294,7 +287,7 @@ protected virtual void ApplyModelTransform(CharacterBase* cBase, Func _boneTranslation; - set => _boneTranslation = ClampVector(value); + get => _translation; + set => _translation = ClampVector(value); } - private Vector3 _boneRotation; - public Vector3 BoneRotation + private Vector3 _rotation = Vector3.Zero; + public Vector3 Rotation { - get => _boneRotation; - set => _boneRotation = ClampAngles(value); + get => _rotation; + set => _rotation = ClampAngles(value); } - private Vector3 _scaling; + private Vector3 _scaling = Vector3.One; public Vector3 Scaling { get => _scaling; set => _scaling = ClampVector(value); } - private Vector3 _limbTranslation; - public Vector3 LimbTranslation + private Vector3 _kinematicTranslation = Vector3.Zero; + public Vector3 KinematicTranslation { - get => _limbTranslation; - set => _limbTranslation = ClampVector(value); + get => _kinematicTranslation; + set => _kinematicTranslation = ClampVector(value); } - private Vector3 _limbRotation; - public Vector3 LimbRotation + private Vector3 _kinematicRotation = Vector3.Zero; + public Vector3 KinematicRotation { - get => _limbRotation; - set => _limbRotation = value; + get => _kinematicRotation; + set => _kinematicRotation = ClampAngles(value); } [OnDeserialized] internal void OnDeserialized(StreamingContext context) { //Sanitize all values on deserialization - _boneTranslation = ClampVector(_boneTranslation); - _boneRotation = ClampAngles(_boneRotation); + _translation = ClampVector(_translation); + _rotation = ClampAngles(_rotation); _scaling = ClampVector(_scaling); - _limbTranslation = ClampVector(_limbTranslation); - _limbRotation = ClampAngles(_limbRotation); + _kinematicTranslation = ClampVector(_kinematicTranslation); + _kinematicRotation = ClampAngles(_kinematicRotation); } private const float VectorUnitEpsilon = 0.00001f; @@ -92,22 +96,22 @@ internal void OnDeserialized(StreamingContext context) public bool IsEdited() { - return !BoneTranslation.IsApproximately(Vector3.Zero, VectorUnitEpsilon) - || !BoneRotation.IsApproximately(Vector3.Zero, AngleUnitEpsilon) + return !Translation.IsApproximately(Vector3.Zero, VectorUnitEpsilon) + || !Rotation.IsApproximately(Vector3.Zero, AngleUnitEpsilon) || !Scaling.IsApproximately(Vector3.One, VectorUnitEpsilon) - || !LimbTranslation.IsApproximately(Vector3.Zero, VectorUnitEpsilon) - || !LimbRotation.IsApproximately(Vector3.Zero, AngleUnitEpsilon); + || !KinematicTranslation.IsApproximately(Vector3.Zero, VectorUnitEpsilon) + || !KinematicRotation.IsApproximately(Vector3.Zero, AngleUnitEpsilon); } public BoneTransform DeepCopy() { return new BoneTransform { - BoneTranslation = BoneTranslation, - BoneRotation = BoneRotation, - Scaling = Scaling, - LimbTranslation = LimbTranslation, - LimbRotation = LimbRotation + Translation = _translation, + Rotation = _rotation, + Scaling = _scaling, + KinematicTranslation = _kinematicTranslation, + KinematicRotation = _kinematicRotation }; } @@ -115,20 +119,20 @@ public void UpdateAttribute(BoneAttribute which, Vector3 newValue) { switch (which) { - case BoneAttribute.BonePosition: - BoneTranslation = newValue; + case BoneAttribute.Position: + Translation = newValue; break; - case BoneAttribute.LimbPosition: - LimbTranslation = newValue; + case BoneAttribute.FKPosition: + KinematicTranslation = newValue; break; - case BoneAttribute.BoneRotation: - BoneRotation = newValue; + case BoneAttribute.Rotation: + Rotation = newValue; break; - case BoneAttribute.LimbRotation: - LimbRotation = newValue; + case BoneAttribute.FKRotation: + KinematicRotation = newValue; break; case BoneAttribute.Scale: @@ -142,11 +146,11 @@ public void UpdateAttribute(BoneAttribute which, Vector3 newValue) public void UpdateToMatch(BoneTransform newValues) { - BoneTranslation = newValues.BoneTranslation; - LimbTranslation = newValues.LimbTranslation; + Translation = newValues.Translation; + KinematicTranslation = newValues.KinematicTranslation; - BoneRotation = newValues.BoneRotation; - LimbRotation = newValues.LimbRotation; + Rotation = newValues.Rotation; + KinematicRotation = newValues.KinematicRotation; Scaling = newValues.Scaling; } @@ -159,8 +163,8 @@ public BoneTransform GetStandardReflection() { return new BoneTransform { - BoneTranslation = new Vector3(BoneTranslation.X, BoneTranslation.Y, -1 * BoneTranslation.Z), - BoneRotation = new Vector3(-1 * BoneRotation.X, -1 * BoneRotation.Y, BoneRotation.Z), + Translation = new Vector3(Translation.X, Translation.Y, -1 * Translation.Z), + Rotation = new Vector3(-1 * Rotation.X, -1 * Rotation.Y, Rotation.Z), Scaling = Scaling }; } @@ -173,8 +177,8 @@ public BoneTransform GetSpecialReflection() { return new BoneTransform { - BoneTranslation = new Vector3(BoneTranslation.X, -1 * BoneTranslation.Y, BoneTranslation.Z), - BoneRotation = new Vector3(BoneRotation.X, -1 * BoneRotation.Y, -1 * BoneRotation.Z), + Translation = new Vector3(Translation.X, -1 * Translation.Y, Translation.Z), + Rotation = new Vector3(Rotation.X, -1 * Rotation.Y, -1 * Rotation.Z), Scaling = Scaling }; } @@ -184,11 +188,11 @@ public BoneTransform GetSpecialReflection() /// private void Sanitize() { - _boneTranslation = ClampVector(_boneTranslation); - _limbTranslation = ClampVector(_limbTranslation); + _translation = ClampVector(_translation); + _kinematicTranslation = ClampVector(_kinematicTranslation); - _boneRotation = ClampAngles(_boneRotation); - _limbRotation = ClampAngles(_limbRotation); + _rotation = ClampAngles(_rotation); + _kinematicRotation = ClampAngles(_kinematicRotation); _scaling = ClampVector(_scaling); } @@ -235,9 +239,9 @@ public hkQsTransformf ModifyScale(hkQsTransformf tr) return tr; } - public hkQsTransformf ModifyBoneRotation(hkQsTransformf tr) + public hkQsTransformf ModifyRotation(hkQsTransformf tr) { - Quaternion newRotation = tr.Rotation.ToClientQuaternion() * BoneRotation.ToQuaternion(); + Quaternion newRotation = tr.Rotation.ToClientQuaternion() * Rotation.ToQuaternion(); tr.Rotation.X = newRotation.X; tr.Rotation.Y = newRotation.Y; tr.Rotation.Z = newRotation.Z; @@ -246,9 +250,9 @@ public hkQsTransformf ModifyBoneRotation(hkQsTransformf tr) return tr; } - public hkQsTransformf TransformLimbRotation(hkQsTransformf tr) + public hkQsTransformf ModifyKinematicRotation(hkQsTransformf tr) { - Quaternion newRotation = tr.Rotation.ToClientQuaternion() * LimbRotation.ToQuaternion(); + Quaternion newRotation = tr.Rotation.ToClientQuaternion() * KinematicRotation.ToQuaternion(); tr.Rotation.X = newRotation.X; tr.Rotation.Y = newRotation.Y; tr.Rotation.Z = newRotation.Z; @@ -270,9 +274,9 @@ public hkQsTransformf TransformLimbRotation(hkQsTransformf tr) // return tr; //} - public hkQsTransformf ModifyBoneTranslationWithRotation(hkQsTransformf tr) + public hkQsTransformf ModifyTranslationWithRotation(hkQsTransformf tr) { - var adjustedTranslation = Vector4.Transform(BoneTranslation, tr.Rotation.ToClientQuaternion()); + var adjustedTranslation = Vector4.Transform(Translation, tr.Rotation.ToClientQuaternion()); tr.Translation.X += adjustedTranslation.X; tr.Translation.Y += adjustedTranslation.Y; tr.Translation.Z += adjustedTranslation.Z; @@ -281,20 +285,31 @@ public hkQsTransformf ModifyBoneTranslationWithRotation(hkQsTransformf tr) return tr; } - public hkQsTransformf ModifyLimbTranslation(hkQsTransformf tr) + public hkQsTransformf ModifyTranslationAsIs(hkQsTransformf tr) { - tr.Translation.X += LimbTranslation.X; - tr.Translation.Y += LimbTranslation.Y; - tr.Translation.Z += LimbTranslation.Z; + tr.Translation.X += Translation.X; + tr.Translation.Y += Translation.Y; + tr.Translation.Z += Translation.Z; + + return tr; + } + + public hkQsTransformf ModifyKineTranslationWithRotation(hkQsTransformf tr) + { + var adjustedTranslation = Vector4.Transform(KinematicTranslation, tr.Rotation.ToClientQuaternion()); + tr.Translation.X += adjustedTranslation.X; + tr.Translation.Y += adjustedTranslation.Y; + tr.Translation.Z += adjustedTranslation.Z; + tr.Translation.W += adjustedTranslation.W; return tr; } - public hkQsTransformf ModifyBoneTranslation(hkQsTransformf tr) + public hkQsTransformf ModifyKineTranslation(hkQsTransformf tr) { - tr.Translation.X += BoneTranslation.X; - tr.Translation.Y += BoneTranslation.Y; - tr.Translation.Z += BoneTranslation.Z; + tr.Translation.X += KinematicTranslation.X; + tr.Translation.Y += KinematicTranslation.Y; + tr.Translation.Z += KinematicTranslation.Z; return tr; } diff --git a/CustomizePlus/Data/IBoneContainer.cs b/CustomizePlus/Data/IBoneContainer.cs index 5c5913e..d1de50b 100644 --- a/CustomizePlus/Data/IBoneContainer.cs +++ b/CustomizePlus/Data/IBoneContainer.cs @@ -19,6 +19,6 @@ public interface IBoneContainer /// Given updated transformation info for a given bone (for the specific attribute, in the given posing space), /// update that bone's transformation values to reflect the updated info. /// - public void UpdateBoneTransformValue(TransformInfo newValue, BoneUpdateMode mode, bool mirrorChanges, bool propagateChanges = false); + public void UpdateBoneTransformValue(TransformInfo newValue, BoneAttribute mode, bool mirrorChanges); } } diff --git a/CustomizePlus/Data/Profile/CharacterProfile.cs b/CustomizePlus/Data/Profile/CharacterProfile.cs index 1649a25..fde33e2 100644 --- a/CustomizePlus/Data/Profile/CharacterProfile.cs +++ b/CustomizePlus/Data/Profile/CharacterProfile.cs @@ -103,21 +103,14 @@ public IEnumerable GetBoneTransformValues(BoneAttribute attribute return Bones.Select(x => new TransformInfo(this, x.Key, x.Value, attribute, space)); } - public void UpdateBoneTransformValue(TransformInfo newTransform, BoneUpdateMode mode, bool mirrorChanges, bool propagateChanges = false) + public void UpdateBoneTransformValue(TransformInfo newTransform, BoneAttribute attribute, bool mirrorChanges) { if (!Bones.ContainsKey(newTransform.BoneCodeName)) { Bones[newTransform.BoneCodeName] = new BoneTransform(); } - BoneAttribute att = mode switch - { - BoneUpdateMode.Position => BoneAttribute.Position, - BoneUpdateMode.Rotation => BoneAttribute.Rotation, - _ => BoneAttribute.Scale - }; - - Bones[newTransform.BoneCodeName].UpdateAttribute(att, newTransform.TransformationValue); + Bones[newTransform.BoneCodeName].UpdateAttribute(attribute, newTransform.TransformationValue); } public string SerializeToJSON() diff --git a/CustomizePlus/Data/TransformInfo.cs b/CustomizePlus/Data/TransformInfo.cs index e77459e..070878e 100644 --- a/CustomizePlus/Data/TransformInfo.cs +++ b/CustomizePlus/Data/TransformInfo.cs @@ -44,7 +44,9 @@ public TransformInfo(IBoneContainer container, ModelBone mb, BoneAttribute att, TransformationValue = att switch { BoneAttribute.Position => bt.Translation, + BoneAttribute.FKPosition => bt.KinematicTranslation, BoneAttribute.Rotation => bt.Rotation, + BoneAttribute.FKRotation => bt.KinematicRotation, _ => bt.Scaling }; } @@ -59,7 +61,9 @@ public TransformInfo(IBoneContainer container, string codename, BoneTransform tr TransformationValue = att switch { BoneAttribute.Position => tr.Translation, + BoneAttribute.FKPosition => tr.KinematicTranslation, BoneAttribute.Rotation => tr.Rotation, + BoneAttribute.FKRotation => tr.KinematicRotation, _ => tr.Scaling }; } @@ -68,9 +72,9 @@ public TransformInfo(IBoneContainer container, string codename, BoneTransform tr /// Push this transformation info back to its source container, updating it with any changes made /// to the information since it was first retrieved. /// - public void PushChanges(BoneUpdateMode mode, bool mirrorChanges, bool propagateChanges = false) + public void PushChanges(BoneAttribute attribute, bool mirrorChanges) { - _sourceContainer.UpdateBoneTransformValue(this, mode, mirrorChanges, propagateChanges); + _sourceContainer.UpdateBoneTransformValue(this, attribute, mirrorChanges); } } } From bf93297b597b7a2fbc88d4191dabc18014053d6f Mon Sep 17 00:00:00 2001 From: Dendroid Date: Tue, 27 Jun 2023 12:13:50 -0400 Subject: [PATCH 29/37] Simplify some of the function calls. --- CustomizePlus/Data/Armature/Armature.cs | 8 +-- CustomizePlus/Data/Armature/ModelBone.cs | 63 ++++++-------------- CustomizePlus/Data/Armature/ModelRootBone.cs | 2 +- CustomizePlus/Data/BoneTransform.cs | 2 +- 4 files changed, 25 insertions(+), 50 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 77fa4bb..ec6cdff 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -416,12 +416,12 @@ public unsafe void ApplyTransformation(GameObject obj) if (!GameStateHelper.GameInPosingModeWithFrozenRotation()) { - mb.ApplyIndividualRotation(cBase); + mb.ApplyRotation(cBase, false); } if (!GameStateHelper.GameInPosingModeWithFrozenPosition()) { - mb.ApplyIndividualTranslationAtAngle(cBase); + mb.ApplyTranslationAtAngle(cBase, false); } } @@ -440,12 +440,12 @@ public unsafe void ApplyTransformation(GameObject obj) if (!GameStateHelper.GameInPosingModeWithFrozenRotation()) { - mb.ApplyInheritableRotation(cBase); + mb.ApplyRotation(cBase, true); } if (!GameStateHelper.GameInPosingModeWithFrozenPosition()) { - mb.ApplyInheritableTranslationAtAngle(cBase); + mb.ApplyTranslationAtAngle(cBase, true); } } diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index d11376a..89b4787 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -231,7 +231,7 @@ public virtual hkQsTransformf GetGameTransform(CharacterBase* cBase, bool? model /// Given a character base to which this model bone's master armature (presumably) applies, /// change to the given transform value the value for the bone corresponding to this model bone. /// - protected virtual void SetLocalTransform(CharacterBase* cBase, hkQsTransformf transform) + protected virtual void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, bool propagate) { FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[PartialSkeletonIndex]; @@ -240,61 +240,36 @@ protected virtual void SetLocalTransform(CharacterBase* cBase, hkQsTransformf tr if (targetPose == null) return; - targetPose->AccessSyncedPoseLocalSpace()->Data[BoneIndex] = transform; - - //targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex] = *targetPose->CalculateBoneModelSpace(BoneIndex); - } - - protected virtual void SetModelTransform(CharacterBase* cBase, hkQsTransformf transform) - { - FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton* skelly = cBase->Skeleton; - FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton pSkelly = skelly->PartialSkeletons[PartialSkeletonIndex]; - hkaPose* targetPose = pSkelly.GetHavokPose(Constants.TruePoseIndex); - - if (targetPose == null) return; - - targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex] = transform; - } - - public void ApplyIndividualScale(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyScale); - - public void ApplyInheritableRotation(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyKinematicRotation); - public void ApplyIndividualRotation(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyRotation); - - public void ApplyInheritableTranslationAtAngle(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyKineTranslationWithRotation); - public void ApplyIndividualTranslationAtAngle(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyTranslationWithRotation); - - public void ApplyInheritableTranslationAsIs(CharacterBase* cBase) => ApplyLocalTransform(cBase, CustomizedTransform.ModifyKineTranslation); - public void ApplyIndividualTranslationAsIs(CharacterBase* cBase) => ApplyModelTransform(cBase, CustomizedTransform.ModifyTranslationAsIs); - - protected virtual void ApplyLocalTransform(CharacterBase* cBase, Func modTrans) - { - if (cBase != null - && CustomizedTransform.IsEdited() - && GetGameTransform(cBase, false) is hkQsTransformf gameTransform - && !gameTransform.Equals(Constants.NullTransform)) + if (propagate) { - hkQsTransformf modTransform = modTrans(gameTransform); - - if (!modTransform.Equals(gameTransform) && !modTransform.Equals(Constants.NullTransform)) - { - SetLocalTransform(cBase, modTransform); - } + targetPose->AccessSyncedPoseLocalSpace()->Data[BoneIndex] = transform; + } + else + { + targetPose->AccessSyncedPoseModelSpace()->Data[BoneIndex] = transform; } } - - protected virtual void ApplyModelTransform(CharacterBase* cBase, Func modTrans) + + public void ApplyIndividualScale(CharacterBase* cBase) => ApplyTransform(cBase, CustomizedTransform.ModifyScale, false); + public void ApplyRotation(CharacterBase* cBase, bool propagate) => ApplyTransform(cBase, + propagate ? CustomizedTransform.ModifyKinematicRotation : CustomizedTransform.ModifyRotation, propagate); + public void ApplyTranslationAtAngle(CharacterBase* cBase, bool propagate) => ApplyTransform(cBase, + propagate ? CustomizedTransform.ModifyKineTranslationWithRotation : CustomizedTransform.ModifyTranslationWithRotation, propagate); + public void ApplyTranslationAsIs(CharacterBase* cBase, bool propagate) => ApplyTransform(cBase, + propagate ? CustomizedTransform.ModifyKineTranslationAsIs : CustomizedTransform.ModifyTranslationAsIs, propagate); + + protected virtual void ApplyTransform(CharacterBase* cBase, Func modTrans, bool propagate) { if (cBase != null && CustomizedTransform.IsEdited() - && GetGameTransform(cBase, true) is hkQsTransformf gameTransform + && GetGameTransform(cBase, !propagate) is hkQsTransformf gameTransform && !gameTransform.Equals(Constants.NullTransform)) { hkQsTransformf modTransform = modTrans(gameTransform); if (!modTransform.Equals(gameTransform) && !modTransform.Equals(Constants.NullTransform)) { - SetModelTransform(cBase, modTransform); + SetGameTransform(cBase, modTransform, propagate); } } } diff --git a/CustomizePlus/Data/Armature/ModelRootBone.cs b/CustomizePlus/Data/Armature/ModelRootBone.cs index acf3463..1fa1495 100644 --- a/CustomizePlus/Data/Armature/ModelRootBone.cs +++ b/CustomizePlus/Data/Armature/ModelRootBone.cs @@ -47,7 +47,7 @@ public override unsafe hkQsTransformf GetGameTransform(CharacterBase* cBase, boo }; } - protected override unsafe void SetLocalTransform(CharacterBase* cBase, hkQsTransformf transform) + protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransformf transform, bool propagate) { //if (_moddedPosition != transform.Translation.ToClientVector3()) //{ diff --git a/CustomizePlus/Data/BoneTransform.cs b/CustomizePlus/Data/BoneTransform.cs index 9a67c32..3c300a1 100644 --- a/CustomizePlus/Data/BoneTransform.cs +++ b/CustomizePlus/Data/BoneTransform.cs @@ -305,7 +305,7 @@ public hkQsTransformf ModifyKineTranslationWithRotation(hkQsTransformf tr) return tr; } - public hkQsTransformf ModifyKineTranslation(hkQsTransformf tr) + public hkQsTransformf ModifyKineTranslationAsIs(hkQsTransformf tr) { tr.Translation.X += KinematicTranslation.X; tr.Translation.Y += KinematicTranslation.Y; From b13e54ceb0561ab6ae4334ff2806bc1ba1e1df9d Mon Sep 17 00:00:00 2001 From: Dendroid Date: Tue, 27 Jun 2023 12:24:54 -0400 Subject: [PATCH 30/37] Fix mirrored bone edits --- CustomizePlus/Data/Armature/Armature.cs | 4 ++-- CustomizePlus/Data/BoneTransform.cs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index ec6cdff..6da60f2 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -481,11 +481,11 @@ public void UpdateBoneTransformValue(TransformInfo newTransform, BoneAttribute a { if (BoneData.IsIVCSBone(twin.BoneName)) { - twin.UpdateModel(oldTransform); + twin.UpdateModel(oldTransform.GetSpecialReflection()); } else { - twin.UpdateModel(oldTransform); + twin.UpdateModel(oldTransform.GetStandardReflection()); } } } diff --git a/CustomizePlus/Data/BoneTransform.cs b/CustomizePlus/Data/BoneTransform.cs index 3c300a1..5f789ed 100644 --- a/CustomizePlus/Data/BoneTransform.cs +++ b/CustomizePlus/Data/BoneTransform.cs @@ -164,7 +164,11 @@ public BoneTransform GetStandardReflection() return new BoneTransform { Translation = new Vector3(Translation.X, Translation.Y, -1 * Translation.Z), + KinematicTranslation = new Vector3(_kinematicTranslation.X, _kinematicTranslation.Y, -1 * _kinematicTranslation.Z), + Rotation = new Vector3(-1 * Rotation.X, -1 * Rotation.Y, Rotation.Z), + KinematicRotation = new Vector3(-1 * _kinematicRotation.X, -1 * _kinematicRotation.Y, _kinematicRotation.Z), + Scaling = Scaling }; } @@ -178,7 +182,11 @@ public BoneTransform GetSpecialReflection() return new BoneTransform { Translation = new Vector3(Translation.X, -1 * Translation.Y, Translation.Z), + KinematicTranslation = new Vector3(_kinematicTranslation.X, -1 * _kinematicTranslation.Y, _kinematicTranslation.Z), + Rotation = new Vector3(Rotation.X, -1 * Rotation.Y, -1 * Rotation.Z), + KinematicRotation = new Vector3(_kinematicRotation.X, -1 * _kinematicRotation.Y, -1 * _kinematicRotation.Z), + Scaling = Scaling }; } From fac63f2a04a6ffcfec0a845afe3c42cddaa6a88c Mon Sep 17 00:00:00 2001 From: Dendroid Date: Tue, 27 Jun 2023 12:46:25 -0400 Subject: [PATCH 31/37] Force distinct bone names so there's only one editable jaw. --- CustomizePlus/Data/Armature/Armature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 6da60f2..0c8e2b0 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -465,7 +465,7 @@ private static bool AreTwinnedNames(string name1, string name2) public IEnumerable GetBoneTransformValues(BoneAttribute attribute, PosingSpace space) { - return GetAllEditableBones().Select(x => new TransformInfo(this, x, attribute, space)); + return GetAllEditableBones().DistinctBy(x => x.BoneName).Select(x => new TransformInfo(this, x, attribute, space)); } public void UpdateBoneTransformValue(TransformInfo newTransform, BoneAttribute attribute, bool mirrorChanges) From 54ae351e32e66a31afab743696a98d8c35149528 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Tue, 27 Jun 2023 12:47:57 -0400 Subject: [PATCH 32/37] Adjust UI for extra DOF --- CustomizePlus/UI/Windows/BoneEditWindow.cs | 192 +++++++++------------ 1 file changed, 83 insertions(+), 109 deletions(-) diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index 2e221f6..45416fd 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -113,148 +113,124 @@ protected unsafe override void DrawContents() ImGui.Separator(); - int numColumns = Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled ? 3 : 2; - - if (ImGui.BeginTable("Checkboxes", numColumns, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoClip)) + if (ImGui.BeginTable("EditingOptions", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoClip)) { - ImGui.TableSetupColumn("CheckEnabled", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableSetupColumn("CheckLive", ImGuiTableColumnFlags.WidthStretch); - if (Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled) - ImGui.TableSetupColumn("CheckAPose", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableSetupColumn("CheckMirrored", ImGuiTableColumnFlags.WidthStretch); - if (Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled) - ImGui.TableSetupColumn("CheckParented", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("RadioButtons", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Space", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Checkboxes", ImGuiTableColumnFlags.WidthFixed); ImGui.TableNextRow(); ImGui.TableSetColumnIndex(0); - if (CtrlHelper.Checkbox("Show Live Bones", ref _settings.ShowLiveBones)) + if (GameStateHelper.GameInPosingMode()) ImGui.BeginDisabled(); + if (ImGui.RadioButton("Static Position", _settings.EditingAttribute == BoneAttribute.Position)) { - _settings.ToggleLiveBones(_settings.ShowLiveBones); - ConfirmSkeletonConnection(); + _settings.EditingAttribute = BoneAttribute.Position; } - CtrlHelper.AddHoverText($"If selected, present for editing all bones found in the game data,\nelse show only bones for which the profile already contains edits."); - - //if (Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled) - //{ - // ImGui.TableNextColumn(); - - // var tempRefSnap = _targetArmature?.SnapToReferencePose ?? false; - // if (_targetArmature != null && CtrlHelper.Checkbox("A-Pose", ref tempRefSnap)) - // { - // ConfirmSkeletonConnection(); - // _targetArmature.SnapToReferencePose = tempRefSnap; - // } - // CtrlHelper.AddHoverText($"D: Force character into their default reference pose"); - //} - - ImGui.TableNextColumn(); + CtrlHelper.AddHoverText($"May have unintended effects. Edit at your own risk!"); - if (!_settings.ShowLiveBones) ImGui.BeginDisabled(); + ImGui.SameLine(); - if (CtrlHelper.Checkbox("Mirror Mode", ref _settings.MirrorModeEnabled)) + if (ImGui.RadioButton("Static Rotation", _settings.EditingAttribute == BoneAttribute.Rotation)) { - ConfirmSkeletonConnection(); + _settings.EditingAttribute = BoneAttribute.Rotation; } - CtrlHelper.AddHoverText($"Bone changes will be reflected from left to right and vice versa"); + CtrlHelper.AddHoverText($"May have unintended effects. Edit at your own risk!"); + if (GameStateHelper.GameInPosingMode()) ImGui.EndDisabled(); - if (Plugin.ConfigurationManager.Configuration.DebuggingModeEnabled) + ImGui.SameLine(); + if (ImGui.RadioButton("Scale", _settings.EditingAttribute == BoneAttribute.Scale)) { - ImGui.TableNextColumn(); + _settings.EditingAttribute = BoneAttribute.Scale; + } - if (CtrlHelper.Checkbox("Parenting Mode", ref _settings.ParentingEnabled)) - { - ConfirmSkeletonConnection(); - } - CtrlHelper.AddHoverText($"D: Changes will propagate \"outward\" from edited bones"); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + + if (CtrlHelper.Checkbox("Show Live Bones", ref _settings.ShowLiveBones)) + { + _settings.ToggleLiveBones(_settings.ShowLiveBones); + ConfirmSkeletonConnection(); } + CtrlHelper.AddHoverText($"If selected, present for editing all bones found in the game data,\nelse show only bones for which the profile already contains edits."); - if (!_settings.ShowLiveBones) ImGui.EndDisabled(); - ImGui.EndTable(); - } + ImGui.SameLine(); - ImGui.Separator(); + if (!_settings.ShowLiveBones || targetObject == null) ImGui.BeginDisabled(); + if (ImGui.Button("Reload Bone Data")) + { + _settings.ArmatureInProgress.RebuildSkeleton(targetObject); + } + CtrlHelper.AddHoverText("Refresh the skeleton data obtained from in-game"); + if (!_settings.ShowLiveBones || targetObject == null) ImGui.EndDisabled(); - if (ImGui.BeginTable("Misc", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoClip)) - { - ImGui.TableSetupColumn("Attributes", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Space", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableSetupColumn("ReloadButton", ImGuiTableColumnFlags.WidthFixed); ImGui.TableNextRow(); ImGui.TableSetColumnIndex(0); if (GameStateHelper.GameInPosingMode()) ImGui.BeginDisabled(); - if (ImGui.RadioButton("Position", _settings.EditingAttribute == BoneAttribute.Position)) + if (ImGui.RadioButton("Kinematic Position", _settings.EditingAttribute == BoneAttribute.FKPosition)) { - _settings.EditingAttribute = BoneAttribute.Position; + _settings.EditingAttribute = BoneAttribute.FKPosition; } CtrlHelper.AddHoverText($"May have unintended effects. Edit at your own risk!"); ImGui.SameLine(); - if (ImGui.RadioButton("Rotation", _settings.EditingAttribute == BoneAttribute.Rotation)) + if (ImGui.RadioButton("Kinematic Rotation", _settings.EditingAttribute == BoneAttribute.FKRotation)) { - _settings.EditingAttribute = BoneAttribute.Rotation; + _settings.EditingAttribute = BoneAttribute.FKRotation; } CtrlHelper.AddHoverText($"May have unintended effects. Edit at your own risk!"); if (GameStateHelper.GameInPosingMode()) ImGui.EndDisabled(); - ImGui.SameLine(); - if (ImGui.RadioButton("Scale", _settings.EditingAttribute == BoneAttribute.Scale)) - { - _settings.EditingAttribute = BoneAttribute.Scale; - } - ImGui.TableNextColumn(); ImGui.TableNextColumn(); - if (!_settings.ShowLiveBones || targetObject == null) ImGui.BeginDisabled(); - if (ImGui.Button("Reload Bone Data")) + var tempRefSnap = _settings.ArmatureInProgress?.SnapToReferencePose ?? false; + if (_settings.ArmatureInProgress != null && CtrlHelper.Checkbox("A-Pose", ref tempRefSnap)) { - _settings.ArmatureInProgress.RebuildSkeleton(targetObject); + ConfirmSkeletonConnection(); + _settings.ArmatureInProgress.SnapToReferencePose = tempRefSnap; } - CtrlHelper.AddHoverText("Refresh the skeleton data obtained from in-game"); - if (!_settings.ShowLiveBones || targetObject == null) ImGui.EndDisabled(); + CtrlHelper.AddHoverText($"Force character into their default reference pose"); - ImGui.EndTable(); - } + ImGui.SameLine(); - //if (!Settings.EditStack.UndoPossible()) ImGui.BeginDisabled(); - //if (ImGuiComponents.IconButton(FontAwesomeIcon.UndoAlt)) - //{ - // Settings.EditStack.Undo(); - //} - //CtrlHelper.AddHoverText("Undo last edit"); - //if (!Settings.EditStack.UndoPossible()) ImGui.EndDisabled(); + if (!_settings.ShowLiveBones) ImGui.BeginDisabled(); - //ImGui.SameLine(); + if (CtrlHelper.Checkbox("Mirror Mode", ref _settings.MirrorModeEnabled)) + { + ConfirmSkeletonConnection(); + } + CtrlHelper.AddHoverText($"Bone changes will be reflected from left to right and vice versa"); - //if (!Settings.EditStack.RedoPossible()) ImGui.BeginDisabled(); - //if (ImGuiComponents.IconButton(FontAwesomeIcon.RedoAlt)) - //{ - // Settings.EditStack.Redo(); - //} - //CtrlHelper.AddHoverText("Redo next edit"); - //if (!Settings.EditStack.RedoPossible()) ImGui.EndDisabled(); + //ImGui.TableNextColumn(); + //if (CtrlHelper.Checkbox("Parenting Mode", ref _settings.ParentingEnabled)) + //{ + // ConfirmSkeletonConnection(); + //} + //CtrlHelper.AddHoverText($"Propagate changes \"outward\" from edited bones"); + + ImGui.EndTable(); + } ImGui.Separator(); //CompleteBoneEditor("n_root"); - var col1Label = _settings.EditingAttribute == BoneAttribute.Rotation - ? "Roll" - : "X"; - var col2Label = _settings.EditingAttribute == BoneAttribute.Rotation - ? "Pitch" - : "Y"; - var col3Label = _settings.EditingAttribute == BoneAttribute.Rotation - ? "Yaw" - : "Z"; - var col4Label = _settings.EditingAttribute == BoneAttribute.Scale - ? "All" - : "N/A"; + string col1Label = "X"; + string col2Label = "Y"; + string col3Label = "Z"; + string col4Label = _settings.EditingAttribute == BoneAttribute.Scale ? "All" : "N/A"; + + if (_settings.EditingAttribute == BoneAttribute.Rotation || _settings.EditingAttribute == BoneAttribute.FKRotation) + { + col1Label = "Roll"; + col2Label = "Pitch"; + col3Label = "Yaw"; + } if (ImGui.BeginTable("Bones", 6, ImGuiTableFlags.BordersOuterH | ImGuiTableFlags.BordersV | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.ScrollY, new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 56))) @@ -377,8 +353,10 @@ protected unsafe override void DrawContents() ConfirmationDialog.Show("Revert all unsaved work?", () => { + bool useAPose = _settings.ArmatureInProgress.SnapToReferencePose; Plugin.ProfileManager.RevertWorkingCopy(); Plugin.ArmatureManager.ConstructArmatureForProfile(_settings.ProfileInProgress, true); + _settings.ArmatureInProgress.SnapToReferencePose = useAPose; _dirty = false; }); } @@ -425,7 +403,6 @@ public unsafe void ConfirmSkeletonConnection() { _settings.ToggleLiveBones(false); _settings.MirrorModeEnabled = false; - _settings.ParentingEnabled = false; DisplayNoLinkMsg(); } } @@ -476,7 +453,9 @@ private bool RevertBoneButton(string codename, ref Vector3 value) value = _settings.EditingAttribute switch { BoneAttribute.Position => bec.Translation, + BoneAttribute.FKPosition => bec.KinematicTranslation, BoneAttribute.Rotation => bec.Rotation, + BoneAttribute.FKRotation => bec.KinematicRotation, _ => bec.Scaling }; } @@ -495,9 +474,9 @@ private bool RevertBoneButton(string codename, ref Vector3 value) private bool FullBoneSlider(string label, ref Vector3 value) { - float velocity = _settings.EditingAttribute == BoneAttribute.Rotation ? 0.1f : 0.001f; - float minValue = _settings.EditingAttribute == BoneAttribute.Rotation ? -360.0f : -10.0f; - float maxValue = _settings.EditingAttribute == BoneAttribute.Rotation ? 360.0f : 10.0f; + float velocity = _settings.EditingRotation ? 0.1f : 0.001f; + float minValue = _settings.EditingRotation ? -360.0f : -10.0f; + float maxValue = _settings.EditingRotation ? 360.0f : 10.0f; float temp = _settings.EditingAttribute switch { @@ -519,9 +498,9 @@ private bool FullBoneSlider(string label, ref Vector3 value) private bool SingleValueSlider(string label, ref float value) { - var velocity = _settings.EditingAttribute == BoneAttribute.Rotation ? 0.1f : 0.001f; - var minValue = _settings.EditingAttribute == BoneAttribute.Rotation ? -360.0f : -10.0f; - var maxValue = _settings.EditingAttribute == BoneAttribute.Rotation ? 360.0f : 10.0f; + var velocity = _settings.EditingRotation ? 0.1f : 0.001f; + var minValue = _settings.EditingRotation ? -360.0f : -10.0f; + var maxValue = _settings.EditingRotation ? 360.0f : 10.0f; ImGui.PushItemWidth(ImGui.GetColumnWidth()); var temp = value; @@ -593,14 +572,7 @@ private void CompleteBoneEditor(TransformInfo trInfo) trInfo.TransformationValue = newVector; - BoneUpdateMode mode = _settings.EditingAttribute switch - { - BoneAttribute.Position => BoneUpdateMode.Position, - BoneAttribute.Rotation => BoneUpdateMode.Rotation, - _ => BoneUpdateMode.Scale - }; - - trInfo.PushChanges(mode, _settings.MirrorModeEnabled, _settings.ParentingEnabled); + trInfo.PushChanges(_settings.EditingAttribute, _settings.MirrorModeEnabled); } } @@ -617,12 +589,14 @@ public struct EditorSessionSettings public bool ShowLiveBones = false; public bool MirrorModeEnabled = false; - public bool ParentingEnabled = false; + public BoneAttribute EditingAttribute = BoneAttribute.Scale; public PosingSpace ReferenceFrame = PosingSpace.Self; public Dictionary GroupExpandedState = new(); + public bool EditingRotation => EditingAttribute == BoneAttribute.Rotation || EditingAttribute == BoneAttribute.FKRotation; + public void ToggleLiveBones(bool setTo) { ShowLiveBones = setTo; From 56566d3e36c9636ce7547bcd2b943d27412bc829 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Tue, 27 Jun 2023 12:54:41 -0400 Subject: [PATCH 33/37] Streamline bone monitor window --- .../UI/Windows/Debug/BoneMonitorWindow.cs | 87 ++++++++----------- 1 file changed, 36 insertions(+), 51 deletions(-) diff --git a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs index 1f55104..843e36b 100644 --- a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs +++ b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs @@ -60,70 +60,55 @@ protected override void DrawContents() DisplayNoLinkMsg(); } - ImGui.TextUnformatted($"Character Name: {_targetProfile.CharacterName}"); - - ImGui.SameLine(); - ImGui.TextUnformatted("|"); - - ImGui.SameLine(); - ImGui.TextUnformatted($"Profile Name: {_targetProfile.ProfileName}"); - - ImGui.SameLine(); - ImGui.TextUnformatted("|"); - - ImGui.SameLine(); - var tempEnabled = _targetProfile.Enabled; - if (CtrlHelper.Checkbox("Live", ref tempEnabled)) + if (ImGui.BeginTable("Header", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoClip | ImGuiTableFlags.BordersInnerV)) { - _targetProfile.Enabled = tempEnabled; - } + ImGui.TableSetupColumn("AttributeType", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("ReferenceFrame", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Space", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Reload", ImGuiTableColumnFlags.WidthFixed); - CtrlHelper.AddHoverText("Hook the editor into the game to edit and preview live bone data"); - - ImGui.Separator(); - - if (ImGui.RadioButton("Position", _targetAttribute == BoneAttribute.Position)) - _targetAttribute = BoneAttribute.Position; + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); - ImGui.SameLine(); - if (ImGui.RadioButton("Rotation", _targetAttribute == BoneAttribute.Rotation)) - _targetAttribute = BoneAttribute.Rotation; + if (ImGui.RadioButton("Position", _targetAttribute == BoneAttribute.Position)) + _targetAttribute = BoneAttribute.Position; - ImGui.SameLine(); - if (ImGui.RadioButton("Scale", _targetAttribute == BoneAttribute.Scale)) - _targetAttribute = BoneAttribute.Scale; + ImGui.SameLine(); + if (ImGui.RadioButton("Rotation", _targetAttribute == BoneAttribute.Rotation)) + _targetAttribute = BoneAttribute.Rotation; - ImGui.SameLine(); - ImGui.Spacing(); - ImGui.SameLine(); + ImGui.SameLine(); + if (ImGui.RadioButton("Scale", _targetAttribute == BoneAttribute.Scale)) + _targetAttribute = BoneAttribute.Scale; - ImGui.TextUnformatted("|"); + ImGui.TableNextColumn(); - ImGui.SameLine(); - ImGui.Spacing(); - ImGui.SameLine(); + if (ImGui.RadioButton("Local", _targetPose == PosingSpace.Self)) + _targetPose = PosingSpace.Self; - if (ImGui.RadioButton("Local", _targetPose == PosingSpace.Self)) - _targetPose = PosingSpace.Self; + ImGui.SameLine(); + if (ImGui.RadioButton("Model", _targetPose == PosingSpace.Parent)) + _targetPose = PosingSpace.Parent; - ImGui.SameLine(); - if (ImGui.RadioButton("Model", _targetPose == PosingSpace.Parent)) - _targetPose = PosingSpace.Parent; + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); - //ImGui.SameLine(); - //if (ImGui.RadioButton("Reference", _targetPose == ModelBone.PoseType.Reference)) - // _targetPose = ModelBone.PoseType.Reference; + if (!_targetProfile.Enabled) ImGui.BeginDisabled(); - //------------- + if (ImGui.Button("Reload Bone Data")) + _targetArmature.RebuildSkeleton(targetObject); - if (!_targetProfile.Enabled) - ImGui.BeginDisabled(); + if (!_targetProfile.Enabled) ImGui.EndDisabled(); - if (ImGui.Button("Reload Bone Data")) - _targetArmature.RebuildSkeleton(targetObject); + ImGui.SameLine(); + var tempEnabled = _targetProfile.Enabled; + if (CtrlHelper.Checkbox("Live", ref tempEnabled)) + { + _targetProfile.Enabled = tempEnabled; + } - if (!_targetProfile.Enabled) - ImGui.EndDisabled(); + ImGui.EndTable(); + } ImGui.Separator(); @@ -220,7 +205,7 @@ private bool MysteryButton(string codename, ref Vector4 value) private void RenderTransformationInfo(ModelBone bone, CharacterBase* cBase) { - if (bone.GetGameTransform(cBase) is FFXIVClientStructs.Havok.hkQsTransformf deform) + if (bone.GetGameTransform(cBase, _targetPose == PosingSpace.Parent) is FFXIVClientStructs.Havok.hkQsTransformf deform) { var displayName = bone.ToString(); From dca6df1c5f287121b295f290edbe000294b003ee Mon Sep 17 00:00:00 2001 From: Dendroid Date: Wed, 28 Jun 2023 16:58:34 -0400 Subject: [PATCH 34/37] Add some extra debug output. Rework partial root bones slightly to bind them more tightly to their real counterparts. Fix a bug wherein the jaw bone/s couldn't be edited without the values snapping back. --- CustomizePlus/Data/Armature/Armature.cs | 32 ++++++++++--- CustomizePlus/Data/Armature/ModelBone.cs | 47 +------------------ .../Data/Armature/PartialRootBone.cs | 16 +++++-- 3 files changed, 38 insertions(+), 57 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 0c8e2b0..c56c272 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -16,6 +16,8 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.Havok; +using CustomizePlus.Extensions; + namespace CustomizePlus.Data.Armature { /// @@ -148,7 +150,7 @@ public bool SnapToReferencePose public Armature(CharacterProfile prof) { - _localId = _nextGlobalId++; + _localId = ++_nextGlobalId; _partialSkeletons = Array.Empty(); @@ -173,8 +175,8 @@ public Armature(CharacterProfile prof) public override string ToString() { return Built - ? $"Armature (#{_localId}) on {Profile.CharacterName} with {TotalBoneCount} bone/s" - : $"Armature (#{_localId}) on {Profile.CharacterName} with no skeleton reference"; + ? $"Armature (#{_localId:00000}) on {Profile.CharacterName} with {TotalBoneCount} bone/s" + : $"Armature (#{_localId:00000}) on {Profile.CharacterName} with no skeleton reference"; } private bool GetReferenceSnap() @@ -322,11 +324,13 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C if (pSkeleIndex == 0 && boneIndex == 0) { newBone = new ModelRootBone(arm, boneName); + PluginLog.LogDebug($"Main root @ <{pSkeleIndex}, {boneIndex}> ({boneName})"); } - else if (boneIndex == 0) + else if (currentPartial.ConnectedBoneIndex == boneIndex) { ModelBone cloneOf = newPartials[0][currentPartial.ConnectedParentBoneIndex]; newBone = new PartialRootBone(arm, cloneOf, boneName, pSkeleIndex); + PluginLog.LogDebug($"Partial root @ <{pSkeleIndex}, {boneIndex}> ({boneName})"); } else { @@ -405,9 +409,12 @@ public unsafe void ApplyTransformation(GameObject obj) { if (GetBoneAt(pSkeleIndex, boneIndex) is ModelBone mb && mb != null + && mb is not PartialRootBone && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String && mb.HasActiveTransform) { + //Partial root bones aren't guaranteed to be parented the way that would + //logically make sense. For that reason, don't bother trying to transform them locally. if (obj.HasScalableRoot()) { @@ -437,6 +444,13 @@ public unsafe void ApplyTransformation(GameObject obj) && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String && mb.HasActiveTransform) { + if (mb is PartialRootBone prb) + { + //In the case of partial root bones, simply copy the transform in model space + //wholesale from the bone that they're a copy of + prb.ApplyOriginalTransform(cBase); + continue; + } if (!GameStateHelper.GameInPosingModeWithFrozenRotation()) { @@ -452,6 +466,13 @@ public unsafe void ApplyTransformation(GameObject obj) } } } + + if (GetAllBones().FirstOrDefault(x => x.BoneName == "j_sebo_b") is ModelBone heightBone + && heightBone.GetGameTransform(cBase, true) is hkQsTransformf spineTrans + && !spineTrans.Equals(Constants.NullTransform)) + { + *cBase->Height() = MathF.Round(spineTrans.Scale.Y, 3); + } } } @@ -470,8 +491,7 @@ public IEnumerable GetBoneTransformValues(BoneAttribute attribute public void UpdateBoneTransformValue(TransformInfo newTransform, BoneAttribute attribute, bool mirrorChanges) { - if (GetAllBones().FirstOrDefault(x => x.BoneName == newTransform.BoneCodeName) is ModelBone mb - && mb != null) + foreach(ModelBone mb in GetAllBones().Where(x => x.BoneName == newTransform.BoneCodeName)) { BoneTransform oldTransform = mb.GetTransformation(); oldTransform.UpdateAttribute(attribute, newTransform.TransformationValue); diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index 89b4787..ce8cbfc 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -150,56 +150,11 @@ public override string ToString() /// Update the transformation associated with this model bone. Optionally extend the transformation /// to the model bone's twin (in which case it will be appropriately mirrored) and/or children. /// - public virtual void UpdateModel(BoneTransform newTransform, bool extendToClones = true) + public virtual void UpdateModel(BoneTransform newTransform) { UpdateTransformation(newTransform); - - if (extendToClones) - { - IEnumerable clones = MasterArmature.GetAllBones() - .Where(x => x is not ModelRootBone) - .Where(x => x.BoneName == BoneName && !ReferenceEquals(x, this)); - - foreach (ModelBone clone in clones) - { - clone.UpdateModel(newTransform, false); - } - } } - //private void PropagateModelUpdate(bool mirror, - // Vector3 translationDelta, Vector3 rotationDelta, - // Vector3 accumulatedTranslation, Vector3 accumulatedRotation) - //{ - // foreach (ModelBone mb in ChildBones) - // { - // Vector3 accRotate = accumulatedRotation + mb.CustomizedTransform.BoneRotation; - // Vector3 accTranslate = accumulatedTranslation + mb.CustomizedTransform.BoneTranslation; - - // BoneTransform modTransform = new(mb.CustomizedTransform); - - // //slide and rotate this bone back into the reference frame of the progenitor of the update - // modTransform.BoneRotation -= accRotate; - // modTransform.BoneTranslation -= Vector3.Transform(accTranslate, accRotate.ToQuaternion()); - - // //apply the delta value - // //remember, since the user can only update one attribute at a time - // //only one of these wll have non-zero values - // modTransform.BoneRotation += rotationDelta; - // modTransform.BoneTranslation += translationDelta; - - // //reapply the accumulated translation in the updated direction - // modTransform.BoneTranslation += Vector3.Transform(accTranslate, (accRotate + rotationDelta).ToQuaternion()); - - // //reapply the accumulated rotation - // modTransform.BoneRotation += accRotate; - - - // mb.UpdateModel(modTransform, mirror, false, true); - // mb.PropagateModelUpdate(mirror, translationDelta, rotationDelta, accTranslate, accRotate); - // } - //} - /// /// Given a character base to which this model bone's master armature (presumably) applies, /// return the game's current transform value for the bone corresponding to this model bone (in model space). diff --git a/CustomizePlus/Data/Armature/PartialRootBone.cs b/CustomizePlus/Data/Armature/PartialRootBone.cs index a6508fd..5ea1466 100644 --- a/CustomizePlus/Data/Armature/PartialRootBone.cs +++ b/CustomizePlus/Data/Armature/PartialRootBone.cs @@ -20,17 +20,23 @@ public PartialRootBone(Armature arm, ModelBone primaryBone, string codeName, int { PrimaryPartialBone = primaryBone; + //partial roots don't have ACTUAL parents, but for the sake of simplicty let's + //pretend that they're parented the same as their duplicates if (PrimaryPartialBone.ParentBone is ModelBone pBone && pBone != null) { AddParent(pBone.PartialSkeletonIndex, pBone.BoneIndex); } - - //if (PrimaryPartialBone.TwinBone is ModelBone tBone && tBone != null) - //{ - // AddTwin(tBone.PartialSkeletonIndex, tBone.BoneIndex); - //} } protected override BoneTransform CustomizedTransform { get => PrimaryPartialBone.GetTransformation(); } + + /// + /// Reference this partial root bone's duplicate model bone and copy its model space transform + /// wholesale. This presumes that the duplicate model bone has first completed its own spacial calcs. + /// + public void ApplyOriginalTransform(CharacterBase *cBase) + { + SetGameTransform(cBase, PrimaryPartialBone.GetGameTransform(cBase, true), true); + } } } From a982789ed6b4fbcdd095b732e3c19656dbf93c44 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Wed, 28 Jun 2023 23:11:40 -0400 Subject: [PATCH 35/37] Abstract armatures out into multiple classes so that they can be composed more elegantly. Change name of reference pose public access variable. Add primitive weapon editing. --- CustomizePlus/Data/Armature/Armature.cs | 268 ++++++------------ .../Data/Armature/ArmatureManager.cs | 8 +- .../Data/Armature/CharacterArmature.cs | 105 +++++++ CustomizePlus/Data/Armature/ModelBone.cs | 19 +- CustomizePlus/Data/Armature/WeaponArmature.cs | 103 +++++++ CustomizePlus/Data/BoneData.cs | 2 + .../Data/Profile/CharacterProfile.cs | 4 +- CustomizePlus/Data/TransformInfo.cs | 6 +- .../Extensions/CharacterBaseExtensions.cs | 59 ++++ CustomizePlus/UI/Windows/BoneEditWindow.cs | 12 +- .../UI/Windows/Debug/BoneMonitorWindow.cs | 41 ++- 11 files changed, 415 insertions(+), 212 deletions(-) create mode 100644 CustomizePlus/Data/Armature/CharacterArmature.cs create mode 100644 CustomizePlus/Data/Armature/WeaponArmature.cs create mode 100644 CustomizePlus/Extensions/CharacterBaseExtensions.cs diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index c56c272..38a5e12 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -24,35 +24,26 @@ namespace CustomizePlus.Data.Armature /// Represents a "copy" of the ingame skeleton upon which the linked character profile is meant to operate. /// Acts as an interface by which the in-game skeleton can be manipulated on a bone-by-bone basis. /// - public unsafe class Armature : IBoneContainer + public abstract unsafe class Armature : IBoneContainer { - /// - /// Gets the Customize+ profile for which this mockup applies transformations. - /// - public CharacterProfile Profile { get; init; } - /// /// Gets or sets a value indicating whether or not this armature has any renderable objects on which it should act. /// public bool IsVisible { get; set; } - /// - /// Gets a value indicating whether or not this armature has successfully built itself with bone information. - /// - public bool IsBuilt { get; private set; } - /// /// For debugging purposes, each armature is assigned a globally-unique ID number upon creation. /// private static uint _nextGlobalId; - private readonly uint _localId; + protected readonly uint _localId; /// /// Each skeleton is made up of several smaller "partial" skeletons. /// Each partial skeleton has its own list of bones, with a root bone at index zero. /// The root bone of a partial skeleton may also be a regular bone in a different partial skeleton. /// - private ModelBone[][] _partialSkeletons; + protected ModelBone[][] _partialSkeletons { get; set; } + protected Armature[] _subArmatures { get; set; } #region Bone Accessors ------------------------------------------------------------------------------- @@ -61,14 +52,6 @@ public unsafe class Armature : IBoneContainer /// public int PartialSkeletonCount => _partialSkeletons.Length; - /// - /// Get the list of bones belonging to the partial skeleton at the given index. - /// - public ModelBone[] this[int i] - { - get => _partialSkeletons[i]; - } - /// /// Returns the number of bones contained within the partial skeleton with the given index. /// @@ -77,11 +60,13 @@ public ModelBone[] this[int i] /// /// Get the bone at index 'j' within the partial skeleton at index 'i'. /// - public ModelBone this[int i, int j] + private ModelBone this[int i, int j] { get => _partialSkeletons[i][j]; } + protected int BoneCount() => _partialSkeletons.Sum(x => x.Length) + _subArmatures.Sum(x => x.BoneCount()); + /// /// Return the bone at the given indices, if it exists /// @@ -101,19 +86,10 @@ public ModelBone[] this[int i] /// public ModelBone GetRootBoneOfPartial(int partialIndex) => this[partialIndex, 0]; - public ModelBone MainRootBone => GetRootBoneOfPartial(0); - - /// - /// Get the total number of bones in each partial skeleton combined. - /// - // In exactly one partial skeleton will the root bone be an independent bone. In all others, it's a reference to a separate, real bone. - // For that reason we must subtract the number of duplicate bones - public int TotalBoneCount => _partialSkeletons.Sum(x => x.Length); - /// - /// Get all individual model bones making up this armature + /// Get all individual model bones making up this armature. /// - public IEnumerable GetAllBones() + public IEnumerable GetLocalBones() { for (int i = 0; i < _partialSkeletons.Length; ++i) { @@ -124,10 +100,18 @@ public IEnumerable GetAllBones() } } - /// - /// Get all individual model bones making up this armature EXCEPT for partial root bones - /// - public IEnumerable GetAllEditableBones() => GetAllBones().Where(x => x is not PartialRootBone); + public IEnumerable GetLocalAndDownstreamBones() + { + foreach (ModelBone mb in GetLocalBones()) yield return mb; + + foreach(Armature arm in _subArmatures) + { + foreach(ModelBone mbd in arm.GetLocalAndDownstreamBones()) + { + yield return mbd; + } + } + } /// /// Gets a value indicating whether this armature has yet built its skeleton. @@ -141,166 +125,56 @@ public IEnumerable GetAllBones() /// Gets or sets a value indicating whether or not this armature should snap all of its bones to their reference "bindposes". /// i.e. force the character ingame to assume their "default" pose. /// - public bool SnapToReferencePose + public bool FrozenPose { - get => GetReferenceSnap(); - set => SetReferenceSnap(value); + get => GetFrozenStatus(); + set => SetFrozenStatus(value); } - private bool _snapToReference; + private bool _frozenInDefaultPose; - public Armature(CharacterProfile prof) + public Armature() { _localId = ++_nextGlobalId; _partialSkeletons = Array.Empty(); + _subArmatures = Array.Empty(); - Profile = prof; IsVisible = false; - - //cross-link the two, though I'm not positive the profile ever needs to refer back - Profile.Armature = this; - - TryLinkSkeleton(); - - PluginLog.LogDebug($"Instantiated {this}, attached to {Profile}"); - } - /// - /// Returns whether or not this armature was designed to apply to an object with the given name. - /// - public bool AppliesTo(string objectName) => Profile.AppliesTo(objectName); - /// - public override string ToString() - { - return Built - ? $"Armature (#{_localId:00000}) on {Profile.CharacterName} with {TotalBoneCount} bone/s" - : $"Armature (#{_localId:00000}) on {Profile.CharacterName} with no skeleton reference"; - } - - private bool GetReferenceSnap() - { - if (Profile != Plugin.ProfileManager.ProfileOpenInEditor) - _snapToReference = false; + public abstract override string ToString(); - return _snapToReference; - } - - private void SetReferenceSnap(bool value) + protected bool GetFrozenStatus() { - if (value && Profile == Plugin.ProfileManager.ProfileOpenInEditor) - _snapToReference = false; - - _snapToReference = value; + return _frozenInDefaultPose; } - /// - /// Returns whether or not a link can be established between the armature and an in-game object. - /// If unbuilt, the armature will use this opportunity to rebuild itself. - /// - public unsafe CharacterBase* TryLinkSkeleton(bool forceRebuild = false) + protected virtual void SetFrozenStatus(bool value) { - try - { - if (GameDataHelper.TryLookupCharacterBase(Profile.CharacterName, out CharacterBase* cBase) - && cBase != null) - { - if (!Built || forceRebuild || NewBonesAvailable(cBase)) - { - RebuildSkeleton(cBase); - } - //else if (NewBonesAvailable(cBase)) - //{ - // AugmentSkeleton(cBase); - //} - return cBase; - } - } - catch (Exception ex) + _frozenInDefaultPose = value; + foreach(Armature arm in _subArmatures) { - PluginLog.LogError($"Error occured while attempting to link skeleton '{this}': {ex}"); + arm.SetFrozenStatus(value); } - - return null; } - private bool NewBonesAvailable(CharacterBase* cBase) - { - if (cBase == null) - { - return false; - } - else if (cBase->Skeleton->PartialSkeletonCount > _partialSkeletons.Length) - { - return true; - } - else - { - for (int i = 0; i < cBase->Skeleton->PartialSkeletonCount; ++i) - { - hkaPose* newPose = cBase->Skeleton->PartialSkeletons[i].GetHavokPose(Constants.TruePoseIndex); - if (newPose != null - && newPose->Skeleton->Bones.Length > _partialSkeletons[i].Length) - { - return true; - } - } - } - - return false; - } + public abstract void UpdateOrDeleteRecord(string recordKey, BoneTransform? trans); /// /// Rebuild the armature using the provided character base as a reference. /// - public void RebuildSkeleton(CharacterBase* cBase) - { - if (cBase == null) - return; - - List> newPartials = ParseBonesFromObject(this, cBase); - - _partialSkeletons = newPartials.Select(x => x.ToArray()).ToArray(); + public abstract void RebuildSkeleton(CharacterBase* cBase); - PluginLog.LogDebug($"Rebuilt {this}"); - } - - public void AugmentSkeleton(CharacterBase* cBase) + protected static unsafe List> ParseBonesFromObject(Armature arm, CharacterBase* cBase, Dictionary? records) { - if (cBase == null) - return; - - List> oldPartials = _partialSkeletons.Select(x => x.ToList()).ToList(); - List> newPartials = ParseBonesFromObject(this, cBase); + List> newPartials = new(); - //for each of the new partial skeletons discovered... - for (int i = 0; i < newPartials.Count; ++i) + if (cBase == null) { - //if the old skeleton doesn't contain the new partial at all, add the whole thing - if (i > oldPartials.Count) - { - oldPartials.Add(newPartials[i]); - } - //otherwise, add every model bone the new partial has that the old one doesn't - else - { - for (int j = oldPartials[i].Count; j < newPartials[i].Count; ++j) - { - oldPartials[i].Add(newPartials[i][j]); - } - } + return newPartials; } - _partialSkeletons = oldPartials.Select(x => x.ToArray()).ToArray(); - - PluginLog.LogDebug($"Augmented {this} with new bones"); - } - - private static unsafe List> ParseBonesFromObject(Armature arm, CharacterBase* cBase) - { - List> newPartials = new(); - try { //build the skeleton @@ -357,7 +231,7 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C } } - if (arm.Profile.Bones.TryGetValue(boneName, out BoneTransform? bt) + if (records != null && records.TryGetValue(boneName, out BoneTransform? bt) && bt != null) { newBone.UpdateModel(bt); @@ -382,15 +256,38 @@ private static unsafe List> ParseBonesFromObject(Armature arm, C return newPartials; } + //protected virtual bool NewBonesAvailable(CharacterBase* cBase) + //{ + // if (cBase == null) + // { + // return false; + // } + // else if (cBase->Skeleton->PartialSkeletonCount > _partialSkeletons.Length) + // { + // return true; + // } + // else + // { + // for (int i = 0; i < cBase->Skeleton->PartialSkeletonCount; ++i) + // { + // hkaPose* newPose = cBase->Skeleton->PartialSkeletons[i].GetHavokPose(Constants.TruePoseIndex); + // if (newPose != null + // && newPose->Skeleton->Bones.Length > _partialSkeletons[i].Length) + // { + // return true; + // } + // } + // } + + // return false; + //} /// /// Iterate through this armature's model bones and apply their associated transformations /// to all of their in-game siblings. /// - public unsafe void ApplyTransformation(GameObject obj) + public virtual unsafe void ApplyTransformation(CharacterBase* cBase, bool applyScaling) { - CharacterBase* cBase = obj.ToCharacterBase(); - if (cBase != null) { for (int pSkeleIndex = 0; pSkeleIndex < cBase->Skeleton->PartialSkeletonCount; ++pSkeleIndex) @@ -399,7 +296,7 @@ public unsafe void ApplyTransformation(GameObject obj) if (currentPose != null) { - if (SnapToReferencePose) + if (FrozenPose) { currentPose->SetToReferencePose(); currentPose->SyncModelSpace(); @@ -410,13 +307,14 @@ public unsafe void ApplyTransformation(GameObject obj) if (GetBoneAt(pSkeleIndex, boneIndex) is ModelBone mb && mb != null && mb is not PartialRootBone - && mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String + && (mb.BoneName == currentPose->Skeleton->Bones[boneIndex].Name.String + || mb.BoneName[3..] == currentPose->Skeleton->Bones[boneIndex].Name.String) && mb.HasActiveTransform) { //Partial root bones aren't guaranteed to be parented the way that would //logically make sense. For that reason, don't bother trying to transform them locally. - if (obj.HasScalableRoot()) + if (applyScaling) { mb.ApplyIndividualScale(cBase); } @@ -467,11 +365,10 @@ public unsafe void ApplyTransformation(GameObject obj) } } - if (GetAllBones().FirstOrDefault(x => x.BoneName == "j_sebo_b") is ModelBone heightBone - && heightBone.GetGameTransform(cBase, true) is hkQsTransformf spineTrans - && !spineTrans.Equals(Constants.NullTransform)) + foreach(Armature subArm in _subArmatures) { - *cBase->Height() = MathF.Round(spineTrans.Scale.Y, 3); + //subs are responsible for figuring out how they want to parse the character base + subArm.ApplyTransformation(cBase, applyScaling); } } } @@ -484,14 +381,25 @@ private static bool AreTwinnedNames(string name1, string name2) && (name1[0..^1] == name2[0..^1]); } - public IEnumerable GetBoneTransformValues(BoneAttribute attribute, PosingSpace space) + public virtual IEnumerable GetBoneTransformValues(BoneAttribute attribute, PosingSpace space) { - return GetAllEditableBones().DistinctBy(x => x.BoneName).Select(x => new TransformInfo(this, x, attribute, space)); + foreach(ModelBone mb in GetLocalBones().Where(x => x is not PartialRootBone)) + { + yield return new TransformInfo(this, mb, attribute, space); + } + + foreach(Armature arm in _subArmatures) + { + foreach(TransformInfo trInfo in arm.GetBoneTransformValues(attribute, space)) + { + yield return trInfo; + } + } } public void UpdateBoneTransformValue(TransformInfo newTransform, BoneAttribute attribute, bool mirrorChanges) { - foreach(ModelBone mb in GetAllBones().Where(x => x.BoneName == newTransform.BoneCodeName)) + foreach(ModelBone mb in GetLocalBones().Where(x => x.BoneName == newTransform.BoneCodeName)) { BoneTransform oldTransform = mb.GetTransformation(); oldTransform.UpdateAttribute(attribute, newTransform.TransformationValue); diff --git a/CustomizePlus/Data/Armature/ArmatureManager.cs b/CustomizePlus/Data/Armature/ArmatureManager.cs index 5e70b13..9fb2f94 100644 --- a/CustomizePlus/Data/Armature/ArmatureManager.cs +++ b/CustomizePlus/Data/Armature/ArmatureManager.cs @@ -16,7 +16,7 @@ namespace CustomizePlus.Data.Armature public sealed class ArmatureManager { private Armature? _defaultArmature = null; - private readonly HashSet _armatures = new(); + private readonly HashSet _armatures = new(); public void RenderCharacterProfiles(params CharacterProfile[] profiles) { @@ -35,7 +35,7 @@ public void RenderCharacterProfiles(params CharacterProfile[] profiles) public void ConstructArmatureForProfile(CharacterProfile newProfile, bool forceNew = false) { if (forceNew - && _armatures.FirstOrDefault(x => x.Profile == newProfile) is Armature arm + && _armatures.FirstOrDefault(x => x.Profile == newProfile) is CharacterArmature arm && arm != null) { _armatures.Remove(arm); @@ -43,7 +43,7 @@ public void ConstructArmatureForProfile(CharacterProfile newProfile, bool forceN if (!_armatures.Any(x => x.Profile == newProfile)) { - var newArm = new Armature(newProfile); + var newArm = new CharacterArmature(newProfile); _armatures.Add(newArm); PluginLog.LogDebug($"Added '{newArm}' to cache"); } @@ -86,7 +86,7 @@ private unsafe void ApplyArmatureTransforms() && prof.Armature != null && prof.Armature.IsVisible) { - prof.Armature.ApplyTransformation(obj); + prof.Armature.ApplyTransformation(obj.ToCharacterBase(), obj.HasScalableRoot()); } } } diff --git a/CustomizePlus/Data/Armature/CharacterArmature.cs b/CustomizePlus/Data/Armature/CharacterArmature.cs new file mode 100644 index 0000000..1f405b2 --- /dev/null +++ b/CustomizePlus/Data/Armature/CharacterArmature.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using CustomizePlus.Data.Profile; +using CustomizePlus.Helpers; + +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Logging; + +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; + +namespace CustomizePlus.Data.Armature +{ + public unsafe class CharacterArmature : Armature + { + /// + /// Gets the Customize+ profile for which this mockup applies transformations. + /// + public CharacterProfile Profile { get; init; } + + public CharacterArmature(CharacterProfile prof) : base() + { + Profile = prof; + //cross-link the two, though I'm not positive the profile ever needs to refer back + Profile.Armature = this; + + TryLinkSkeleton(); + + PluginLog.LogDebug($"Instantiated {this}, attached to {Profile}"); + } + + /// + public override string ToString() + { + return Built + ? $"Armature (#{_localId:00000}) on {Profile.CharacterName} with {BoneCount()} bone/s" + : $"Armature (#{_localId:00000}) on {Profile.CharacterName} with no skeleton reference"; + } + + protected override void SetFrozenStatus(bool value) + { + base.SetFrozenStatus(value && Profile == Plugin.ProfileManager.ProfileOpenInEditor); + } + + /// + /// Returns whether or not a link can be established between the armature and an in-game object. + /// If unbuilt, the armature will use this opportunity to rebuild itself. + /// + public unsafe CharacterBase* TryLinkSkeleton(bool forceRebuild = false) + { + try + { + if (GameDataHelper.TryLookupCharacterBase(Profile.CharacterName, out CharacterBase* cBase) + && cBase != null) + { + if (!Built || forceRebuild) + { + RebuildSkeleton(cBase); + } + return cBase; + } + } + catch (Exception ex) + { + PluginLog.LogError($"Error occured while attempting to link skeleton '{this}': {ex}"); + } + + return null; + } + + public override void UpdateOrDeleteRecord(string recordKey, BoneTransform? trans) + { + if (trans == null) + { + Profile.Bones.Remove(recordKey); + } + else + { + Profile.Bones[recordKey] = trans; + } + } + public override unsafe void RebuildSkeleton(CharacterBase* cBase) + { + if (cBase == null) + return; + + List> newPartials = ParseBonesFromObject(this, cBase, Profile.Bones); + + _partialSkeletons = newPartials.Select(x => x.ToArray()).ToArray(); + + List weapons = new(); + if (WeaponArmature.CreateMainHand(this, cBase) is WeaponArmature main && main != null) + weapons.Add(main); + if (WeaponArmature.CreateOffHand(this, cBase) is WeaponArmature off && off != null) + weapons.Add(off); + + if (weapons.Any()) _subArmatures = weapons.ToArray(); + + PluginLog.LogDebug($"Rebuilt {this}"); + } + } +} diff --git a/CustomizePlus/Data/Armature/ModelBone.cs b/CustomizePlus/Data/Armature/ModelBone.cs index ce8cbfc..39907c4 100644 --- a/CustomizePlus/Data/Armature/ModelBone.cs +++ b/CustomizePlus/Data/Armature/ModelBone.cs @@ -39,7 +39,7 @@ public unsafe class ModelBone /// public ModelBone? ParentBone => (_parentPartialIndex >= 0 && _parentPartialIndex < MasterArmature.PartialSkeletonCount && _parentBoneIndex >= 0 && _parentBoneIndex < MasterArmature.GetBoneCountOfPartial(_parentPartialIndex)) - ? MasterArmature[_parentPartialIndex, _parentBoneIndex] + ? MasterArmature.GetBoneAt(_parentPartialIndex, _parentBoneIndex) : null; private int _parentPartialIndex = -1; private int _parentBoneIndex = -1; @@ -48,7 +48,7 @@ public unsafe class ModelBone /// Gets each model bone for which this model bone corresponds to a direct parent thereof. /// A model bone may have zero children. /// - public IEnumerable ChildBones => _childPartialIndices.Zip(_childBoneIndices, (x, y) => MasterArmature[x, y]); + public IEnumerable ChildBones => _childPartialIndices.Zip(_childBoneIndices, MasterArmature.GetBoneAt); private List _childPartialIndices = new(); private List _childBoneIndices = new(); @@ -56,7 +56,7 @@ public unsafe class ModelBone /// Gets the model bone that forms a mirror image of this model bone, if one exists. /// public ModelBone? TwinBone => (_twinPartialIndex >= 0 && _twinBoneIndex >= 0) - ? MasterArmature[_twinPartialIndex, _twinBoneIndex] + ? MasterArmature.GetBoneAt(_twinPartialIndex, _twinBoneIndex) : null; private int _twinPartialIndex = -1; private int _twinBoneIndex = -1; @@ -64,7 +64,8 @@ public unsafe class ModelBone /// /// The name of the bone within the in-game skeleton. Referred to in some places as its "code name". /// - public string BoneName; + public string BoneName { get; } + public BoneData.BoneFamily FamilyName; /// /// The transform that this model bone will impart upon its in-game sibling when the master armature @@ -83,6 +84,7 @@ public ModelBone(Armature arm, string codeName, int partialIdx, int boneIdx) BoneIndex = boneIdx; BoneName = codeName; + FamilyName = BoneData.GetBoneFamily(codeName); CustomizedTransform = new(); } @@ -128,14 +130,7 @@ protected virtual void UpdateTransformation(BoneTransform newTransform) //the model bones should(?) be the same, by reference //but we still may need to delete them - if (newTransform.IsEdited()) - { - MasterArmature.Profile.Bones[BoneName] = new(newTransform); - } - else - { - MasterArmature.Profile.Bones.Remove(BoneName); - } + MasterArmature.UpdateOrDeleteRecord(BoneName, newTransform.IsEdited() ? newTransform : null); } public override string ToString() diff --git a/CustomizePlus/Data/Armature/WeaponArmature.cs b/CustomizePlus/Data/Armature/WeaponArmature.cs new file mode 100644 index 0000000..a1d57ea --- /dev/null +++ b/CustomizePlus/Data/Armature/WeaponArmature.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using CustomizePlus.Data.Profile; +using CustomizePlus.Extensions; +using CustomizePlus.Helpers; + +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Logging; + +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; + +namespace CustomizePlus.Data.Armature +{ + public unsafe class WeaponArmature : Armature + { + public CharacterArmature ParentArmature; + private bool _mainHand; + private bool _offHand => !_mainHand; + + private WeaponArmature(CharacterArmature chArm, bool mainHand) : base() + { + ParentArmature = chArm; + _mainHand = mainHand; + } + + public static WeaponArmature CreateMainHand(CharacterArmature arm, CharacterBase* cBase) + { + WeaponArmature output = new WeaponArmature(arm, true); + output.RebuildSkeleton(cBase); + return output; + } + public static WeaponArmature CreateOffHand(CharacterArmature arm, CharacterBase* cBase) + { + WeaponArmature output = new WeaponArmature(arm, false); + output.RebuildSkeleton(cBase); + return output; + } + + public override void UpdateOrDeleteRecord(string recordKey, BoneTransform? trans) + { + UpdateOrDeleteRecord(recordKey, trans, _mainHand + ? ParentArmature.Profile.MHBones + : ParentArmature.Profile.OHBones); + } + + private static void UpdateOrDeleteRecord(string key, BoneTransform? trans, Dictionary records) + { + if (trans == null) + records.Remove(key); + else + records[key] = trans; + } + + public override unsafe void RebuildSkeleton(CharacterBase* cBase) + { + if (cBase == null) + return; + + List> newPartials = _mainHand + ? ParseBonesFromObject(this, cBase->GetChild1(), ParentArmature.Profile.MHBones) + : ParseBonesFromObject(this, cBase->GetChild2(), ParentArmature.Profile.OHBones); + + _partialSkeletons = newPartials.Select(x => x.ToArray()).ToArray(); + + foreach(ModelBone mb in newPartials.SelectMany(x => x)) + { + mb.FamilyName = _mainHand ? BoneData.BoneFamily.MainHand : BoneData.BoneFamily.OffHand; + } + + PluginLog.LogDebug($"Rebuilt {this}"); + } + + public override void ApplyTransformation(CharacterBase* cBase, bool applyScaling) + { + base.ApplyTransformation(_mainHand ? cBase->GetChild1() : cBase->GetChild2(), applyScaling); + } + + /// + public override string ToString() + { + return Built + ? $"Armature (#{_localId:00000}) on {ParentArmature.Profile.CharacterName}'s weapon with {BoneCount()} bone/s" + : $"Armature (#{_localId:00000}) on {ParentArmature.Profile.CharacterName}'s weapon with no skeleton reference"; + } + + public override IEnumerable GetBoneTransformValues(BoneAttribute attribute, PosingSpace space) + { + foreach(ModelBone mb in GetLocalAndDownstreamBones().Where(x => x is not PartialRootBone)) + { + TransformInfo trInfo = new(this, mb, attribute, space); + trInfo.BoneDisplayName = $"{(_mainHand ? "Main Hand" : "Off Hand")} {trInfo.BoneDisplayName}"; + trInfo.BoneFamilyName = _mainHand ? BoneData.BoneFamily.MainHand : BoneData.BoneFamily.OffHand; + + yield return trInfo; + } + } + } +} diff --git a/CustomizePlus/Data/BoneData.cs b/CustomizePlus/Data/BoneData.cs index 06494fa..7318aae 100644 --- a/CustomizePlus/Data/BoneData.cs +++ b/CustomizePlus/Data/BoneData.cs @@ -427,6 +427,8 @@ private static BoneFamily ParseFamilyName(string n) "armor" => BoneFamily.Armor, "skirt" => BoneFamily.Skirt, "equipment" => BoneFamily.Equipment, + "mainhand" => BoneFamily.MainHand, + "offhand" => BoneFamily.OffHand, _ => BoneFamily.Unknown }; diff --git a/CustomizePlus/Data/Profile/CharacterProfile.cs b/CustomizePlus/Data/Profile/CharacterProfile.cs index fde33e2..c9dcf46 100644 --- a/CustomizePlus/Data/Profile/CharacterProfile.cs +++ b/CustomizePlus/Data/Profile/CharacterProfile.cs @@ -22,7 +22,7 @@ public sealed class CharacterProfile : IBoneContainer [NonSerialized] private readonly int _localId; - [NonSerialized] public Armature.Armature? Armature; + [NonSerialized] public CharacterArmature? Armature; [NonSerialized] public string? OriginalFilePath; @@ -63,6 +63,8 @@ public CharacterProfile(CharacterProfile original) : this() [JsonIgnore] public int UniqueId => CreationDate.GetHashCode(); public Dictionary Bones { get; init; } = new(); + public Dictionary MHBones { get; init; } = new(); + public Dictionary OHBones { get; init; } = new(); /// /// Returns whether or not this profile applies to the object with the indicated name. diff --git a/CustomizePlus/Data/TransformInfo.cs b/CustomizePlus/Data/TransformInfo.cs index 070878e..9ec6dff 100644 --- a/CustomizePlus/Data/TransformInfo.cs +++ b/CustomizePlus/Data/TransformInfo.cs @@ -18,7 +18,8 @@ public class TransformInfo private IBoneContainer _sourceContainer; public string BoneCodeName { get; } - public string BoneDisplayName => BoneData.GetBoneDisplayName(BoneCodeName); + public string BoneDisplayName { get; set; } + public BoneData.BoneFamily BoneFamilyName { get; set; } public Vector3 TransformationValue { get; set; } public BoneAttribute Attribute { get; } @@ -30,6 +31,9 @@ private TransformInfo(IBoneContainer container, string codename, BoneAttribute a BoneCodeName = codename; Attribute = att; ReferenceFrame = ps; + + BoneDisplayName = BoneData.GetBoneDisplayName(BoneCodeName); + BoneFamilyName = BoneData.GetBoneFamily(BoneCodeName); } /// diff --git a/CustomizePlus/Extensions/CharacterBaseExtensions.cs b/CustomizePlus/Extensions/CharacterBaseExtensions.cs new file mode 100644 index 0000000..fbd4340 --- /dev/null +++ b/CustomizePlus/Extensions/CharacterBaseExtensions.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using System.Runtime.InteropServices; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using CustomizePlus.Data.Armature; +using Lumina.Excel.GeneratedSheets; + +namespace CustomizePlus.Extensions +{ + //Thanks to Ktisis contributors for discovering some of these previously-undocumented class members. + public static class CharacterBaseExtensions + { + public static unsafe float* Height(this CharacterBase cBase) + { + return (float*)(new nint(&cBase) + 0x274); + } + + public static unsafe CharacterBase* GetChild1(this CharacterBase cBase) + { + if (cBase.DrawObject.Object.ChildObject != null) + { + CharacterBase* child1 = (CharacterBase*)cBase.DrawObject.Object.ChildObject; + + if (child1 != null + && child1->GetModelType() == CharacterBase.ModelType.Weapon + && child1->Skeleton->PartialSkeletonCount > 0) + { + return child1; + } + } + + return null; + } + + public static unsafe CharacterBase* GetChild2(this CharacterBase cBase) + { + CharacterBase* child1 = cBase.GetChild1(); + + if (child1 != null) + { + CharacterBase* child2 = (CharacterBase*)child1->DrawObject.Object.NextSiblingObject; + + if (child2 != null + && child1 != child2 + && child2->GetModelType() == CharacterBase.ModelType.Weapon + && child2->Skeleton->PartialSkeletonCount > 0) + { + return child2; + } + } + + return null; + } + } +} diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index 45416fd..cd5661d 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -187,11 +187,11 @@ protected unsafe override void DrawContents() ImGui.TableNextColumn(); ImGui.TableNextColumn(); - var tempRefSnap = _settings.ArmatureInProgress?.SnapToReferencePose ?? false; + var tempRefSnap = _settings.ArmatureInProgress?.FrozenPose ?? false; if (_settings.ArmatureInProgress != null && CtrlHelper.Checkbox("A-Pose", ref tempRefSnap)) { ConfirmSkeletonConnection(); - _settings.ArmatureInProgress.SnapToReferencePose = tempRefSnap; + _settings.ArmatureInProgress.FrozenPose = tempRefSnap; } CtrlHelper.AddHoverText($"Force character into their default reference pose"); @@ -256,7 +256,7 @@ protected unsafe override void DrawContents() : _settings.ProfileInProgress; var groupedBones = container.GetBoneTransformValues(_settings.EditingAttribute, _settings.ReferenceFrame) - .GroupBy(x => BoneData.GetBoneFamily(x.BoneCodeName)).ToList(); + .GroupBy(x => x.BoneFamilyName).ToList(); foreach (var boneGroup in groupedBones.OrderBy(x => (int)x.Key)) { @@ -353,10 +353,10 @@ protected unsafe override void DrawContents() ConfirmationDialog.Show("Revert all unsaved work?", () => { - bool useAPose = _settings.ArmatureInProgress.SnapToReferencePose; + bool useAPose = _settings.ArmatureInProgress.FrozenPose; Plugin.ProfileManager.RevertWorkingCopy(); Plugin.ArmatureManager.ConstructArmatureForProfile(_settings.ProfileInProgress, true); - _settings.ArmatureInProgress.SnapToReferencePose = useAPose; + _settings.ArmatureInProgress.FrozenPose = useAPose; _dirty = false; }); } @@ -585,7 +585,7 @@ public struct EditorSessionSettings private string? _originalCharName; private string? _originalProfName; - public Armature ArmatureInProgress => ProfileInProgress.Armature; + public CharacterArmature ArmatureInProgress => ProfileInProgress.Armature; public bool ShowLiveBones = false; public bool MirrorModeEnabled = false; diff --git a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs index 843e36b..be8d5dc 100644 --- a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs +++ b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs @@ -22,9 +22,7 @@ namespace CustomizePlus.UI.Windows.Debug internal unsafe class BoneMonitorWindow : WindowBase { private readonly Dictionary _groupExpandedState = new(); - private readonly bool _modelFrozen = false; private PosingSpace _targetPose; - private bool _aggregateDeforms; private BoneAttribute _targetAttribute; @@ -47,7 +45,7 @@ public static void Show(CharacterProfile prof) editWnd._targetProfile = prof; - Plugin.ArmatureManager.RenderCharacterProfiles(prof); + //Plugin.ArmatureManager.RenderCharacterProfiles(prof); editWnd._targetArmature = prof.Armature; } @@ -141,9 +139,9 @@ protected override void DrawContents() if (_targetArmature != null && targetObject != null) { - IEnumerable relevantModelBones = _targetArmature.GetAllBones(); + IEnumerable relevantModelBones = _targetArmature.GetLocalAndDownstreamBones(); - var groupedBones = relevantModelBones.GroupBy(x => BoneData.GetBoneFamily(x.BoneName)); + var groupedBones = relevantModelBones.GroupBy(x => x.FamilyName); foreach (var boneGroup in groupedBones.OrderBy(x => (int)x.Key)) { @@ -166,7 +164,9 @@ protected override void DrawContents() if (expanded) { - foreach (ModelBone mb in boneGroup.OrderBy(x => BoneData.GetBoneRanking(x.BoneName))) + foreach (ModelBone mb in boneGroup + .OrderBy(x => x.PartialSkeletonIndex) + .ThenBy(x => x.BoneIndex)) { RenderTransformationInfo(mb, targetObject); } @@ -182,9 +182,34 @@ protected override void DrawContents() ImGui.Separator(); //---------------------------------- + if (ImGui.BeginTable("Footer", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoClip)) + { + ImGui.TableSetupColumn("HeightInfo", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("VarSpace", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Close", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Bumper", ImGuiTableColumnFlags.WidthFixed); + + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + + if (targetObject != null) + { + CtrlHelper.StaticLabel($"Character Height: {targetObject->Height()->ToString()}"); + } + + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + + if (ImGui.Button("Close")) + Close(); + + ImGui.TableNextColumn(); + + ImGui.Dummy(new(CtrlHelper.IconButtonWidth, 0)); + + ImGui.EndTable(); + } - if (ImGui.Button("Cancel")) - Close(); } public void DisplayNoLinkMsg() From 51578ade8c25d7b02084e7e7d8d5039ecf5d34f6 Mon Sep 17 00:00:00 2001 From: Dendroid Date: Fri, 30 Jun 2023 00:22:33 -0400 Subject: [PATCH 36/37] Wrap up some changes for weapon edits. Adjust debug output in a few places. --- CustomizePlus/Data/Armature/Armature.cs | 14 ++++++++++ .../Data/Armature/CharacterArmature.cs | 3 +-- CustomizePlus/Data/Armature/ModelRootBone.cs | 5 ++++ CustomizePlus/Data/Armature/WeaponArmature.cs | 9 +++---- .../Extensions/CharacterBaseExtensions.cs | 27 +++++++++++++++---- CustomizePlus/UI/Windows/BoneEditWindow.cs | 1 + .../UI/Windows/Debug/BoneMonitorWindow.cs | 2 +- 7 files changed, 48 insertions(+), 13 deletions(-) diff --git a/CustomizePlus/Data/Armature/Armature.cs b/CustomizePlus/Data/Armature/Armature.cs index 38a5e12..6f07824 100644 --- a/CustomizePlus/Data/Armature/Armature.cs +++ b/CustomizePlus/Data/Armature/Armature.cs @@ -248,6 +248,20 @@ protected static unsafe List> ParseBonesFromObject(Armature arm, } BoneData.LogNewBones(newPartials.SelectMany(x => x.Select(y => y.BoneName)).ToArray()); + + if (newPartials.Any()) + { + PluginLog.LogDebug($"Rebuilt {arm}"); + PluginLog.LogDebug($"Height: {cBase->Height()}"); + PluginLog.LogDebug($"Attachment Info:"); + PluginLog.LogDebug($"\t Type: {cBase->AttachType()}"); + PluginLog.LogDebug($"\tTarget: {(cBase->AttachTarget() == null ? "N/A" : cBase->AttachTarget()->PartialSkeletonCount)} partial/s"); + PluginLog.LogDebug($"\tParent: {(cBase->AttachParent() == null ? "N/A" : cBase->AttachParent()->PartialSkeletonCount)} partial/s"); + PluginLog.LogDebug($"\t Count: {cBase->AttachCount()}"); + PluginLog.LogDebug($"\tBoneID: {cBase->AttachBoneID()}"); + PluginLog.LogDebug($"\t Scale: {cBase->AttachBoneScale()}"); + } + } catch (Exception ex) { diff --git a/CustomizePlus/Data/Armature/CharacterArmature.cs b/CustomizePlus/Data/Armature/CharacterArmature.cs index 1f405b2..091ddf8 100644 --- a/CustomizePlus/Data/Armature/CharacterArmature.cs +++ b/CustomizePlus/Data/Armature/CharacterArmature.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using CustomizePlus.Data.Profile; +using CustomizePlus.Extensions; using CustomizePlus.Helpers; using Dalamud.Game.ClientState.Objects.Types; @@ -98,8 +99,6 @@ public override unsafe void RebuildSkeleton(CharacterBase* cBase) weapons.Add(off); if (weapons.Any()) _subArmatures = weapons.ToArray(); - - PluginLog.LogDebug($"Rebuilt {this}"); } } } diff --git a/CustomizePlus/Data/Armature/ModelRootBone.cs b/CustomizePlus/Data/Armature/ModelRootBone.cs index 1fa1495..d7b73df 100644 --- a/CustomizePlus/Data/Armature/ModelRootBone.cs +++ b/CustomizePlus/Data/Armature/ModelRootBone.cs @@ -68,6 +68,11 @@ protected override unsafe void SetGameTransform(CharacterBase* cBase, hkQsTransf cBase->Skeleton->Transform = tr; + //if (cBase->AttachType() == 4 && cBase->AttachCount() == 1) + //{ + // cBase->Skeleton->Transform.Scale *= cBase->AttachBoneScale() * cBase->AttachParent()->Owner->Height(); + //} + //CharacterBase* child1 = (CharacterBase*)cBase->DrawObject.Object.ChildObject; //if (child1 != null && child1->GetModelType() == CharacterBase.ModelType.Weapon) //{ diff --git a/CustomizePlus/Data/Armature/WeaponArmature.cs b/CustomizePlus/Data/Armature/WeaponArmature.cs index a1d57ea..c2337e7 100644 --- a/CustomizePlus/Data/Armature/WeaponArmature.cs +++ b/CustomizePlus/Data/Armature/WeaponArmature.cs @@ -71,8 +71,6 @@ public override unsafe void RebuildSkeleton(CharacterBase* cBase) { mb.FamilyName = _mainHand ? BoneData.BoneFamily.MainHand : BoneData.BoneFamily.OffHand; } - - PluginLog.LogDebug($"Rebuilt {this}"); } public override void ApplyTransformation(CharacterBase* cBase, bool applyScaling) @@ -83,9 +81,10 @@ public override void ApplyTransformation(CharacterBase* cBase, bool applyScaling /// public override string ToString() { - return Built - ? $"Armature (#{_localId:00000}) on {ParentArmature.Profile.CharacterName}'s weapon with {BoneCount()} bone/s" - : $"Armature (#{_localId:00000}) on {ParentArmature.Profile.CharacterName}'s weapon with no skeleton reference"; + string wep = _mainHand ? "MH" : "OH"; + string bInf = Built ? $"{BoneCount()} bone/s" : "no skeleton reference"; + + return $"Armature (#{_localId:00000}) on {ParentArmature.Profile.CharacterName}'s {wep} weapon with {bInf}"; } public override IEnumerable GetBoneTransformValues(BoneAttribute attribute, PosingSpace space) diff --git a/CustomizePlus/Extensions/CharacterBaseExtensions.cs b/CustomizePlus/Extensions/CharacterBaseExtensions.cs index fbd4340..5753b7e 100644 --- a/CustomizePlus/Extensions/CharacterBaseExtensions.cs +++ b/CustomizePlus/Extensions/CharacterBaseExtensions.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using CustomizePlus.Data.Armature; using Lumina.Excel.GeneratedSheets; @@ -14,11 +15,6 @@ namespace CustomizePlus.Extensions //Thanks to Ktisis contributors for discovering some of these previously-undocumented class members. public static class CharacterBaseExtensions { - public static unsafe float* Height(this CharacterBase cBase) - { - return (float*)(new nint(&cBase) + 0x274); - } - public static unsafe CharacterBase* GetChild1(this CharacterBase cBase) { if (cBase.DrawObject.Object.ChildObject != null) @@ -55,5 +51,26 @@ public static class CharacterBaseExtensions return null; } + public static unsafe float Height(this CharacterBase cBase) + { + return *(float*)(new nint(&cBase) + 0x274); + } + + private unsafe static nint GetAttachPtr(CharacterBase cBase) + { + return new nint(&cBase) + 0x0D0; + } + + private unsafe static nint GetBoneAttachPtr(CharacterBase cBase) + { + return *(nint*)(GetAttachPtr(cBase) + 0x70); + } + + public static unsafe uint AttachType(this CharacterBase cBase) => *(uint*)(GetAttachPtr(cBase) + 0x50); + public static unsafe Skeleton* AttachTarget(this CharacterBase cBase) => *(Skeleton**)(GetAttachPtr(cBase) + 0x58); + public static unsafe Skeleton* AttachParent(this CharacterBase cBase) => *(Skeleton**)(GetAttachPtr(cBase) + 0x60); + public static unsafe uint AttachCount(this CharacterBase cBase) => *(uint*)(GetAttachPtr(cBase) + 0x68); + public static unsafe ushort AttachBoneID(this CharacterBase cBase) => *(ushort*)(GetBoneAttachPtr(cBase) + 0x02); + public static unsafe float AttachBoneScale(this CharacterBase cBase) => *(float*)(GetBoneAttachPtr(cBase) + 0x30); } } diff --git a/CustomizePlus/UI/Windows/BoneEditWindow.cs b/CustomizePlus/UI/Windows/BoneEditWindow.cs index cd5661d..75667b8 100644 --- a/CustomizePlus/UI/Windows/BoneEditWindow.cs +++ b/CustomizePlus/UI/Windows/BoneEditWindow.cs @@ -205,6 +205,7 @@ protected unsafe override void DrawContents() } CtrlHelper.AddHoverText($"Bone changes will be reflected from left to right and vice versa"); + if (!_settings.ShowLiveBones) ImGui.EndDisabled(); //ImGui.TableNextColumn(); //if (CtrlHelper.Checkbox("Parenting Mode", ref _settings.ParentingEnabled)) diff --git a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs index be8d5dc..c574f52 100644 --- a/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs +++ b/CustomizePlus/UI/Windows/Debug/BoneMonitorWindow.cs @@ -194,7 +194,7 @@ protected override void DrawContents() if (targetObject != null) { - CtrlHelper.StaticLabel($"Character Height: {targetObject->Height()->ToString()}"); + CtrlHelper.StaticLabel($"Character Height: {targetObject->Height().ToString()}"); } ImGui.TableNextColumn(); From 8e57008f3dd4825946c7dcf1f132c443cad55d16 Mon Sep 17 00:00:00 2001 From: Stoia Date: Fri, 30 Jun 2023 15:53:28 +0200 Subject: [PATCH 37/37] Fix Post merge --- CustomizePlus/Helpers/GameDataHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CustomizePlus/Helpers/GameDataHelper.cs b/CustomizePlus/Helpers/GameDataHelper.cs index de0e1f5..db1aff2 100644 --- a/CustomizePlus/Helpers/GameDataHelper.cs +++ b/CustomizePlus/Helpers/GameDataHelper.cs @@ -84,7 +84,7 @@ public static unsafe bool TryLookupCharacterBase(string name, out CharacterBase* cBase = anyObj.ToCharacterBase(); return true; } - else if (FindModelByName(name) is DalamudObject obj + else if (FindGameObjectByName(name) is DalamudObject obj && obj.Address is nint objPtr && objPtr != nint.Zero) {