From 4422fe6970df75628bb8513be6a46982b18dfb72 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 4 Sep 2024 20:21:19 +0900 Subject: [PATCH 01/15] feat: initial commit for fix yaw / pitch --- .../.MergePhysBoneEditorModificationUtils.ts | 9 -- Editor/Inspector/MergePhysBoneEditor.cs | 97 ++++++++++++++----- .../MergePhysBoneEditorModificationUtils.cs | 55 +++++++++++ ...ysBoneEditorModificationUtils.generated.cs | 45 --------- Editor/Processors/MergePhysBoneProcessor.cs | 71 ++++++++++---- Localization/en-us.po | 3 + Runtime/MergePhysBone.cs | 10 +- 7 files changed, 191 insertions(+), 99 deletions(-) diff --git a/Editor/.MergePhysBoneEditorModificationUtils.ts b/Editor/.MergePhysBoneEditorModificationUtils.ts index 8174d252f..120a5a5c2 100644 --- a/Editor/.MergePhysBoneEditorModificationUtils.ts +++ b/Editor/.MergePhysBoneEditorModificationUtils.ts @@ -14,15 +14,6 @@ const config: Config = { ['Curve', 'curve'], ], }, - CurveVector3ConfigProp: { - base: "OverridePropBase", - values: [ - ['Value', 'value'], - ['CurveX', 'curveX'], - ['CurveY', 'curveY'], - ['CurveZ', "curveZ"] - ], - }, PermissionConfigProp: { base: "OverridePropBase", values: [ diff --git a/Editor/Inspector/MergePhysBoneEditor.cs b/Editor/Inspector/MergePhysBoneEditor.cs index e41b187df..30f048d06 100644 --- a/Editor/Inspector/MergePhysBoneEditor.cs +++ b/Editor/Inspector/MergePhysBoneEditor.cs @@ -307,14 +307,58 @@ protected override void Pb3DCurveProp(string label, string pbXCurveLabel, string pbYCurveLabel, string pbZCurveLabel, CurveVector3ConfigProp prop, bool forceOverride = false) { - PbPropImpl(label, prop, forceOverride, (rect, merged, labelContent) => + var (rect, overrideRect) = SplitRect(EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight), OverrideWidth); + + switch (prop.GetOverride(forceOverride)) { - var (valueRect, buttonRect) = SplitRect(rect, CurveButtonWidth); + case MergePhysBone.CurveVector3Config.CurveOverride.Copy: + { + var valueProp = prop.SourceValue!; + var xCurveProp = prop.SourceCurveX!; + var yCurveProp = prop.SourceCurveY!; + var zCurveProp = prop.SourceCurveZ!; - var valueProp = prop.GetValueProperty(merged); - var xCurveProp = prop.GetCurveXProperty(merged); - var yCurveProp = prop.GetCurveYProperty(merged); - var zCurveProp = prop.GetCurveZProperty(merged); + EditorGUI.BeginDisabledGroup(true); + DrawProperties(rect, new GUIContent(label), valueProp, xCurveProp, yCurveProp, zCurveProp); + EditorGUI.EndDisabledGroup(); + + if (valueProp.hasMultipleDifferentValues + || xCurveProp.hasMultipleDifferentValues + || yCurveProp.hasMultipleDifferentValues + || zCurveProp.hasMultipleDifferentValues) + { + EditorGUILayout.HelpBox(AAOL10N.Tr("MergePhysBone:error:differValueSingle"), MessageType.Error); + } + } + break; + case MergePhysBone.CurveVector3Config.CurveOverride.Override: + { + var valueProp = prop.OverrideValue; + var xCurveProp = prop.OverrideCurveX; + var yCurveProp = prop.OverrideCurveY; + var zCurveProp = prop.OverrideCurveZ; + + DrawProperties(rect, new GUIContent(label), valueProp, xCurveProp, yCurveProp, zCurveProp); + } + break; + case MergePhysBone.CurveVector3Config.CurveOverride.Fix: + { + EditorGUI.LabelField(rect, label, AAOL10N.Tr("MergePhysBone:message:fix-yaw-pitch")); + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + + EditorGUI.BeginProperty(overrideRect, null, prop.OverrideProperty); + var selected = PopupNoIndent(overrideRect, prop.OverrideProperty.enumValueIndex, prop.OverrideProperty.enumDisplayNames); + if (selected != prop.OverrideProperty.enumValueIndex) + prop.OverrideProperty.enumValueIndex = selected; + EditorGUI.EndProperty(); + + void DrawProperties(Rect rect, GUIContent labelContent, SerializedProperty valueProp, SerializedProperty xCurveProp, SerializedProperty yCurveProp, SerializedProperty zCurveProp) + { + var (valueRect, buttonRect) = SplitRect(rect, CurveButtonWidth); void DrawCurve(string curveLabel, SerializedProperty curveProp) { @@ -337,7 +381,7 @@ void DrawCurve(string curveLabel, SerializedProperty curveProp) { // without curve: constant EditorGUI.PropertyField(valueRect, valueProp, labelContent); - + if (GUI.Button(buttonRect, "C")) { var curve = new AnimationCurve(); @@ -348,12 +392,7 @@ void DrawCurve(string curveLabel, SerializedProperty curveProp) zCurveProp.animationCurveValue = curve; } } - - return valueProp.hasMultipleDifferentValues - || xCurveProp.hasMultipleDifferentValues - || yCurveProp.hasMultipleDifferentValues - || zCurveProp.hasMultipleDifferentValues; - }); + } } private static readonly string[] CopyOverride = { "C:Copy", "O:Override" }; @@ -646,17 +685,27 @@ protected override void Pb3DCurveProp(string label, string pbXCurveLabel, string pbYCurveLabel, string pbZCurveLabel, CurveVector3ConfigProp prop, bool forceOverride = false) { - if (forceOverride || prop.IsOverride) return; - - if (prop.SourceValue!.hasMultipleDifferentValues - || prop.SourceCurveX!.hasMultipleDifferentValues - || prop.SourceCurveY!.hasMultipleDifferentValues - || prop.SourceCurveZ!.hasMultipleDifferentValues) - _differProps.Add(label); - - _usingCopyCurve |= prop.GetCurveXProperty(false).animationCurveValue.length > 0; - _usingCopyCurve |= prop.GetCurveYProperty(false).animationCurveValue.length > 0; - _usingCopyCurve |= prop.GetCurveZProperty(false).animationCurveValue.length > 0; + switch (prop.GetOverride(forceOverride)) + { + case MergePhysBone.CurveVector3Config.CurveOverride.Copy: + if (prop.SourceValue!.hasMultipleDifferentValues + || prop.SourceCurveX!.hasMultipleDifferentValues + || prop.SourceCurveY!.hasMultipleDifferentValues + || prop.SourceCurveZ!.hasMultipleDifferentValues) + _differProps.Add(label); + + _usingCopyCurve |= prop.SourceCurveX!.animationCurveValue.length > 0; + _usingCopyCurve |= prop.SourceCurveY!.animationCurveValue.length > 0; + _usingCopyCurve |= prop.SourceCurveZ!.animationCurveValue.length > 0; + break; + case MergePhysBone.CurveVector3Config.CurveOverride.Override: + break; + case MergePhysBone.CurveVector3Config.CurveOverride.Fix: + // more validation about skew scaling and rotation animation on build + break; + default: + throw new ArgumentOutOfRangeException(); + } } protected override void PbPermissionProp(string label, PermissionConfigProp prop, bool forceOverride = false) diff --git a/Editor/MergePhysBoneEditorModificationUtils.cs b/Editor/MergePhysBoneEditorModificationUtils.cs index 2d4b74c1f..4beeccf4f 100644 --- a/Editor/MergePhysBoneEditorModificationUtils.cs +++ b/Editor/MergePhysBoneEditorModificationUtils.cs @@ -377,6 +377,61 @@ internal override void UpdateSource(SerializedObject sourcePb) PhysBoneValue = sourcePb.FindProperty(PhysBoneValueName); } } + + // Very Special Case + protected partial class CurveVector3ConfigProp : PropBase + { + public readonly SerializedProperty OverrideProperty; + public readonly SerializedProperty OverrideValue; + public SerializedProperty? SourceValue { get; private set; } + public readonly string PhysBoneValueName; + public readonly SerializedProperty OverrideCurveX; + public SerializedProperty? SourceCurveX { get; private set; } + public readonly string PhysBoneCurveXName; + public readonly SerializedProperty OverrideCurveY; + public SerializedProperty? SourceCurveY { get; private set; } + public readonly string PhysBoneCurveYName; + public readonly SerializedProperty OverrideCurveZ; + public SerializedProperty? SourceCurveZ { get; private set; } + public readonly string PhysBoneCurveZName; + + public CurveVector3ConfigProp( + SerializedProperty rootProperty + , string physBoneValueName + , string physBoneCurveXName + , string physBoneCurveYName + , string physBoneCurveZName + ) : base(rootProperty) + { + OverrideProperty = rootProperty.FindPropertyRelative("override"); + OverrideValue = rootProperty.FindPropertyRelative("value"); + PhysBoneValueName = physBoneValueName; + OverrideCurveX = rootProperty.FindPropertyRelative("curveX"); + PhysBoneCurveXName = physBoneCurveXName; + OverrideCurveY = rootProperty.FindPropertyRelative("curveY"); + PhysBoneCurveYName = physBoneCurveYName; + OverrideCurveZ = rootProperty.FindPropertyRelative("curveZ"); + PhysBoneCurveZName = physBoneCurveZName; + } + + internal override void UpdateSource(SerializedObject sourcePb) + { + SourceValue = sourcePb.FindProperty(PhysBoneValueName); + SourceCurveX = sourcePb.FindProperty(PhysBoneCurveXName); + SourceCurveY = sourcePb.FindProperty(PhysBoneCurveYName); + SourceCurveZ = sourcePb.FindProperty(PhysBoneCurveZName); + } + + public MergePhysBone.CurveVector3Config.CurveOverride GetOverride(bool forceOverride) => + forceOverride + ? MergePhysBone.CurveVector3Config.CurveOverride.Override + : (MergePhysBone.CurveVector3Config.CurveOverride)OverrideProperty.enumValueIndex; + + public SerializedProperty GetValueProperty(bool @override) => @override ? OverrideValue : SourceValue!; + public SerializedProperty GetCurveXProperty(bool @override) => @override ? OverrideCurveX : SourceCurveX!; + public SerializedProperty GetCurveYProperty(bool @override) => @override ? OverrideCurveY : SourceCurveY!; + public SerializedProperty GetCurveZProperty(bool @override) => @override ? OverrideCurveZ : SourceCurveZ!; + } } } diff --git a/Editor/MergePhysBoneEditorModificationUtils.generated.cs b/Editor/MergePhysBoneEditorModificationUtils.generated.cs index 082318e2c..227565b65 100644 --- a/Editor/MergePhysBoneEditorModificationUtils.generated.cs +++ b/Editor/MergePhysBoneEditorModificationUtils.generated.cs @@ -40,51 +40,6 @@ internal override void UpdateSource(SerializedObject sourcePb) public SerializedProperty GetValueProperty(bool @override) => @override ? OverrideValue : SourceValue!; public SerializedProperty GetCurveProperty(bool @override) => @override ? OverrideCurve : SourceCurve!; } - protected partial class CurveVector3ConfigProp : OverridePropBase - { - public readonly SerializedProperty OverrideValue; - public SerializedProperty? SourceValue { get; private set; } - public readonly string PhysBoneValueName; - public readonly SerializedProperty OverrideCurveX; - public SerializedProperty? SourceCurveX { get; private set; } - public readonly string PhysBoneCurveXName; - public readonly SerializedProperty OverrideCurveY; - public SerializedProperty? SourceCurveY { get; private set; } - public readonly string PhysBoneCurveYName; - public readonly SerializedProperty OverrideCurveZ; - public SerializedProperty? SourceCurveZ { get; private set; } - public readonly string PhysBoneCurveZName; - - public CurveVector3ConfigProp( - SerializedProperty rootProperty - , string physBoneValueName - , string physBoneCurveXName - , string physBoneCurveYName - , string physBoneCurveZName - ) : base(rootProperty) - { - OverrideValue = rootProperty.FindPropertyRelative("value"); - PhysBoneValueName = physBoneValueName; - OverrideCurveX = rootProperty.FindPropertyRelative("curveX"); - PhysBoneCurveXName = physBoneCurveXName; - OverrideCurveY = rootProperty.FindPropertyRelative("curveY"); - PhysBoneCurveYName = physBoneCurveYName; - OverrideCurveZ = rootProperty.FindPropertyRelative("curveZ"); - PhysBoneCurveZName = physBoneCurveZName; - } - - internal override void UpdateSource(SerializedObject sourcePb) - { - SourceValue = sourcePb.FindProperty(PhysBoneValueName); - SourceCurveX = sourcePb.FindProperty(PhysBoneCurveXName); - SourceCurveY = sourcePb.FindProperty(PhysBoneCurveYName); - SourceCurveZ = sourcePb.FindProperty(PhysBoneCurveZName); - } - public SerializedProperty GetValueProperty(bool @override) => @override ? OverrideValue : SourceValue!; - public SerializedProperty GetCurveXProperty(bool @override) => @override ? OverrideCurveX : SourceCurveX!; - public SerializedProperty GetCurveYProperty(bool @override) => @override ? OverrideCurveY : SourceCurveY!; - public SerializedProperty GetCurveZProperty(bool @override) => @override ? OverrideCurveZ : SourceCurveZ!; - } protected partial class PermissionConfigProp : OverridePropBase { public readonly SerializedProperty OverrideValue; diff --git a/Editor/Processors/MergePhysBoneProcessor.cs b/Editor/Processors/MergePhysBoneProcessor.cs index 8257cd4ad..8783e7baa 100644 --- a/Editor/Processors/MergePhysBoneProcessor.cs +++ b/Editor/Processors/MergePhysBoneProcessor.cs @@ -75,6 +75,11 @@ internal static void DoMerge(MergePhysBone merge, BuildContext? context) } } + // yaw / pitch fix + if (merge.limitRotationConfig.@override == MergePhysBone.CurveVector3Config.CurveOverride.Fix) + foreach (var physBone in sourceComponents) + FixYawPitch(physBone, context); + // clear endpoint position if (merge.endpointPositionConfig.@override == MergePhysBone.EndPointPositionConfig.Override.Clear) foreach (var physBone in sourceComponents) @@ -143,7 +148,21 @@ internal static void DoMerge(MergePhysBone merge, BuildContext? context) } } } - + + // To preserve bone reference, we keep original bone and create new GameObject for it. + // and later Trace and Object remove unused objects will merge original bones + public static void FixYawPitch(VRCPhysBoneBase physBone, BuildContext? context) + { + // Already fixed; nothing to do! + if (physBone.limitRotation.Equals(Vector3.zero)) return; + + + physBone.InitTransforms(true); + var maxChainLength = physBone.BoneChainLength(); + + throw new NotImplementedException(); + } + private static readonly string[] TransformRotationAndPositionAnimationKeys = { "m_LocalRotation.x", "m_LocalRotation.y", "m_LocalRotation.z", "m_LocalRotation.w", @@ -236,26 +255,38 @@ protected override void PbCurveProp(string label, CurveConfigProp prop, bool for protected override void Pb3DCurveProp(string label, string pbXCurveLabel, string pbYCurveLabel, string pbZCurveLabel, CurveVector3ConfigProp prop, bool forceOverride = false) { - var @override = forceOverride || prop.IsOverride; - _mergedPhysBone.FindProperty(prop.PhysBoneValueName).vector3Value = - prop.GetValueProperty(@override).vector3Value; - if (@override) - { - _mergedPhysBone.FindProperty(prop.PhysBoneCurveXName).animationCurveValue = - prop.GetCurveXProperty(@override).animationCurveValue; - _mergedPhysBone.FindProperty(prop.PhysBoneCurveYName).animationCurveValue = - prop.GetCurveYProperty(@override).animationCurveValue; - _mergedPhysBone.FindProperty(prop.PhysBoneCurveZName).animationCurveValue = - prop.GetCurveZProperty(@override).animationCurveValue; - } - else + switch (prop.GetOverride(forceOverride)) { - _mergedPhysBone.FindProperty(prop.PhysBoneCurveXName).animationCurveValue = - FixCurve(prop.GetCurveXProperty(@override).animationCurveValue); - _mergedPhysBone.FindProperty(prop.PhysBoneCurveYName).animationCurveValue = - FixCurve(prop.GetCurveYProperty(@override).animationCurveValue); - _mergedPhysBone.FindProperty(prop.PhysBoneCurveZName).animationCurveValue = - FixCurve(prop.GetCurveZProperty(@override).animationCurveValue); + case MergePhysBone.CurveVector3Config.CurveOverride.Copy: + _mergedPhysBone.FindProperty(prop.PhysBoneValueName).vector3Value = + prop.SourceValue!.vector3Value; + _mergedPhysBone.FindProperty(prop.PhysBoneCurveXName).animationCurveValue = + FixCurve(prop.SourceCurveX!.animationCurveValue); + _mergedPhysBone.FindProperty(prop.PhysBoneCurveYName).animationCurveValue = + FixCurve(prop.SourceCurveY!.animationCurveValue); + _mergedPhysBone.FindProperty(prop.PhysBoneCurveZName).animationCurveValue = + FixCurve(prop.SourceCurveZ!.animationCurveValue); + break; + case MergePhysBone.CurveVector3Config.CurveOverride.Override: + _mergedPhysBone.FindProperty(prop.PhysBoneValueName).vector3Value = + prop.OverrideValue.vector3Value; + _mergedPhysBone.FindProperty(prop.PhysBoneCurveXName).animationCurveValue = + prop.OverrideCurveX.animationCurveValue; + _mergedPhysBone.FindProperty(prop.PhysBoneCurveYName).animationCurveValue = + prop.OverrideCurveY.animationCurveValue; + _mergedPhysBone.FindProperty(prop.PhysBoneCurveZName).animationCurveValue = + prop.OverrideCurveZ.animationCurveValue; + break; + case MergePhysBone.CurveVector3Config.CurveOverride.Fix: + // Fixing rotation is proceeded before. + // We just reset the value and curve. + _mergedPhysBone.FindProperty(prop.PhysBoneValueName).vector3Value = Vector3.zero; + _mergedPhysBone.FindProperty(prop.PhysBoneCurveXName).animationCurveValue = new AnimationCurve(); + _mergedPhysBone.FindProperty(prop.PhysBoneCurveYName).animationCurveValue = new AnimationCurve(); + _mergedPhysBone.FindProperty(prop.PhysBoneCurveZName).animationCurveValue = new AnimationCurve(); + break; + default: + throw new ArgumentOutOfRangeException(); } } diff --git a/Localization/en-us.po b/Localization/en-us.po index c31395798..de18e6f48 100644 --- a/Localization/en-us.po +++ b/Localization/en-us.po @@ -153,6 +153,9 @@ msgid "MergePhysBone:error:unsupportedPbVersion" msgstr "The PhysBone Version is not supported (yet) by Avatar Optimizer.\n" "Please tell author on twitter (@anatawa12_vrc) or GitHub (anatawa12/AvatarOptimizer)!" +msgid "MergePhysBone:message:fix-yaw-pitch" +msgstr "Fix to 0, 0, 0 by rotating bones" + msgid "MergePhysBone:error:differValues" msgstr "The values is differ between two or more sources. You have to set same value OR override this property: {0}" diff --git a/Runtime/MergePhysBone.cs b/Runtime/MergePhysBone.cs index baf0a9879..c30dceb0b 100644 --- a/Runtime/MergePhysBone.cs +++ b/Runtime/MergePhysBone.cs @@ -150,11 +150,19 @@ public struct CurveNoLimitConfig [Serializable] public struct CurveVector3Config { - public bool @override; + public CurveOverride @override; public Vector3 value; public AnimationCurve curveX; public AnimationCurve curveY; public AnimationCurve curveZ; + + public enum CurveOverride + { + Copy, + Override, + // Change bone angle to match the curve + Fix, + } } [Serializable] From c10db680fb3dd21bd2351886bda90603825d6efc Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 24 Oct 2024 22:10:34 +0900 Subject: [PATCH 02/15] feat: implement fixing roll with physBone --- Editor/Processors/MergePhysBoneProcessor.cs | 238 +++++++++++++++++- ...m.anatawa12.avatar-optimizer.editor.asmdef | 1 + 2 files changed, 231 insertions(+), 8 deletions(-) diff --git a/Editor/Processors/MergePhysBoneProcessor.cs b/Editor/Processors/MergePhysBoneProcessor.cs index 8783e7baa..c458d4dcb 100644 --- a/Editor/Processors/MergePhysBoneProcessor.cs +++ b/Editor/Processors/MergePhysBoneProcessor.cs @@ -5,6 +5,7 @@ using System.Linq; using Anatawa12.AvatarOptimizer.AnimatorParsersV2; using nadena.dev.ndmf; +using Unity.Mathematics; using UnityEditor; using UnityEngine; using VRC.Dynamics; @@ -75,11 +76,6 @@ internal static void DoMerge(MergePhysBone merge, BuildContext? context) } } - // yaw / pitch fix - if (merge.limitRotationConfig.@override == MergePhysBone.CurveVector3Config.CurveOverride.Fix) - foreach (var physBone in sourceComponents) - FixYawPitch(physBone, context); - // clear endpoint position if (merge.endpointPositionConfig.@override == MergePhysBone.EndPointPositionConfig.Override.Clear) foreach (var physBone in sourceComponents) @@ -127,6 +123,70 @@ internal static void DoMerge(MergePhysBone merge, BuildContext? context) default: throw new ArgumentOutOfRangeException(); } + // == Limits == + + // yaw / pitch fix + if (merge.limitRotationConfig.@override == MergePhysBone.CurveVector3Config.CurveOverride.Fix) + { + var newIgnores = new List(); + // fix rotations + foreach (var physBone in sourceComponents) + FixYawPitch(physBone, root, context, newIgnores); + + // fix configurations + merged.ignoreTransforms = merged.ignoreTransforms.Concat(newIgnores).ToList(); + + var sourceComponent = sourceComponents[0]; + var chainLength = sourceComponent.BoneChainLength(); + var yaws = new float[chainLength]; + float fixedRollOfLastBone = 0; + var pitches = new float[chainLength]; + + for (var i = 0; i < chainLength; i++) + { + var rotationSpecified = sourceComponent.CalcLimitRotation((float)i / (chainLength - 1)); + var rotation = ConvertRotation(rotationSpecified); + pitches[i] = rotation.x; + fixedRollOfLastBone = rotation.y; + yaws[i] = rotation.z; + } + + var maxPitch = pitches.Select(Mathf.Abs).Max(); + var maxYaw = yaws.Select(Mathf.Abs).Max(); + + merged.limitRotation = new Vector3(maxPitch, 0, maxYaw); + + if (maxPitch != 0 || maxYaw != 0) + { + // avoid NaN + if (maxPitch == 0) maxPitch = 1; + if (maxYaw == 0) maxYaw = 1; + + var pitchCurve = new AnimationCurve(); + var yawCurve = new AnimationCurve(); + + pitchCurve.AddKey(0, pitches[0] / maxPitch); + yawCurve.AddKey(0, yaws[0] / maxYaw); + + for (var i = 0; i < chainLength; i++) + { + var time = (float)(i + 1) / chainLength; + pitchCurve.AddKey(time, pitches[i] / maxPitch); + yawCurve.AddKey(time, yaws[i] / maxYaw); + } + + merged.limitRotationXCurve = pitchCurve; + merged.limitRotationZCurve = yawCurve; + } + + if (merged.endpointPosition != Vector3.zero) + { + // TODO: this Endpoint Fix might not enough + // Rotation fix will conflict with this fix + merged.endpointPosition = Quaternion.Euler(0, -fixedRollOfLastBone, 0) * merged.endpointPosition; + } + } + // == Options == merged.isAnimated = merge.isAnimatedConfig.value || sourceComponents.Any(x => x.isAnimated); @@ -151,16 +211,178 @@ internal static void DoMerge(MergePhysBone merge, BuildContext? context) // To preserve bone reference, we keep original bone and create new GameObject for it. // and later Trace and Object remove unused objects will merge original bones - public static void FixYawPitch(VRCPhysBoneBase physBone, BuildContext? context) + public static void FixYawPitch( + VRCPhysBoneBase physBone, + Transform root, + BuildContext? context, + List newIgnores) { // Already fixed; nothing to do! if (physBone.limitRotation.Equals(Vector3.zero)) return; - physBone.InitTransforms(true); var maxChainLength = physBone.BoneChainLength(); - throw new NotImplementedException(); + var ignoreTransforms = new HashSet(physBone.ignoreTransforms); + + RotateRecursive(physBone, physBone.GetTarget(), root, maxChainLength, 0, ignoreTransforms, newIgnores); + } + + /* + RotateRecursive will transform + Parent <= Parent + `- Root <= Transform + +- Bone1 + | +- Bone2 + | +- Bone3 + `- Bone4 + +- Bone5 + into + Parent + `- Root (AAO Merge Proxy) + +- Root + +- Bone1 (AAO Merge Proxy) + | +- Bone1 + | `- Bone2 (AAO Merge Proxy) + | +- Bone2 + | `- Bone3 (AAO Merge Proxy) + | +- Bone3 + `- Bone4 (AAO Merge Proxy) + +- Bone4 + `- Bone5 (AAO Merge Proxy) + +- Bone5 + + One pass of this method will transform into + + Parent + `- Root (AAO Merge Proxy) <= New Parent + `- Root + +- Bone1 <= New Transform + | +- Bone2 + | +- Bone3 + `- Bone4 <= New Transform + +- Bone5 + and calls RotateRecursive with new set of bones to complete + + */ + + private static void RotateRecursive(VRCPhysBoneBase physBone, + Transform transform, + Transform parent, + int totalDepth, + int depth, + HashSet ignoreTransforms, + List newIgnores) + { + Vector3 targetLocation; + + var activeChildren = Enumerable.Range(0, transform.childCount) + .Select(transform.GetChild) + .Where(child => !ignoreTransforms.Contains(child)) + .ToArray(); + + switch (activeChildren.Length) + { + case 0: + // end bone + if (physBone.endpointPosition != Vector3.zero) + targetLocation = physBone.endpointPosition; + else + targetLocation = Vector3.up; + break; + case 1: + targetLocation = activeChildren[0].localPosition; + break; + default: + switch (physBone.multiChildType) + { + case VRCPhysBoneBase.MultiChildType.Ignore: + targetLocation = Vector3.up; + break; + case VRCPhysBoneBase.MultiChildType.First: + targetLocation = activeChildren[0].localPosition; + break; + case VRCPhysBoneBase.MultiChildType.Average: + targetLocation = + activeChildren.Aggregate(Vector3.zero, (current, child) => current + child.localPosition) / + activeChildren.Length; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + break; + } + + var specifiedRotation = physBone.CalcLimitRotation((float)depth / totalDepth); + var rotation = ConvertRotation(specifiedRotation).y; + + // if the bone is at (0, -x, 0), we have infinite rotation for `FromToRotation` and + // `Quaternion.FromToRotation`'s choice is not happy for logic below. + // We need special handling for this case. + var dot = Vector3.Dot(Vector3.up, math.normalizesafe(targetLocation)); + var critical = dot <= -1; + + //Debug.Log($"is critical: {critical}, dot: {dot}, transform: {transform.name}"); + var thisRotation = !critical ? rotation : -rotation; + + // create new (actual) bone + var newBone = new GameObject($"{transform.name} (AAO Merge Proxy)"); + + // new bone should be at exactly same transform as the original bone + newBone.transform.parent = transform; + newBone.transform.localPosition = Vector3.zero; + newBone.transform.localRotation = Quaternion.identity; + newBone.transform.localScale = Vector3.one; + + // move to parent + newBone.transform.SetParent(parent, true); + + // rotate newBone to fix roll + newBone.transform.Rotate(Vector3.up, thisRotation, Space.Self); + + // move old bone to child of newBone + transform.SetParent(newBone.transform, true); + + newIgnores.Add(transform); + + //var rotationQuaternion = Quaternion.Euler(0, -thisRotation, 0); + + foreach (var child in activeChildren) + { + //child.localPosition = rotationQuaternion * child.localPosition; + //child.localRotation = rotationQuaternion * child.localRotation; + + if (ignoreTransforms.Contains(child)) continue; + RotateRecursive(physBone, child, newBone.transform, totalDepth, depth + 1, ignoreTransforms, newIgnores); + } + } + + private static Vector3 ConvertRotation( + Vector3 limitRotation + ) + { + // XYZ is the order used in VRCPhysBone + var quat = quaternion.EulerXYZ(limitRotation * Mathf.Deg2Rad); + return QuaternionToEulerXZY(quat) * Mathf.Rad2Deg; + } + + private static Vector3 QuaternionToEulerXZY(Quaternion q) + { + // Quaternion to Euler + // https://qiita.com/aa_debdeb/items/abe90a9bd0b4809813da + // YZX Order in the article. (XZY in Unity) + // We use different perspective to represent same order of Euler order between Unity and the article. + var sz = 2 * q.x * q.y + 2 * q.z * q.w; + var unlocked = Mathf.Abs(sz) < 0.99999f; + Debug.Log("unlocked: " + unlocked); + return new Vector3( + unlocked ? Mathf.Atan2(-(2 * q.y * q.z - 2 * q.x * q.w), 2 * q.w * q.w + 2 * q.y * q.y - 1) : 0, + unlocked + ? Mathf.Atan2(-(2 * q.x * q.z - 2 * q.y * q.w), 2 * q.w * q.w + 2 * q.x * q.x - 1) + : Mathf.Atan2(2 * q.x * q.z + 2 * q.y * q.w, 2 * q.w * q.w + 2 * q.z * q.z - 1), + Mathf.Asin(sz) + ); } private static readonly string[] TransformRotationAndPositionAnimationKeys = diff --git a/Editor/com.anatawa12.avatar-optimizer.editor.asmdef b/Editor/com.anatawa12.avatar-optimizer.editor.asmdef index 72816d14f..b27602886 100644 --- a/Editor/com.anatawa12.avatar-optimizer.editor.asmdef +++ b/Editor/com.anatawa12.avatar-optimizer.editor.asmdef @@ -14,6 +14,7 @@ "com.anatawa12.avatar-optimizer.internal.meshinfo2", "com.anatawa12.avatar-optimizer.internal.utils", "Unity.Burst", + "Unity.Mathematics", "nadena.dev.ndmf", "nadena.dev.ndmf.runtime", "nadena.dev.ndmf.reactive-query.core", From 8212b287179e488101a687faee424778c9b70215 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 25 Oct 2024 09:20:22 +0900 Subject: [PATCH 03/15] refactor: move ScaledEvenly to Utils --- Editor/Processors/MergeBoneProcessor.cs | 9 +-------- .../TraceAndOptimize/FindUnusedObjectsProcessor.cs | 2 +- Internal/Utils/Utils.cs | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Editor/Processors/MergeBoneProcessor.cs b/Editor/Processors/MergeBoneProcessor.cs index f7fbad319..158ca7925 100644 --- a/Editor/Processors/MergeBoneProcessor.cs +++ b/Editor/Processors/MergeBoneProcessor.cs @@ -33,7 +33,7 @@ public static void Validate(MergeBone mergeBone, GameObject root) if (AnyNotMergedBone(mergeBone.transform)) { // if the bone has non-merged bones, uneven scaling is not supported. - if (!ScaledEvenly(mergeBone.transform.localScale)) + if (!Utils.ScaledEvenly(mergeBone.transform.localScale)) BuildLog.LogWarning("MergeBone:validation:unevenScaling"); } @@ -48,13 +48,6 @@ bool AnyNotMergedBone(Transform bone) } } - public static bool ScaledEvenly(Vector3 localScale) - { - bool CheckScale(float scale) => 0.995 < scale && scale < 1.005; - return CheckScale(localScale.x / localScale.y) && CheckScale(localScale.x / localScale.z) && - CheckScale(localScale.y / localScale.z); - } - protected override void Execute(BuildContext context) { // merge from -> merge into diff --git a/Editor/Processors/TraceAndOptimize/FindUnusedObjectsProcessor.cs b/Editor/Processors/TraceAndOptimize/FindUnusedObjectsProcessor.cs index 6d648dc6b..02d5440ce 100644 --- a/Editor/Processors/TraceAndOptimize/FindUnusedObjectsProcessor.cs +++ b/Editor/Processors/TraceAndOptimize/FindUnusedObjectsProcessor.cs @@ -375,7 +375,7 @@ private void MergeBone(GCComponentInfoHolder componentInfos) // if this is not identity transform, animating children is not good return NotMerged(); - if (!MergeBoneProcessor.ScaledEvenly(localScale)) + if (!Utils.ScaledEvenly(localScale)) // non even scaling is not possible to reproduce in children return NotMerged(); } diff --git a/Internal/Utils/Utils.cs b/Internal/Utils/Utils.cs index e0efea931..e8cc113a8 100644 --- a/Internal/Utils/Utils.cs +++ b/Internal/Utils/Utils.cs @@ -379,5 +379,19 @@ bool ComputeSafeToUseFastImplementation() // Exception-safe swap public static void Swap(ref T a, ref T b) => (a, b) = (b, a); + + /// + /// Returns whether the given local scale is scaled evenly. + /// + /// If the scale is skewed, this returns false. + /// + /// the local scale to check + /// whether the given local scale is scaled evenly + public static bool ScaledEvenly(Vector3 localScale) + { + bool CheckScale(float scale) => 0.995 < scale && scale < 1.005; + return CheckScale(localScale.x / localScale.y) && CheckScale(localScale.x / localScale.z) && + CheckScale(localScale.y / localScale.z); + } } } From 80c0656ed207f4f6a39d0f7e021941b45479b821 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 25 Oct 2024 11:25:36 +0900 Subject: [PATCH 04/15] feat(utils): MaxBy --- Internal/Utils/Utils.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Internal/Utils/Utils.cs b/Internal/Utils/Utils.cs index e8cc113a8..62d62dc88 100644 --- a/Internal/Utils/Utils.cs +++ b/Internal/Utils/Utils.cs @@ -393,5 +393,27 @@ public static bool ScaledEvenly(Vector3 localScale) return CheckScale(localScale.x / localScale.y) && CheckScale(localScale.x / localScale.z) && CheckScale(localScale.y / localScale.z); } + + public static TSource MaxBy(this IEnumerable source, + Func selector) + where TComparable : IComparable + { + using var enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) throw new InvalidOperationException("Sequence is empty"); + var max = enumerator.Current; + var maxComparable = selector(max); + while (enumerator.MoveNext()) + { + var current = enumerator.Current; + var currentComparable = selector(current); + if (currentComparable.CompareTo(maxComparable) > 0) + { + max = current; + maxComparable = currentComparable; + } + } + + return max; + } } } From e4489cb55fdefcc0bc617fb9abdf0d779a95d4ce Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 25 Oct 2024 11:26:43 +0900 Subject: [PATCH 05/15] feat(merge-pb): build-time validation for LimitRotationFix --- Editor/Inspector/MergePhysBoneEditor.cs | 73 ++++++++++++++++++++- Editor/Processors/MergePhysBoneProcessor.cs | 4 +- Localization/en-us.po | 20 ++++++ 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/Editor/Inspector/MergePhysBoneEditor.cs b/Editor/Inspector/MergePhysBoneEditor.cs index 30f048d06..a4969c76e 100644 --- a/Editor/Inspector/MergePhysBoneEditor.cs +++ b/Editor/Inspector/MergePhysBoneEditor.cs @@ -607,6 +607,9 @@ protected override void BeginPbConfig() { if (SourcePhysBones.Count() <= 1) BuildLog.LogError("MergePhysBone:error:oneSource"); + + foreach (var vrcPhysBoneBase in SourcePhysBones) + vrcPhysBoneBase.InitTransforms(true); } protected override bool BeginSection(string name, string docTag) => true; @@ -619,8 +622,6 @@ protected override void EndPbConfig() { if (_usingCopyCurve) { - foreach (var vrcPhysBoneBase in SourcePhysBones) - vrcPhysBoneBase.InitTransforms(true); var maxLength = SourcePhysBones.Max(x => x.BoneChainLength()); if (SourcePhysBones.Any(x => x.BoneChainLength() != maxLength)) BuildLog.LogWarning("MergePhysBone:warning:differChainLength", @@ -701,7 +702,73 @@ protected override void Pb3DCurveProp(string label, case MergePhysBone.CurveVector3Config.CurveOverride.Override: break; case MergePhysBone.CurveVector3Config.CurveOverride.Fix: - // more validation about skew scaling and rotation animation on build + if (SourcePhysBones.Any()) + { + // skew scaling is disallowed + var scaledUnevenly = SourcePhysBones + .SelectMany(x => x.GetAffectedTransforms()) + .Where(x => !Utils.ScaledEvenly(x.localScale)) + .ToList(); + + if (scaledUnevenly.Count != 0) + BuildLog.LogError("MergePhysBone:error:LimitRotationFix:SkewScaling", scaledUnevenly); + + // error if there is different limit / rotation + var longestPhysBone = SourcePhysBones.MaxBy(x => x.BoneChainLength()); + + var fixedRotations = Enumerable.Range(0, longestPhysBone.BoneChainLength()) + .Select(index => + { + var time = (float)index / longestPhysBone.BoneChainLength() - 1; + + var rotation = longestPhysBone.CalcLimitRotation(time); + + return Processors.MergePhysBoneProcessor.ConvertRotation(rotation) + with + { + y = 0 + }; + }) + .ToList(); + + var differRotation = SourcePhysBones + .Any(physBone => + { + return Enumerable.Range(0, physBone.BoneChainLength()).Any(index => + { + var time = (float)index / physBone.BoneChainLength() - 1; + var rotation = longestPhysBone.CalcLimitRotation(time); + var fixedRot = Processors.MergePhysBoneProcessor.ConvertRotation(rotation)with + { + y = 0 + }; + + return fixedRot != fixedRotations[index]; + }); + }); + + if (differRotation) + { + BuildLog.LogError("MergePhysBone:error:LimitRotationFix:DifferRotation"); + } + + // endpoint position must be zero + switch ((MergePhysBone.EndPointPositionConfig.Override)EndpointPosition.OverrideProperty.enumValueIndex) + { + case MergePhysBone.EndPointPositionConfig.Override.Clear: + // no problem; endpoint position is zero + break; + case MergePhysBone.EndPointPositionConfig.Override.Copy: + if (EndpointPosition.PhysBoneValue!.vector3Value != Vector3.zero) + BuildLog.LogError("MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition"); + break; + case MergePhysBone.EndPointPositionConfig.Override.Override: + if (EndpointPosition.ValueProperty.vector3Value != Vector3.zero) + BuildLog.LogError("MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition"); + break; + } + } + break; default: throw new ArgumentOutOfRangeException(); diff --git a/Editor/Processors/MergePhysBoneProcessor.cs b/Editor/Processors/MergePhysBoneProcessor.cs index c458d4dcb..6390eb4be 100644 --- a/Editor/Processors/MergePhysBoneProcessor.cs +++ b/Editor/Processors/MergePhysBoneProcessor.cs @@ -358,9 +358,7 @@ private static void RotateRecursive(VRCPhysBoneBase physBone, } } - private static Vector3 ConvertRotation( - Vector3 limitRotation - ) + public static Vector3 ConvertRotation(Vector3 limitRotation) { // XYZ is the order used in VRCPhysBone var quat = quaternion.EulerXYZ(limitRotation * Mathf.Deg2Rad); diff --git a/Localization/en-us.po b/Localization/en-us.po index de18e6f48..a3573e5e0 100644 --- a/Localization/en-us.po +++ b/Localization/en-us.po @@ -165,6 +165,26 @@ msgstr "The value is differ between two or more sources. You have to set same va msgid "MergePhysBone:warning:differChainLength" msgstr "The chain length is differ between two or more sources. Shorter chain will be thicker than original." +msgid "MergePhysBone:error:LimitRotationFix:SkewScaling" +msgstr "" +"Skew scaling is not supported with Limit Rotation mode Fix.\n" +"Please change the Limit Rotation mode to other than Fix, or fix the skew scaling." + +msgid "MergePhysBone:error:LimitRotationFix:DifferRotation" +msgstr "" +"Limit Rotation of source phys bones differs in unfixable way.\n" +"Please fix Limit Rotation of source phys bones to same value, or change to other mode." + +msgid "MergePhysBone:error:LimitRotationFix:DifferRotation:description" +msgstr "" +"Limit Rotation Fix can fix Roll axis of different rotation but we cannot fix difference in other axis.\n" +"Roll axis in this context is local X axis, and might be differs from Roll config on PhysBone inspector if there is Yaw rotation." + +msgid "MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition" +msgstr "" +"Endpoint Position is not zero while Limit Rotation mode Fix.\n" +"Please set Endpoint Position to zero, or change Limit Rotation mode to other than Fix." + msgid "MergePhysBone:dialog:versionInfo:title" msgstr "Version Info" From b5326291823ea271cd7341e85d0da3261ef21270 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 25 Oct 2024 13:15:10 +0900 Subject: [PATCH 06/15] chore: update MergePhysBone messages --- Localization/en-us.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Localization/en-us.po b/Localization/en-us.po index a3573e5e0..34cf23272 100644 --- a/Localization/en-us.po +++ b/Localization/en-us.po @@ -154,7 +154,7 @@ msgstr "The PhysBone Version is not supported (yet) by Avatar Optimizer.\n" "Please tell author on twitter (@anatawa12_vrc) or GitHub (anatawa12/AvatarOptimizer)!" msgid "MergePhysBone:message:fix-yaw-pitch" -msgstr "Fix to 0, 0, 0 by rotating bones" +msgstr "Fix Roll with rotating bones" msgid "MergePhysBone:error:differValues" msgstr "The values is differ between two or more sources. You have to set same value OR override this property: {0}" @@ -183,7 +183,7 @@ msgstr "" msgid "MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition" msgstr "" "Endpoint Position is not zero while Limit Rotation mode Fix.\n" -"Please set Endpoint Position to zero, or change Limit Rotation mode to other than Fix." +"Please set Endpoint Position to zero, set Endpoint Position mode to Clear, or change Limit Rotation mode to other than Fix." msgid "MergePhysBone:dialog:versionInfo:title" msgstr "Version Info" From 51f0392563b19f94447cdd1cb931ab6b6151182d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 25 Oct 2024 14:02:57 +0900 Subject: [PATCH 07/15] chore(merge-pb): show errors on inspector --- Editor/Inspector/MergePhysBoneEditor.cs | 129 ++++++++++++++---------- 1 file changed, 76 insertions(+), 53 deletions(-) diff --git a/Editor/Inspector/MergePhysBoneEditor.cs b/Editor/Inspector/MergePhysBoneEditor.cs index a4969c76e..b06e89eae 100644 --- a/Editor/Inspector/MergePhysBoneEditor.cs +++ b/Editor/Inspector/MergePhysBoneEditor.cs @@ -344,6 +344,29 @@ protected override void Pb3DCurveProp(string label, case MergePhysBone.CurveVector3Config.CurveOverride.Fix: { EditorGUI.LabelField(rect, label, AAOL10N.Tr("MergePhysBone:message:fix-yaw-pitch")); + + if (SourcePhysBones.Any()) + { + foreach (var physBone in SourcePhysBones) + physBone.InitTransforms(force: false); + + // skew scaling is disallowed + if (MergePhysBoneValidator.SkewBones(SourcePhysBones) is { Count: > 0 }) + EditorGUILayout.HelpBox(AAOL10N.Tr("MergePhysBone:error:LimitRotationFix:SkewScaling"), MessageType.Error); + + // error if there is different limit / rotation + if (MergePhysBoneValidator.HasDifferentYawPitch(SourcePhysBones)) + EditorGUILayout.HelpBox(AAOL10N.Tr("MergePhysBone:error:LimitRotationFix:DifferRotation"), MessageType.Error); + + // endpoint position must be zero + switch ((MergePhysBone.EndPointPositionConfig.Override)EndpointPosition.OverrideProperty.enumValueIndex) + { + case MergePhysBone.EndPointPositionConfig.Override.Copy when EndpointPosition.PhysBoneValue!.vector3Value != Vector3.zero: + case MergePhysBone.EndPointPositionConfig.Override.Override when EndpointPosition.ValueProperty.vector3Value != Vector3.zero: + EditorGUILayout.HelpBox(AAOL10N.Tr("MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition"), MessageType.Error); + break; + } + } } break; default: @@ -705,66 +728,19 @@ protected override void Pb3DCurveProp(string label, if (SourcePhysBones.Any()) { // skew scaling is disallowed - var scaledUnevenly = SourcePhysBones - .SelectMany(x => x.GetAffectedTransforms()) - .Where(x => !Utils.ScaledEvenly(x.localScale)) - .ToList(); - - if (scaledUnevenly.Count != 0) - BuildLog.LogError("MergePhysBone:error:LimitRotationFix:SkewScaling", scaledUnevenly); + if (SkewBones(SourcePhysBones) is { Count: > 0 } skewBones) + BuildLog.LogError("MergePhysBone:error:LimitRotationFix:SkewScaling", skewBones); // error if there is different limit / rotation - var longestPhysBone = SourcePhysBones.MaxBy(x => x.BoneChainLength()); - - var fixedRotations = Enumerable.Range(0, longestPhysBone.BoneChainLength()) - .Select(index => - { - var time = (float)index / longestPhysBone.BoneChainLength() - 1; - - var rotation = longestPhysBone.CalcLimitRotation(time); - - return Processors.MergePhysBoneProcessor.ConvertRotation(rotation) - with - { - y = 0 - }; - }) - .ToList(); - - var differRotation = SourcePhysBones - .Any(physBone => - { - return Enumerable.Range(0, physBone.BoneChainLength()).Any(index => - { - var time = (float)index / physBone.BoneChainLength() - 1; - var rotation = longestPhysBone.CalcLimitRotation(time); - var fixedRot = Processors.MergePhysBoneProcessor.ConvertRotation(rotation)with - { - y = 0 - }; - - return fixedRot != fixedRotations[index]; - }); - }); - - if (differRotation) - { + if (HasDifferentYawPitch(SourcePhysBones)) BuildLog.LogError("MergePhysBone:error:LimitRotationFix:DifferRotation"); - } // endpoint position must be zero switch ((MergePhysBone.EndPointPositionConfig.Override)EndpointPosition.OverrideProperty.enumValueIndex) { - case MergePhysBone.EndPointPositionConfig.Override.Clear: - // no problem; endpoint position is zero - break; - case MergePhysBone.EndPointPositionConfig.Override.Copy: - if (EndpointPosition.PhysBoneValue!.vector3Value != Vector3.zero) - BuildLog.LogError("MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition"); - break; - case MergePhysBone.EndPointPositionConfig.Override.Override: - if (EndpointPosition.ValueProperty.vector3Value != Vector3.zero) - BuildLog.LogError("MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition"); + case MergePhysBone.EndPointPositionConfig.Override.Copy when EndpointPosition.PhysBoneValue!.vector3Value != Vector3.zero: + case MergePhysBone.EndPointPositionConfig.Override.Override when EndpointPosition.ValueProperty.vector3Value != Vector3.zero: + BuildLog.LogError("MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition"); break; } } @@ -775,6 +751,53 @@ protected override void Pb3DCurveProp(string label, } } + public static List SkewBones(IEnumerable sourcePhysBones) + { + // skew scaling is disallowed + return sourcePhysBones + .SelectMany(x => x.GetAffectedTransforms()) + .Where(x => !Utils.ScaledEvenly(x.localScale)) + .ToList(); + } + + public static bool HasDifferentYawPitch(IEnumerable sourcePhysBones) + { + var longestPhysBone = sourcePhysBones.MaxBy(x => x.BoneChainLength()); + + var fixedRotations = Enumerable.Range(0, longestPhysBone.BoneChainLength()) + .Select(index => + { + var time = (float)index / longestPhysBone.BoneChainLength() - 1; + + var rotation = longestPhysBone.CalcLimitRotation(time); + + return Processors.MergePhysBoneProcessor.ConvertRotation(rotation) + with + { + y = 0 + }; + }) + .ToList(); + + var differRotation = sourcePhysBones + .Any(physBone => + { + return Enumerable.Range(0, physBone.BoneChainLength()).Any(index => + { + var time = (float)index / physBone.BoneChainLength() - 1; + var rotation = longestPhysBone.CalcLimitRotation(time); + var fixedRot = Processors.MergePhysBoneProcessor.ConvertRotation(rotation)with + { + y = 0 + }; + + return fixedRot != fixedRotations[index]; + }); + }); + + return differRotation; + } + protected override void PbPermissionProp(string label, PermissionConfigProp prop, bool forceOverride = false) { if (forceOverride || prop.IsOverride) return; From 86cc57ea8b425511e7924e2f3559390d6538c71d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 25 Oct 2024 14:49:03 +0900 Subject: [PATCH 08/15] docs(merge-pb): document Fix mode --- .docs/content/docs/reference/merge-physbone/index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.docs/content/docs/reference/merge-physbone/index.md b/.docs/content/docs/reference/merge-physbone/index.md index 5fcd8f1d4..15ee5033e 100644 --- a/.docs/content/docs/reference/merge-physbone/index.md +++ b/.docs/content/docs/reference/merge-physbone/index.md @@ -41,3 +41,6 @@ For each property, you may select `Copy` to copy value from source property For colliders, you can select `Merge` to merge colliders array from source physbones. For Endpoint Position, you can select `Clear` to apply [Clear Endpoint Position](../clear-endpoint-position) + +For Limit Rotation, you can select `Fix` to fix different roll axis on the model. +If your model has different roll bones and fixes with Limit Rotation, you may use this option. From b536989485208198c0c7a95d1cb50a78ee98def0 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 25 Oct 2024 15:09:16 +0900 Subject: [PATCH 09/15] docs(changelog): Fix mode for PhysBone Limits in Merge PhysBone --- CHANGELOG-PRERELEASE.md | 5 +++++ CHANGELOG.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 8995566ad..bc1e3e39d 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -9,6 +9,11 @@ The format is based on [Keep a Changelog]. ## [Unreleased] ### Added - Right-click menu option to create a new GameObject with a specified component [`#1290`](https://github.com/anatawa12/AvatarOptimizer/pull/1290) +- Fix mode for PhysBone Limits in Merge PhysBone `#665` + - In addition to existing `Copy` and `Override`, we added `Fix` mode. + - This mode will try to correct roll axis by rotating bone. + - This feature allows you to configure the mode for PhysBone Limits in Merge PhysBone. + - This is useful if all configuration is same but roll axis is different. ### Changed - More Preference Improvement `#1288` diff --git a/CHANGELOG.md b/CHANGELOG.md index a6552b3b9..c7d625b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,11 @@ The format is based on [Keep a Changelog]. - When you changed shader for an material, properties for previously used shaders might be remain - This may increase your avatar size by unexpectedly including unused textures - Right-click menu option to create a new GameObject with a specified component [`#1290`](https://github.com/anatawa12/AvatarOptimizer/pull/1290) +- Fix mode for PhysBone Limits in Merge PhysBone `#665` + - In addition to existing `Copy` and `Override`, we added `Fix` mode. + - This mode will try to correct roll axis by rotating bone. + - This feature allows you to configure the mode for PhysBone Limits in Merge PhysBone. + - This is useful if all configuration is same but roll axis is different. ### Changed - Skip Enablement Mismatched Renderers is now disabled by default `#1169` From ad8c688564288cbe07e9033f7e4130b29eb16529 Mon Sep 17 00:00:00 2001 From: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:50:12 +0900 Subject: [PATCH 10/15] chore: en fix message --- Localization/en-us.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/en-us.po b/Localization/en-us.po index add3213ce..7397f017f 100644 --- a/Localization/en-us.po +++ b/Localization/en-us.po @@ -147,7 +147,7 @@ msgid "MergePhysBone:error:oneSource" msgstr "There is only one source PhysBone. You must specify two or more merge source PhysBones." msgid "MergePhysBone:error:multiChildType" -msgstr "Some PysBone has multi child type != Ignore" +msgstr "Some PhysBone has multi child type != Ignore" msgid "MergePhysBone:error:unsupportedPbVersion" msgstr "The PhysBone Version is not supported (yet) by Avatar Optimizer.\n" @@ -172,8 +172,8 @@ msgstr "" msgid "MergePhysBone:error:LimitRotationFix:DifferRotation" msgstr "" -"Limit Rotation of source phys bones differs in unfixable way.\n" -"Please fix Limit Rotation of source phys bones to same value, or change to other mode." +"Limit Rotation of source PhysBones differs in unfixable way.\n" +"Please fix Limit Rotation of source PhysBones to same value, or change to other mode." msgid "MergePhysBone:error:LimitRotationFix:DifferRotation:description" msgstr "" @@ -182,7 +182,7 @@ msgstr "" msgid "MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition" msgstr "" -"Endpoint Position is not zero while Limit Rotation mode Fix.\n" +"Endpoint Position is not zero while Limit Rotation mode is Fix.\n" "Please set Endpoint Position to zero, set Endpoint Position mode to Clear, or change Limit Rotation mode to other than Fix." msgid "MergePhysBone:dialog:versionInfo:title" From ab2349a44432d6565fa2adceabb64440ccd52e3e Mon Sep 17 00:00:00 2001 From: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:50:45 +0900 Subject: [PATCH 11/15] chore(l10n): [ja] update --- .../docs/reference/merge-physbone/index.ja.md | 6 +++-- Localization/ja-jp.po | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.docs/content/docs/reference/merge-physbone/index.ja.md b/.docs/content/docs/reference/merge-physbone/index.ja.md index f9835713c..32b130f8f 100644 --- a/.docs/content/docs/reference/merge-physbone/index.ja.md +++ b/.docs/content/docs/reference/merge-physbone/index.ja.md @@ -39,6 +39,8 @@ MultiChildTypeはIgnoreになります。 それぞれの項目について、統合対象の項目から値をコピーする場合は`Copy`(すべての統合対象で値が同じ場合のみ有効)、 代わりに新しい値を設定する場合は`Override`を選択してください。 -コライダーについては、`Merge`を選択して統合対象のコライダー一覧を統合することも出来ます。 +コライダーについては、`Merge`を選択して統合対象のコライダー一覧を統合することができます。 -Endpoint Positionについては、`Clear`を選択して[Clear Endpoint Position](../clear-endpoint-position)を使用することもできます。 +Endpoint Positionについては、`Clear`を選択して[Clear Endpoint Position](../clear-endpoint-position)を使用することができます。 + +角度制限については、`Fix`を選択して向きの異なるボーンに対する角度制限を1つに纏めるすることができます。 \ No newline at end of file diff --git a/Localization/ja-jp.po b/Localization/ja-jp.po index 169bde441..94f79c627 100644 --- a/Localization/ja-jp.po +++ b/Localization/ja-jp.po @@ -156,6 +156,9 @@ msgid "MergePhysBone:error:unsupportedPbVersion" msgstr "このPhysBoneバージョンは(まだ)Avatar Optimizerによって対応されていません。\n" "作者にtwitter (@anatawa12_vrc)またはGitHub (anatawa12/AvatarOptimizer)で連絡してください!" +msgid "MergePhysBone:message:fix-yaw-pitch" +msgstr "ボーンの向きを揃える" + msgid "MergePhysBone:error:differValues" msgstr "複数の統合対象の間で値に差異があります。以下の値は同じ値にするかOverrideする必要があります: {0}" @@ -165,6 +168,26 @@ msgstr "複数の統合対象の間で値に差異があります。同じ値に msgid "MergePhysBone:warning:differChainLength" msgstr "複数の統合対象の間でチェーンの長さが異なります。短いチェーンのPBは元よりも太くなる可能性があります。" +msgid "MergePhysBone:error:LimitRotationFix:SkewScaling" +msgstr "" +"Scaleが均一でないボーンの角度制限を統合することはサポートされていません。\n" +"角度制限の設定をFix以外にするか、Scaleを均一にしてください。" + +msgid "MergePhysBone:error:LimitRotationFix:DifferRotation" +msgstr "" +"角度制限を統合できる状態ではありません。\n" +"統合対象の角度制限の値を揃えるか、角度制限の設定をFix以外にしてください。" + +msgid "MergePhysBone:error:LimitRotationFix:DifferRotation:description" +msgstr "" +"角度制限の統合はロール回転の差がある場合にのみ用いることができ、他の軸で回転に差がある場合には用いることができません。\n" +"ロール回転の軸は通常ローカル座標におけるX軸ですが、ヨー回転が含まれている場合はInspectorの表示と異なることがあります。" + +msgid "MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition" +msgstr "" +"角度制限の設定がFixですが、Endpoint Positionが空ではありません。\n" +"Endpoint Positionを空にするか、Endpoint Positionの設定をClearにするか、角度制限の設定をFix以外にしてください。" + msgid "MergePhysBone:dialog:versionInfo:title" msgstr "バージョンについて" From d87a7e9baad675a4aff62ae3ddbf55c54a38db54 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 31 Oct 2024 20:44:03 +0900 Subject: [PATCH 12/15] docs: Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- .docs/content/docs/reference/merge-physbone/index.ja.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docs/content/docs/reference/merge-physbone/index.ja.md b/.docs/content/docs/reference/merge-physbone/index.ja.md index 32b130f8f..d7318baac 100644 --- a/.docs/content/docs/reference/merge-physbone/index.ja.md +++ b/.docs/content/docs/reference/merge-physbone/index.ja.md @@ -43,4 +43,4 @@ MultiChildTypeはIgnoreになります。 Endpoint Positionについては、`Clear`を選択して[Clear Endpoint Position](../clear-endpoint-position)を使用することができます。 -角度制限については、`Fix`を選択して向きの異なるボーンに対する角度制限を1つに纏めるすることができます。 \ No newline at end of file +角度制限では、Fix`を選択することで、ボーンに対して捻るような回転(ロール)を自動で調整して角度制限を纏めて適用することができます。 \ No newline at end of file From e054414ab12f3c86174b48050259e7a70996b08d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 31 Oct 2024 23:00:23 +0900 Subject: [PATCH 13/15] docs(en): Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- .docs/content/docs/reference/merge-physbone/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docs/content/docs/reference/merge-physbone/index.md b/.docs/content/docs/reference/merge-physbone/index.md index 15ee5033e..d0bc77977 100644 --- a/.docs/content/docs/reference/merge-physbone/index.md +++ b/.docs/content/docs/reference/merge-physbone/index.md @@ -43,4 +43,4 @@ For colliders, you can select `Merge` to merge colliders array from source physb For Endpoint Position, you can select `Clear` to apply [Clear Endpoint Position](../clear-endpoint-position) For Limit Rotation, you can select `Fix` to fix different roll axis on the model. -If your model has different roll bones and fixes with Limit Rotation, you may use this option. +If your model has different roll bones, you can limit their rotations together with this option. From eaef26ccc77f5a22039af1ebdab98ab99dc7feb5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 31 Oct 2024 23:16:57 +0900 Subject: [PATCH 14/15] chore: Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- .docs/content/docs/reference/merge-physbone/index.ja.md | 2 +- Localization/ja-jp.po | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.docs/content/docs/reference/merge-physbone/index.ja.md b/.docs/content/docs/reference/merge-physbone/index.ja.md index d7318baac..c9a45687a 100644 --- a/.docs/content/docs/reference/merge-physbone/index.ja.md +++ b/.docs/content/docs/reference/merge-physbone/index.ja.md @@ -43,4 +43,4 @@ MultiChildTypeはIgnoreになります。 Endpoint Positionについては、`Clear`を選択して[Clear Endpoint Position](../clear-endpoint-position)を使用することができます。 -角度制限では、Fix`を選択することで、ボーンに対して捻るような回転(ロール)を自動で調整して角度制限を纏めて適用することができます。 \ No newline at end of file +角度制限では、`Fix`を選択することで、ボーンに対して捻るような回転(ロール)を自動で調整して角度制限を纏めて適用することができます。 \ No newline at end of file diff --git a/Localization/ja-jp.po b/Localization/ja-jp.po index 7965c5143..b44cf8566 100644 --- a/Localization/ja-jp.po +++ b/Localization/ja-jp.po @@ -157,7 +157,7 @@ msgstr "このPhysBoneバージョンは(まだ)Avatar Optimizerによって対 "作者にtwitter (@anatawa12_vrc)またはGitHub (anatawa12/AvatarOptimizer)で連絡してください!" msgid "MergePhysBone:message:fix-yaw-pitch" -msgstr "ボーンの向きを揃える" +msgstr "ボーンの回転(ロール)を揃える" msgid "MergePhysBone:error:differValues" msgstr "複数の統合対象の間で値に差異があります。以下の値は同じ値にするかOverrideする必要があります: {0}" @@ -180,7 +180,7 @@ msgstr "" msgid "MergePhysBone:error:LimitRotationFix:DifferRotation:description" msgstr "" -"角度制限の統合はロール回転の差がある場合にのみ用いることができ、他の軸で回転に差がある場合には用いることができません。\n" +"角度制限の統合は、ボーンに対する捻るような回転(ロール)の差は揃えることができますが、他の軸の回転は揃えることができません。\n" "ロール回転の軸は通常ローカル座標におけるX軸ですが、ヨー回転が含まれている場合はInspectorの表示と異なることがあります。" msgid "MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition" From fd5fe3472e5d7579ef4a01000624ac9b5b5c16ba Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 1 Nov 2024 00:48:57 +0900 Subject: [PATCH 15/15] chore(ja): Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- .docs/content/docs/reference/merge-physbone/index.ja.md | 3 ++- Localization/ja-jp.po | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.docs/content/docs/reference/merge-physbone/index.ja.md b/.docs/content/docs/reference/merge-physbone/index.ja.md index c9a45687a..2862abaa2 100644 --- a/.docs/content/docs/reference/merge-physbone/index.ja.md +++ b/.docs/content/docs/reference/merge-physbone/index.ja.md @@ -43,4 +43,5 @@ MultiChildTypeはIgnoreになります。 Endpoint Positionについては、`Clear`を選択して[Clear Endpoint Position](../clear-endpoint-position)を使用することができます。 -角度制限では、`Fix`を選択することで、ボーンに対して捻るような回転(ロール)を自動で調整して角度制限を纏めて適用することができます。 \ No newline at end of file +角度制限では、`Fix`を選択することで、ボーンに対する捻るような回転(Roll)の値を自動で揃えられます。 +これにより、Rollの値だけが異なっているような場合に角度制限を纏めて適用することができます。 \ No newline at end of file diff --git a/Localization/ja-jp.po b/Localization/ja-jp.po index b44cf8566..e70681b2f 100644 --- a/Localization/ja-jp.po +++ b/Localization/ja-jp.po @@ -157,7 +157,7 @@ msgstr "このPhysBoneバージョンは(まだ)Avatar Optimizerによって対 "作者にtwitter (@anatawa12_vrc)またはGitHub (anatawa12/AvatarOptimizer)で連絡してください!" msgid "MergePhysBone:message:fix-yaw-pitch" -msgstr "ボーンの回転(ロール)を揃える" +msgstr "ボーンを回転させてRollの値を揃える" msgid "MergePhysBone:error:differValues" msgstr "複数の統合対象の間で値に差異があります。以下の値は同じ値にするかOverrideする必要があります: {0}" @@ -180,8 +180,8 @@ msgstr "" msgid "MergePhysBone:error:LimitRotationFix:DifferRotation:description" msgstr "" -"角度制限の統合は、ボーンに対する捻るような回転(ロール)の差は揃えることができますが、他の軸の回転は揃えることができません。\n" -"ロール回転の軸は通常ローカル座標におけるX軸ですが、ヨー回転が含まれている場合はInspectorの表示と異なることがあります。" +"角度制限の統合は、ボーンに対する捻るような回転(Roll)の値は揃えることができますが、他の軸の回転は揃えることができません。\n" +"Roll回転の軸はローカル座標系におけるX軸になります。Yaw回転が含まれている場合はPBのInspectorにあるRollの表示とは異なることがあります。" msgid "MergePhysBone:error:LimitRotationFix:NonZeroEndpointPosition" msgstr ""