Skip to content

Commit

Permalink
Merge pull request #1291 from anatawa12/merge-physbone-roll
Browse files Browse the repository at this point in the history
Fix roll in Merge PhysBone
  • Loading branch information
anatawa12 authored Oct 31, 2024
2 parents d7178a4 + fd5fe34 commit 55169fc
Show file tree
Hide file tree
Showing 16 changed files with 602 additions and 111 deletions.
7 changes: 5 additions & 2 deletions .docs/content/docs/reference/merge-physbone/index.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ 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`を選択することで、ボーンに対する捻るような回転(Roll)の値を自動で揃えられます。
これにより、Rollの値だけが異なっているような場合に角度制限を纏めて適用することができます。
3 changes: 3 additions & 0 deletions .docs/content/docs/reference/merge-physbone/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, you can limit their rotations together with this option.
5 changes: 5 additions & 0 deletions CHANGELOG-PRERELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ The format is based on [Keep a Changelog].
- AAO 1.8.0 introduced BlendShape support for Merge Skinned Mesh, but new default mode "Rename to avoid conflicts" would increase number of BlendShape.
- This feature is added to relax this problem by automatically merging multiple BlendShapes of one Mesh.
- With this feature, you can use rename mode without performance loss.
- 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

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ The format is based on [Keep a Changelog].
- AAO 1.8.0 introduced BlendShape support for Merge Skinned Mesh, but new default mode "Rename to avoid conflicts" would increase number of BlendShape.
- This feature is added to relax this problem by automatically merging multiple BlendShapes of one Mesh.
- With this feature, you can use rename mode without performance loss.
- 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`
Expand Down
9 changes: 0 additions & 9 deletions Editor/.MergePhysBoneEditorModificationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
187 changes: 163 additions & 24 deletions Editor/Inspector/MergePhysBoneEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,14 +307,81 @@ 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"));

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:
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)
{
Expand All @@ -337,7 +404,7 @@ void DrawCurve(string curveLabel, SerializedProperty curveProp)
{
// without curve: constant
EditorGUI.PropertyField(valueRect, valueProp, labelContent);

if (GUI.Button(buttonRect, "C"))
{
var curve = new AnimationCurve();
Expand All @@ -348,12 +415,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" };
Expand Down Expand Up @@ -568,6 +630,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;
Expand All @@ -580,8 +645,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",
Expand Down Expand Up @@ -646,17 +709,93 @@ protected override void Pb3DCurveProp(string label,
string pbXCurveLabel, string pbYCurveLabel, string pbZCurveLabel,
CurveVector3ConfigProp prop, bool forceOverride = false)
{
if (forceOverride || prop.IsOverride) return;
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:
if (SourcePhysBones.Any())
{
// skew scaling is disallowed
if (SkewBones(SourcePhysBones) is { Count: > 0 } skewBones)
BuildLog.LogError("MergePhysBone:error:LimitRotationFix:SkewScaling", skewBones);

// error if there is different limit / rotation
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.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;
}
}

if (prop.SourceValue!.hasMultipleDifferentValues
|| prop.SourceCurveX!.hasMultipleDifferentValues
|| prop.SourceCurveY!.hasMultipleDifferentValues
|| prop.SourceCurveZ!.hasMultipleDifferentValues)
_differProps.Add(label);
break;
default:
throw new ArgumentOutOfRangeException();
}
}

public static List<Transform> SkewBones(IEnumerable<VRCPhysBoneBase> sourcePhysBones)
{
// skew scaling is disallowed
return sourcePhysBones
.SelectMany(x => x.GetAffectedTransforms())
.Where(x => !Utils.ScaledEvenly(x.localScale))
.ToList();
}

_usingCopyCurve |= prop.GetCurveXProperty(false).animationCurveValue.length > 0;
_usingCopyCurve |= prop.GetCurveYProperty(false).animationCurveValue.length > 0;
_usingCopyCurve |= prop.GetCurveZProperty(false).animationCurveValue.length > 0;
public static bool HasDifferentYawPitch(IEnumerable<VRCPhysBoneBase> 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)
Expand Down
55 changes: 55 additions & 0 deletions Editor/MergePhysBoneEditorModificationUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
}
}
}

Expand Down
Loading

0 comments on commit 55169fc

Please sign in to comment.