mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-19 21:00:08 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.PlayerLoop;
|
||||
using VRC.Core;
|
||||
using static net.fushizen.modular_avatar.core.editor.Localization;
|
||||
|
||||
namespace net.fushizen.modular_avatar.core.editor
|
||||
{
|
||||
@ -17,6 +21,8 @@ namespace net.fushizen.modular_avatar.core.editor
|
||||
private ReorderableList _list;
|
||||
private SerializedProperty _bindings;
|
||||
|
||||
private Dictionary<Mesh, string[]> blendshapeNames = new Dictionary<Mesh, string[]>();
|
||||
|
||||
static BlendshapeSyncEditor()
|
||||
{
|
||||
f_m_SerializedObject =
|
||||
@ -59,17 +65,43 @@ namespace net.fushizen.modular_avatar.core.editor
|
||||
_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)
|
||||
{
|
||||
var leftHalf = rect;
|
||||
leftHalf.width /= 2;
|
||||
ComputeRects(rect, out var meshFieldRect, out var baseShapeNameRect, out var targetShapeNameRect);
|
||||
|
||||
var rightHalf = rect;
|
||||
rightHalf.width /= 2;
|
||||
rightHalf.x += rightHalf.width;
|
||||
|
||||
EditorGUI.LabelField(leftHalf, "Mesh");
|
||||
EditorGUI.LabelField(rightHalf, "Blendshape");
|
||||
EditorGUI.LabelField(meshFieldRect, G("blendshape.mesh"));
|
||||
EditorGUI.LabelField(baseShapeNameRect, G("blendshape.source"));
|
||||
EditorGUI.LabelField(targetShapeNameRect, G("blendshape.target"));
|
||||
}
|
||||
|
||||
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.y += 1;
|
||||
|
||||
var leftHalf = rect;
|
||||
leftHalf.width /= 2;
|
||||
leftHalf.width -= 12;
|
||||
if (Math.Abs(elementWidth - rect.width) > 0.5f && rect.width > 1)
|
||||
{
|
||||
elementWidth = rect.width;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
var rightHalf = rect;
|
||||
rightHalf.width /= 2;
|
||||
rightHalf.x += rightHalf.width;
|
||||
ComputeRects(rect, out var meshFieldRect, out var baseShapeNameRect, out var targetShapeNameRect);
|
||||
|
||||
var item = _bindings.GetArrayElementAtIndex(index);
|
||||
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())
|
||||
{
|
||||
EditorGUI.PropertyField(leftHalf, mesh, GUIContent.none);
|
||||
EditorGUI.PropertyField(rightHalf, blendshape, GUIContent.none);
|
||||
EditorGUI.PropertyField(meshFieldRect, mesh, 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()
|
||||
{
|
||||
LogoDisplay.DisplayLogo();
|
||||
@ -104,6 +250,8 @@ namespace net.fushizen.modular_avatar.core.editor
|
||||
|
||||
_list.DoLayoutList();
|
||||
|
||||
ShowLanguageUI();
|
||||
|
||||
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.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.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.quest": "このコンポーネントはクエスト単体非対応のため無効化となっています。",
|
||||
"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 string Blendshape;
|
||||
public string LocalBlendshape;
|
||||
|
||||
public bool Equals(BlendshapeBinding other)
|
||||
{
|
||||
@ -82,7 +83,10 @@ namespace net.fushizen.modular_avatar.core
|
||||
if (mesh == null)
|
||||
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);
|
||||
if (localIndex == -1 || refIndex == -1)
|
||||
continue;
|
||||
|
Loading…
Reference in New Issue
Block a user