mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-02-01 03:02:56 +08:00
Add support for mapping blendshapes with different names in source and target
Closes: #70
This commit is contained in:
parent
c634956569
commit
bd9e3711ea
@ -1,10 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditorInternal;
|
using UnityEditorInternal;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.PlayerLoop;
|
using UnityEngine.PlayerLoop;
|
||||||
|
using VRC.Core;
|
||||||
|
using static net.fushizen.modular_avatar.core.editor.Localization;
|
||||||
|
|
||||||
namespace net.fushizen.modular_avatar.core.editor
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
{
|
{
|
||||||
@ -17,6 +21,8 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
private ReorderableList _list;
|
private ReorderableList _list;
|
||||||
private SerializedProperty _bindings;
|
private SerializedProperty _bindings;
|
||||||
|
|
||||||
|
private Dictionary<Mesh, string[]> blendshapeNames = new Dictionary<Mesh, string[]>();
|
||||||
|
|
||||||
static BlendshapeSyncEditor()
|
static BlendshapeSyncEditor()
|
||||||
{
|
{
|
||||||
f_m_SerializedObject =
|
f_m_SerializedObject =
|
||||||
@ -59,17 +65,43 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
_list.elementHeight += 2;
|
_list.elementHeight += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float elementWidth = 0;
|
||||||
|
|
||||||
|
private void ComputeRects(
|
||||||
|
Rect rect,
|
||||||
|
out Rect meshFieldRect,
|
||||||
|
out Rect baseShapeNameRect,
|
||||||
|
out Rect targetShapeNameRect
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (elementWidth > 1 && elementWidth < rect.width)
|
||||||
|
{
|
||||||
|
rect.x += rect.width - elementWidth;
|
||||||
|
rect.width = elementWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
meshFieldRect = rect;
|
||||||
|
meshFieldRect.width /= 3;
|
||||||
|
|
||||||
|
baseShapeNameRect = rect;
|
||||||
|
baseShapeNameRect.width /= 3;
|
||||||
|
baseShapeNameRect.x = meshFieldRect.x + meshFieldRect.width;
|
||||||
|
|
||||||
|
targetShapeNameRect = rect;
|
||||||
|
targetShapeNameRect.width /= 3;
|
||||||
|
targetShapeNameRect.x = baseShapeNameRect.x + baseShapeNameRect.width;
|
||||||
|
|
||||||
|
meshFieldRect.width -= 12;
|
||||||
|
baseShapeNameRect.width -= 12;
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawHeader(Rect rect)
|
private void DrawHeader(Rect rect)
|
||||||
{
|
{
|
||||||
var leftHalf = rect;
|
ComputeRects(rect, out var meshFieldRect, out var baseShapeNameRect, out var targetShapeNameRect);
|
||||||
leftHalf.width /= 2;
|
|
||||||
|
|
||||||
var rightHalf = rect;
|
EditorGUI.LabelField(meshFieldRect, G("blendshape.mesh"));
|
||||||
rightHalf.width /= 2;
|
EditorGUI.LabelField(baseShapeNameRect, G("blendshape.source"));
|
||||||
rightHalf.x += rightHalf.width;
|
EditorGUI.LabelField(targetShapeNameRect, G("blendshape.target"));
|
||||||
|
|
||||||
EditorGUI.LabelField(leftHalf, "Mesh");
|
|
||||||
EditorGUI.LabelField(rightHalf, "Blendshape");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawElement(Rect rect, int index, bool isactive, bool isfocused)
|
private void DrawElement(Rect rect, int index, bool isactive, bool isfocused)
|
||||||
@ -77,25 +109,139 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
rect.height -= 2;
|
rect.height -= 2;
|
||||||
rect.y += 1;
|
rect.y += 1;
|
||||||
|
|
||||||
var leftHalf = rect;
|
if (Math.Abs(elementWidth - rect.width) > 0.5f && rect.width > 1)
|
||||||
leftHalf.width /= 2;
|
{
|
||||||
leftHalf.width -= 12;
|
elementWidth = rect.width;
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
|
||||||
var rightHalf = rect;
|
ComputeRects(rect, out var meshFieldRect, out var baseShapeNameRect, out var targetShapeNameRect);
|
||||||
rightHalf.width /= 2;
|
|
||||||
rightHalf.x += rightHalf.width;
|
|
||||||
|
|
||||||
var item = _bindings.GetArrayElementAtIndex(index);
|
var item = _bindings.GetArrayElementAtIndex(index);
|
||||||
var mesh = item.FindPropertyRelative(nameof(BlendshapeBinding.ReferenceMesh));
|
var mesh = item.FindPropertyRelative(nameof(BlendshapeBinding.ReferenceMesh));
|
||||||
var blendshape = item.FindPropertyRelative(nameof(BlendshapeBinding.Blendshape));
|
var sourceBlendshape = item.FindPropertyRelative(nameof(BlendshapeBinding.Blendshape));
|
||||||
|
var localBlendshape = item.FindPropertyRelative(nameof(BlendshapeBinding.LocalBlendshape));
|
||||||
|
|
||||||
using (var scope = new ZeroIndentScope())
|
using (var scope = new ZeroIndentScope())
|
||||||
{
|
{
|
||||||
EditorGUI.PropertyField(leftHalf, mesh, GUIContent.none);
|
EditorGUI.PropertyField(meshFieldRect, mesh, GUIContent.none);
|
||||||
EditorGUI.PropertyField(rightHalf, blendshape, GUIContent.none);
|
|
||||||
|
var sourceMesh =
|
||||||
|
(targets.Length == 1 ? target as ModularAvatarBlendshapeSync : null)?.Bindings[index]
|
||||||
|
.ReferenceMesh.Get((Component) target)
|
||||||
|
?.GetComponent<SkinnedMeshRenderer>()
|
||||||
|
?.sharedMesh;
|
||||||
|
DrawBlendshapePopup(sourceMesh, baseShapeNameRect, sourceBlendshape);
|
||||||
|
|
||||||
|
var localMesh =
|
||||||
|
(targets.Length == 1 ? target as ModularAvatarBlendshapeSync : null)?
|
||||||
|
.GetComponent<SkinnedMeshRenderer>()
|
||||||
|
?.sharedMesh;
|
||||||
|
|
||||||
|
DrawBlendshapePopup(localMesh, targetShapeNameRect, localBlendshape, sourceBlendshape.stringValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawBlendshapePopup(Mesh targetMesh, Rect rect, SerializedProperty prop,
|
||||||
|
string defaultValue = null)
|
||||||
|
{
|
||||||
|
var style = new GUIStyle(EditorStyles.popup);
|
||||||
|
|
||||||
|
style.fixedHeight = rect.height;
|
||||||
|
|
||||||
|
if (targetMesh == null)
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(rect, prop, GUIContent.none);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string[] selections = GetBlendshapeNames(targetMesh);
|
||||||
|
|
||||||
|
int shapeIndex = Array.FindIndex(selections, s => s == prop.stringValue);
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
int newShapeIndex = EditorGUI.Popup(rect, shapeIndex, selections, style);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
prop.stringValue = selections[newShapeIndex];
|
||||||
|
}
|
||||||
|
else if (shapeIndex < 0)
|
||||||
|
{
|
||||||
|
var toDisplay = prop.stringValue;
|
||||||
|
bool colorRed = true;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(toDisplay) && defaultValue != null)
|
||||||
|
{
|
||||||
|
toDisplay = defaultValue;
|
||||||
|
colorRed = Array.FindIndex(selections, s => s == toDisplay) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!colorRed)
|
||||||
|
{
|
||||||
|
UpdateAllStates(style, s => s.textColor = Color.Lerp(s.textColor, Color.clear, 0.2f));
|
||||||
|
style.fontStyle = FontStyle.Italic;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateAllStates(style, s => s.textColor = Color.Lerp(s.textColor, Color.red, 0.85f));
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.Label(rect, toDisplay, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateAllStates(GUIStyle style, Action<GUIStyleState> action)
|
||||||
|
{
|
||||||
|
var state = style.normal;
|
||||||
|
action(state);
|
||||||
|
style.normal = state;
|
||||||
|
|
||||||
|
state = style.hover;
|
||||||
|
action(state);
|
||||||
|
style.hover = state;
|
||||||
|
|
||||||
|
state = style.active;
|
||||||
|
action(state);
|
||||||
|
style.active = state;
|
||||||
|
|
||||||
|
state = style.focused;
|
||||||
|
action(state);
|
||||||
|
style.focused = state;
|
||||||
|
|
||||||
|
state = style.onNormal;
|
||||||
|
action(state);
|
||||||
|
style.onNormal = state;
|
||||||
|
|
||||||
|
state = style.onHover;
|
||||||
|
action(state);
|
||||||
|
style.onHover = state;
|
||||||
|
|
||||||
|
state = style.onActive;
|
||||||
|
action(state);
|
||||||
|
style.onActive = state;
|
||||||
|
|
||||||
|
state = style.onFocused;
|
||||||
|
action(state);
|
||||||
|
style.onFocused = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] GetBlendshapeNames(Mesh targetMesh)
|
||||||
|
{
|
||||||
|
if (!blendshapeNames.TryGetValue(targetMesh, out var selections))
|
||||||
|
{
|
||||||
|
selections = new string[targetMesh.blendShapeCount];
|
||||||
|
for (int i = 0; i < targetMesh.blendShapeCount; i++)
|
||||||
|
{
|
||||||
|
selections[i] = targetMesh.GetBlendShapeName(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
blendshapeNames[targetMesh] = selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selections;
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
LogoDisplay.DisplayLogo();
|
LogoDisplay.DisplayLogo();
|
||||||
@ -104,6 +250,8 @@ namespace net.fushizen.modular_avatar.core.editor
|
|||||||
|
|
||||||
_list.DoLayoutList();
|
_list.DoLayoutList();
|
||||||
|
|
||||||
|
ShowLanguageUI();
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
public class GUIColorScope : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Color _oldColor;
|
||||||
|
|
||||||
|
public GUIColorScope(Color color)
|
||||||
|
{
|
||||||
|
_oldColor = GUI.color;
|
||||||
|
GUI.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GUI.color = _oldColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 775469f355c64ef2b3e3f5de10e2cde4
|
||||||
|
timeCreated: 1667876954
|
@ -41,5 +41,8 @@
|
|||||||
"fpvisible.normal": "This object will be visible in your first person view.",
|
"fpvisible.normal": "This object will be visible in your first person view.",
|
||||||
"fpvisible.NotUnderHead": "This component has no effect when not placed under the head bone.",
|
"fpvisible.NotUnderHead": "This component has no effect when not placed under the head bone.",
|
||||||
"fpvisible.quest": "This component is not compatible with the standalone Oculus Quest and will have no effect.",
|
"fpvisible.quest": "This component is not compatible with the standalone Oculus Quest and will have no effect.",
|
||||||
"fpvisible.InPhysBoneChain": "This object is controlled by a Physics Bone chain and cannot be made visible in first person safely. Select the start of the chain instead."
|
"fpvisible.InPhysBoneChain": "This object is controlled by a Physics Bone chain and cannot be made visible in first person safely. Select the start of the chain instead.",
|
||||||
|
"blendshape.mesh": "Mesh",
|
||||||
|
"blendshape.source": "Source blendshape",
|
||||||
|
"blendshape.target": "Target blendshape"
|
||||||
}
|
}
|
@ -41,5 +41,8 @@
|
|||||||
"fpvisible.normal": "このオブジェクトは一人視点で表示されます。",
|
"fpvisible.normal": "このオブジェクトは一人視点で表示されます。",
|
||||||
"fpvisible.quest": "このコンポーネントはクエスト単体非対応のため無効化となっています。",
|
"fpvisible.quest": "このコンポーネントはクエスト単体非対応のため無効化となっています。",
|
||||||
"fpvisible.NotUnderHead": "このコンポーネントはヘッドボーン外では効果がありません。",
|
"fpvisible.NotUnderHead": "このコンポーネントはヘッドボーン外では効果がありません。",
|
||||||
"fpvisible.InPhysBoneChain": "このオブジェクトはPhysBoneに制御されているため、一人視点で表示できません。PhysBoneの始点を指定してください。"
|
"fpvisible.InPhysBoneChain": "このオブジェクトはPhysBoneに制御されているため、一人視点で表示できません。PhysBoneの始点を指定してください。",
|
||||||
|
"blendshape.mesh": "メッシュ",
|
||||||
|
"blendshape.source": "元メッシュのブレンドシェープ",
|
||||||
|
"blendshape.target": "このメッシュのブレンドシェープ"
|
||||||
}
|
}
|
@ -9,6 +9,7 @@ namespace net.fushizen.modular_avatar.core
|
|||||||
{
|
{
|
||||||
public AvatarObjectReference ReferenceMesh;
|
public AvatarObjectReference ReferenceMesh;
|
||||||
public string Blendshape;
|
public string Blendshape;
|
||||||
|
public string LocalBlendshape;
|
||||||
|
|
||||||
public bool Equals(BlendshapeBinding other)
|
public bool Equals(BlendshapeBinding other)
|
||||||
{
|
{
|
||||||
@ -82,7 +83,10 @@ namespace net.fushizen.modular_avatar.core
|
|||||||
if (mesh == null)
|
if (mesh == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var localIndex = localMesh.GetBlendShapeIndex(binding.Blendshape);
|
var localShape = string.IsNullOrWhiteSpace(binding.LocalBlendshape)
|
||||||
|
? binding.Blendshape
|
||||||
|
: binding.LocalBlendshape;
|
||||||
|
var localIndex = localMesh.GetBlendShapeIndex(localShape);
|
||||||
var refIndex = mesh.GetBlendShapeIndex(binding.Blendshape);
|
var refIndex = mesh.GetBlendShapeIndex(binding.Blendshape);
|
||||||
if (localIndex == -1 || refIndex == -1)
|
if (localIndex == -1 || refIndex == -1)
|
||||||
continue;
|
continue;
|
||||||
|
Loading…
Reference in New Issue
Block a user