Streamingle_URP/Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs

264 lines
7.9 KiB
C#

using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(StreamingleFacialReceiver))]
public class StreamingleFacialReceiverEditor : Editor
{
private SerializedProperty mirrorMode;
private SerializedProperty faceMeshRenderers;
private SerializedProperty availablePorts;
private SerializedProperty enableFiltering;
private SerializedProperty smoothingFactor;
private SerializedProperty maxBlendShapeDelta;
private SerializedProperty maxRotationDelta;
private SerializedProperty fastBlendShapeMultiplier;
private SerializedProperty spikeToleranceFrames;
private SerializedProperty globalIntensity;
private SerializedProperty blendShapeIntensityOverrides;
private bool showConnection = false;
private bool showFiltering = false;
private bool showIntensity = false;
private static readonly Color HeaderColor = new Color(0.18f, 0.18f, 0.18f, 1f);
private static readonly Color ActivePortColor = new Color(0.2f, 0.8f, 0.4f, 1f);
private static readonly Color InactivePortColor = new Color(0.35f, 0.35f, 0.35f, 1f);
private static readonly Color AccentColor = new Color(0.4f, 0.7f, 1f, 1f);
private GUIStyle _headerStyle;
private GUIStyle _sectionBoxStyle;
private GUIStyle _portButtonStyle;
private GUIStyle _portButtonActiveStyle;
private GUIStyle _statusLabelStyle;
void OnEnable()
{
mirrorMode = serializedObject.FindProperty("mirrorMode");
faceMeshRenderers = serializedObject.FindProperty("faceMeshRenderers");
availablePorts = serializedObject.FindProperty("availablePorts");
enableFiltering = serializedObject.FindProperty("enableFiltering");
smoothingFactor = serializedObject.FindProperty("smoothingFactor");
maxBlendShapeDelta = serializedObject.FindProperty("maxBlendShapeDelta");
maxRotationDelta = serializedObject.FindProperty("maxRotationDelta");
fastBlendShapeMultiplier = serializedObject.FindProperty("fastBlendShapeMultiplier");
spikeToleranceFrames = serializedObject.FindProperty("spikeToleranceFrames");
globalIntensity = serializedObject.FindProperty("globalIntensity");
blendShapeIntensityOverrides = serializedObject.FindProperty("blendShapeIntensityOverrides");
}
void InitStyles()
{
if (_headerStyle != null) return;
_headerStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13,
alignment = TextAnchor.MiddleLeft,
padding = new RectOffset(8, 0, 4, 4),
};
_headerStyle.normal.textColor = AccentColor;
_sectionBoxStyle = new GUIStyle("HelpBox")
{
padding = new RectOffset(10, 10, 8, 8),
margin = new RectOffset(0, 0, 4, 8),
};
_portButtonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 12,
fontStyle = FontStyle.Bold,
fixedHeight = 36,
alignment = TextAnchor.MiddleCenter,
};
_portButtonActiveStyle = new GUIStyle(_portButtonStyle);
_portButtonActiveStyle.normal.textColor = Color.white;
_statusLabelStyle = new GUIStyle(EditorStyles.miniLabel)
{
alignment = TextAnchor.MiddleCenter,
fontSize = 10,
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
InitStyles();
var mocap = (StreamingleFacialReceiver)target;
// 타이틀
EditorGUILayout.Space(4);
DrawTitle();
EditorGUILayout.Space(4);
// 기본 설정
DrawBasicSettings();
// 포트 핫스왑
DrawPortSection(mocap);
// 필터링
DrawFilteringSection();
// 페이셜 강도
DrawIntensitySection();
serializedObject.ApplyModifiedProperties();
}
void DrawTitle()
{
var rect = EditorGUILayout.GetControlRect(false, 32);
EditorGUI.DrawRect(rect, HeaderColor);
var titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 15,
alignment = TextAnchor.MiddleLeft,
};
titleStyle.normal.textColor = Color.white;
EditorGUI.LabelField(rect, " Streamingle Facial Receiver", titleStyle);
// 상태 표시
if (Application.isPlaying)
{
var statusRect = new Rect(rect.xMax - 80, rect.y, 75, rect.height);
var dotRect = new Rect(statusRect.x - 12, rect.y + rect.height / 2 - 4, 8, 8);
EditorGUI.DrawRect(dotRect, ActivePortColor);
EditorGUI.LabelField(statusRect, "LIVE", _statusLabelStyle);
}
}
void DrawBasicSettings()
{
EditorGUILayout.BeginVertical(_sectionBoxStyle);
EditorGUILayout.PropertyField(faceMeshRenderers, new GUIContent("Face Mesh Renderers"));
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(mirrorMode, new GUIContent("Mirror Mode (L/R Flip)"));
EditorGUILayout.EndVertical();
}
void DrawPortSection(StreamingleFacialReceiver mocap)
{
showConnection = DrawSectionHeader("Port Hot-Swap", showConnection);
if (!showConnection) return;
EditorGUILayout.BeginVertical(_sectionBoxStyle);
// 현재 포트 표시
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Active Port", EditorStyles.miniLabel, GUILayout.Width(70));
var portLabel = new GUIStyle(EditorStyles.boldLabel);
portLabel.normal.textColor = ActivePortColor;
EditorGUILayout.LabelField(mocap.LOCAL_PORT.ToString(), portLabel);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(4);
// 포트 버튼 그리드
if (mocap.availablePorts != null && mocap.availablePorts.Length > 0)
{
EditorGUILayout.BeginHorizontal();
for (int i = 0; i < mocap.availablePorts.Length; i++)
{
bool isActive = i == mocap.activePortIndex;
var prevBg = GUI.backgroundColor;
GUI.backgroundColor = isActive ? ActivePortColor : InactivePortColor;
var style = isActive ? _portButtonActiveStyle : _portButtonStyle;
string label = mocap.availablePorts[i].ToString();
if (GUILayout.Button(label, style))
{
if (Application.isPlaying)
{
mocap.SwitchToPort(i);
}
else
{
Undo.RecordObject(mocap, "Switch Port");
mocap.activePortIndex = i;
EditorUtility.SetDirty(mocap);
}
}
GUI.backgroundColor = prevBg;
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(availablePorts, new GUIContent("Port List"), true);
EditorGUILayout.EndVertical();
}
void DrawFilteringSection()
{
showFiltering = DrawSectionHeader("Data Filtering", showFiltering);
if (!showFiltering) return;
EditorGUILayout.BeginVertical(_sectionBoxStyle);
EditorGUILayout.PropertyField(enableFiltering, new GUIContent("Enable"));
if (enableFiltering.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(smoothingFactor, new GUIContent("Smoothing"));
EditorGUILayout.PropertyField(maxBlendShapeDelta, new GUIContent("Max BlendShape Delta"));
EditorGUILayout.PropertyField(maxRotationDelta, new GUIContent("Max Rotation Delta"));
EditorGUILayout.PropertyField(fastBlendShapeMultiplier, new GUIContent("Fast BS Multiplier"));
EditorGUILayout.PropertyField(spikeToleranceFrames, new GUIContent("Spike Tolerance"));
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
}
void DrawIntensitySection()
{
showIntensity = DrawSectionHeader("Facial Intensity", showIntensity);
if (!showIntensity) return;
EditorGUILayout.BeginVertical(_sectionBoxStyle);
// Global intensity - 크게 표시
EditorGUILayout.LabelField("Global", EditorStyles.miniLabel);
EditorGUILayout.PropertyField(globalIntensity, GUIContent.none);
EditorGUILayout.Space(6);
// 구분선
var lineRect = EditorGUILayout.GetControlRect(false, 1);
EditorGUI.DrawRect(lineRect, new Color(0.4f, 0.4f, 0.4f, 0.5f));
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("Per-BlendShape Overrides", EditorStyles.miniLabel);
EditorGUILayout.PropertyField(blendShapeIntensityOverrides, GUIContent.none, true);
EditorGUILayout.EndVertical();
}
bool DrawSectionHeader(string title, bool expanded)
{
EditorGUILayout.Space(2);
var rect = EditorGUILayout.GetControlRect(false, 24);
EditorGUI.DrawRect(rect, HeaderColor);
// 폴드 화살표 + 타이틀
var foldRect = new Rect(rect.x + 4, rect.y, rect.width - 4, rect.height);
bool result = EditorGUI.Foldout(foldRect, expanded, " " + title, true, _headerStyle);
return result;
}
}