diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Editor/OptitrackSkeletonAnimatorEditor.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Editor/OptitrackSkeletonAnimatorEditor.cs index 6ff4f96a6..1d3b7fd3f 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Editor/OptitrackSkeletonAnimatorEditor.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Editor/OptitrackSkeletonAnimatorEditor.cs @@ -4,12 +4,57 @@ using UnityEngine; [CustomEditor(typeof(OptitrackSkeletonAnimator_Mingle))] public class OptitrackSkeletonAnimatorEditor : Editor { + private static readonly string[] k_FilterLabels = { "Off", "Low", "Medium", "High", "Custom" }; + public override void OnInspectorGUI() { DrawDefaultInspector(); var anim = (OptitrackSkeletonAnimator_Mingle)target; + // 필터 강도 버튼 나열 + EditorGUILayout.Space(4); + EditorGUILayout.LabelField("필터 강도", EditorStyles.boldLabel); + + EditorGUILayout.BeginHorizontal(); + for (int i = 0; i < k_FilterLabels.Length; i++) + { + var strength = (OptitrackSkeletonAnimator_Mingle.FilterStrength)i; + bool isSelected = anim.filterStrength == strength; + + var prevBgBtn = GUI.backgroundColor; + GUI.backgroundColor = isSelected ? new Color(0.4f, 0.7f, 1f) : Color.white; + + if (GUILayout.Button(k_FilterLabels[i], GUILayout.Height(24))) + { + Undo.RecordObject(anim, "Change Filter Strength"); + anim.SetFilterStrength(strength); + EditorUtility.SetDirty(anim); + } + + GUI.backgroundColor = prevBgBtn; + } + EditorGUILayout.EndHorizontal(); + + // Custom일 때 슬라이더 표시 + if (anim.filterStrength == OptitrackSkeletonAnimator_Mingle.FilterStrength.Custom) + { + EditorGUI.indentLevel++; + EditorGUI.BeginChangeCheck(); + float minCutoff = EditorGUILayout.Slider("Min Cutoff (Hz)", anim.filterMinCutoff, 0.1f, 10f); + float beta = EditorGUILayout.Slider("Beta", anim.filterBeta, 0f, 5f); + float maxCutoff = EditorGUILayout.Slider("Max Cutoff (Hz)", anim.filterMaxCutoff, 5f, 120f); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(anim, "Change Filter Custom Values"); + anim.filterMinCutoff = minCutoff; + anim.filterBeta = beta; + anim.filterMaxCutoff = maxCutoff; + EditorUtility.SetDirty(anim); + } + EditorGUI.indentLevel--; + } + // 연결 상태 배지 EditorGUILayout.Space(8); var prevBg = GUI.backgroundColor; diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs index 9a99205dd..3a7cba5bf 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs @@ -33,19 +33,53 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour [Tooltip("에디터에서 캡처한 T-포즈 데이터. 비어 있으면 런타임 CacheRestPose()를 사용합니다.")] public OptitrackRestPoseData RestPoseAsset; + public enum FilterStrength { Off, Low, Medium, High, Custom } + [Header("본 1€ 필터 (속도 적응형 저역통과)")] - [Tooltip("활성화 시 빠른 움직임은 그대로, 정지/느린 움직임의 노이즈를 제거합니다.\n단순 EMA보다 모션 보존이 훨씬 우수합니다.")] - public bool enableBoneFilter = true; - [Tooltip("최소 차단 주파수 (Hz). 정지 시 노이즈 제거 강도. 낮을수록 강함. 권장: 2~4 Hz")] - [Range(0.1f, 10f)] - public float filterMinCutoff = 3.0f; - [Tooltip("속도 계수. 빠른 동작에서 cutoff 상승 속도. 높을수록 지연 감소. 권장: 0.5~2.0")] - [Range(0f, 5f)] - public float filterBeta = 1.5f; - [Tooltip("최대 차단 주파수 상한 (Hz). 빠른 동작에서도 이 이상 주파수는 항상 제거됩니다.\n" + - "MagicaCloth2 지터가 빠른 동작에서 발생하면 낮추세요. 권장: 10~20 Hz")] - [Range(5f, 120f)] - public float filterMaxCutoff = 15.0f; + [HideInInspector] + public FilterStrength filterStrength = FilterStrength.Medium; + + [HideInInspector] public float filterMinCutoff = 3.0f; + [HideInInspector] public float filterBeta = 1.5f; + [HideInInspector] public float filterMaxCutoff = 15.0f; + + // 프리셋별 파라미터 (minCutoff, beta, maxCutoff) + private static readonly (float minCutoff, float beta, float maxCutoff)[] k_FilterPresets = + { + (0f, 0f, 0f), // Off (사용 안 함) + (5.0f, 2.0f, 25.0f), // Low + (3.0f, 1.5f, 15.0f), // Medium + (1.5f, 0.8f, 10.0f), // High + (0f, 0f, 0f), // Custom (프리셋 적용 안 함) + }; + + [HideInInspector] public bool enableBoneFilter = true; + + /// + /// 런타임에서 필터 강도를 변경합니다. StreamDeck/핫키 등에서 호출. + /// + public void SetFilterStrength(FilterStrength strength) + { + filterStrength = strength; + if (strength != FilterStrength.Off && strength != FilterStrength.Custom) + { + var preset = k_FilterPresets[(int)strength]; + filterMinCutoff = preset.minCutoff; + filterBeta = preset.beta; + filterMaxCutoff = preset.maxCutoff; + } + // 프리셋 전환 시 필터 상태 리셋 (이전 값과 불연속 방지) + m_filterStates.Clear(); + } + + /// + /// 현재 프리셋에서 다음 프리셋으로 순환. 버튼 하나로 Off→Low→Medium→High→Custom→Off... + /// + public void CycleFilterStrength() + { + int next = ((int)filterStrength + 1) % System.Enum.GetValues(typeof(FilterStrength)).Length; + SetFilterStrength((FilterStrength)next); + } private OptitrackSkeletonDefinition m_skeletonDef; private string previousSkeletonName; @@ -239,6 +273,9 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour return; } + // 필터 활성화 상태 동기화 (프리셋 값은 SetFilterStrength()에서만 적용) + enableBoneFilter = filterStrength != FilterStrength.Off; + // MirrorMode 변경 감지 → 필터 상태 리셋 (불연속 튐 방지) bool currentMirrorMode = StreamingClient != null && StreamingClient.MirrorMode; if (currentMirrorMode != m_lastMirrorMode) diff --git a/Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs b/Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs index c64595b8f..fdb3844cf 100644 --- a/Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs +++ b/Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs @@ -17,6 +17,9 @@ public class StreamingleFacialReceiverEditor : Editor private VisualElement statusContainer; private VisualElement emaFields; private VisualElement euroFields; + private VisualElement medianEuroFields; + private VisualElement sharedPortInfo; + private Label masterStatusValue; public override VisualElement CreateInspectorGUI() { @@ -40,6 +43,9 @@ public class StreamingleFacialReceiverEditor : Editor portButtonsContainer = root.Q("portButtonsContainer"); emaFields = root.Q("emaFields"); euroFields = root.Q("euroFields"); + medianEuroFields = root.Q("medianEuroFields"); + sharedPortInfo = root.Q("sharedPortInfo"); + masterStatusValue = root.Q