Fix : 페이셜 이름 업데이트
This commit is contained in:
parent
efc0adced8
commit
4e5634536a
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 223415c286f7fd74f85d05754d2da6ad
|
||||
guid: 07bce16f321d7734488143b6d237cc59
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
8
Assets/External/StreamingleFacial/Editor.meta
vendored
Normal file
8
Assets/External/StreamingleFacial/Editor.meta
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d38f0b9c853644840a90e334b9404fc1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
136
Assets/External/StreamingleFacial/Editor/BlendShapeIntensityOverrideDrawer.cs
vendored
Normal file
136
Assets/External/StreamingleFacial/Editor/BlendShapeIntensityOverrideDrawer.cs
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[CustomPropertyDrawer(typeof(StreamingleFacialReceiver.BlendShapeIntensityOverride))]
|
||||
public class BlendShapeIntensityOverrideDrawer : PropertyDrawer
|
||||
{
|
||||
// 카테고리별로 구분된 ARKit BlendShape 이름 (Popup에 구분선 역할)
|
||||
private static readonly string[] ARKitBlendShapeNames = new string[]
|
||||
{
|
||||
// Eye (0-13)
|
||||
"EyeBlinkLeft", "EyeBlinkRight",
|
||||
"EyeLookDownLeft", "EyeLookDownRight",
|
||||
"EyeLookInLeft", "EyeLookInRight",
|
||||
"EyeLookOutLeft", "EyeLookOutRight",
|
||||
"EyeLookUpLeft", "EyeLookUpRight",
|
||||
"EyeSquintLeft", "EyeSquintRight",
|
||||
"EyeWideLeft", "EyeWideRight",
|
||||
// Jaw (14-17)
|
||||
"JawForward", "JawLeft", "JawRight", "JawOpen",
|
||||
// Mouth (18-37)
|
||||
"MouthClose", "MouthFunnel", "MouthPucker",
|
||||
"MouthLeft", "MouthRight",
|
||||
"MouthSmileLeft", "MouthSmileRight",
|
||||
"MouthFrownLeft", "MouthFrownRight",
|
||||
"MouthDimpleLeft", "MouthDimpleRight",
|
||||
"MouthStretchLeft", "MouthStretchRight",
|
||||
"MouthRollLower", "MouthRollUpper",
|
||||
"MouthShrugLower", "MouthShrugUpper",
|
||||
"MouthPressLeft", "MouthPressRight",
|
||||
"MouthLowerDownLeft", "MouthLowerDownRight",
|
||||
"MouthUpperUpLeft", "MouthUpperUpRight",
|
||||
// Brow (38-42)
|
||||
"BrowDownLeft", "BrowDownRight",
|
||||
"BrowInnerUp",
|
||||
"BrowOuterUpLeft", "BrowOuterUpRight",
|
||||
// Cheek/Nose (43-47)
|
||||
"CheekPuff", "CheekSquintLeft", "CheekSquintRight",
|
||||
"NoseSneerLeft", "NoseSneerRight",
|
||||
// Tongue (48)
|
||||
"TongueOut",
|
||||
};
|
||||
|
||||
// 카테고리 구분 표시용
|
||||
private static readonly string[] DisplayNames;
|
||||
private static readonly Dictionary<string, int> nameToIndex;
|
||||
|
||||
static BlendShapeIntensityOverrideDrawer()
|
||||
{
|
||||
nameToIndex = new Dictionary<string, int>(System.StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 카테고리 프리픽스 부여
|
||||
DisplayNames = new string[ARKitBlendShapeNames.Length];
|
||||
for (int i = 0; i < ARKitBlendShapeNames.Length; i++)
|
||||
{
|
||||
string name = ARKitBlendShapeNames[i];
|
||||
string category;
|
||||
|
||||
if (name.StartsWith("Eye")) category = "Eye";
|
||||
else if (name.StartsWith("Jaw")) category = "Jaw";
|
||||
else if (name.StartsWith("Mouth")) category = "Mouth";
|
||||
else if (name.StartsWith("Brow")) category = "Brow";
|
||||
else if (name.StartsWith("Cheek") || name.StartsWith("Nose")) category = "Cheek-Nose";
|
||||
else if (name.StartsWith("Tongue")) category = "Tongue";
|
||||
else category = "";
|
||||
|
||||
DisplayNames[i] = category + "/" + name;
|
||||
nameToIndex[name] = i;
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight + 2f;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
position.y += 1f;
|
||||
position.height -= 2f;
|
||||
|
||||
var nameProp = property.FindPropertyRelative("blendShapeName");
|
||||
var intensityProp = property.FindPropertyRelative("intensity");
|
||||
|
||||
// 새로 추가된 항목의 기본값 보정
|
||||
if (intensityProp.floatValue == 0f && string.IsNullOrEmpty(nameProp.stringValue))
|
||||
{
|
||||
intensityProp.floatValue = 1.0f;
|
||||
}
|
||||
|
||||
// 레이아웃: [드롭다운 55%] [슬라이더 35%] [값 라벨 10%]
|
||||
float dropW = position.width * 0.55f;
|
||||
float sliderW = position.width * 0.35f;
|
||||
float valW = position.width * 0.10f - 6f;
|
||||
|
||||
Rect dropRect = new Rect(position.x, position.y, dropW - 2f, position.height);
|
||||
Rect sliderRect = new Rect(position.x + dropW + 2f, position.y, sliderW - 2f, position.height);
|
||||
Rect valRect = new Rect(position.x + dropW + sliderW + 4f, position.y, valW, position.height);
|
||||
|
||||
// 현재 인덱스
|
||||
int currentIndex = 0;
|
||||
if (!string.IsNullOrEmpty(nameProp.stringValue) && nameToIndex.TryGetValue(nameProp.stringValue, out int idx))
|
||||
{
|
||||
currentIndex = idx;
|
||||
}
|
||||
|
||||
// 드롭다운 (카테고리 구분)
|
||||
int newIndex = EditorGUI.Popup(dropRect, currentIndex, DisplayNames);
|
||||
if (newIndex != currentIndex || string.IsNullOrEmpty(nameProp.stringValue))
|
||||
{
|
||||
nameProp.stringValue = ARKitBlendShapeNames[newIndex];
|
||||
}
|
||||
|
||||
// 슬라이더
|
||||
intensityProp.floatValue = GUI.HorizontalSlider(sliderRect, intensityProp.floatValue, 0f, 3f);
|
||||
|
||||
// 값 표시 (색상으로 강약 표현)
|
||||
float val = intensityProp.floatValue;
|
||||
Color valColor;
|
||||
if (val < 0.5f) valColor = new Color(1f, 0.4f, 0.4f); // 약함 = 빨강
|
||||
else if (val > 1.5f) valColor = new Color(0.4f, 0.8f, 1f); // 강함 = 파랑
|
||||
else valColor = new Color(0.7f, 0.7f, 0.7f); // 보통 = 회색
|
||||
|
||||
var valStyle = new GUIStyle(EditorStyles.miniLabel)
|
||||
{
|
||||
alignment = TextAnchor.MiddleRight,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
valStyle.normal.textColor = valColor;
|
||||
EditorGUI.LabelField(valRect, $"x{val:F1}", valStyle);
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
2
Assets/External/StreamingleFacial/Editor/BlendShapeIntensityOverrideDrawer.cs.meta
vendored
Normal file
2
Assets/External/StreamingleFacial/Editor/BlendShapeIntensityOverrideDrawer.cs.meta
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92034514226264240963a52d16489da8
|
||||
263
Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs
vendored
Normal file
263
Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
2
Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs.meta
vendored
Normal file
2
Assets/External/StreamingleFacial/Editor/StreamingleFacialReceiverEditor.cs.meta
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49f85d984656bc24583e253320766599
|
||||
@ -5,31 +5,30 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
|
||||
public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
public class StreamingleFacialReceiver : MonoBehaviour
|
||||
{
|
||||
// broadcast address
|
||||
public bool gameStartWithConnect = true;
|
||||
public string iOS_IPAddress = "255.255.255.255";
|
||||
public bool mirrorMode = false; // 좌우 반전 모드 설정
|
||||
private UdpClient client;
|
||||
public bool mirrorMode = true; // 좌우 반전 모드 설정
|
||||
private bool StartFlag = true;
|
||||
|
||||
//object references
|
||||
public SkinnedMeshRenderer[] faceMeshRenderers;
|
||||
public Transform headBone;
|
||||
public Transform rightEyeBone;
|
||||
public Transform leftEyeBone;
|
||||
public Transform headPositionObject;
|
||||
|
||||
private UdpClient udp;
|
||||
private Thread thread;
|
||||
private volatile bool isThreadRunning = false; // 스레드 실행 상태 플래그
|
||||
private string messageString = "";
|
||||
private string lastProcessedMessage = ""; // 이전 메시지 저장용
|
||||
public int LOCAL_PORT = 49983;
|
||||
|
||||
// 포트 핫스왑 시스템
|
||||
[Header("Port Hot-Swap")]
|
||||
[Tooltip("사용 가능한 아이폰 포트 목록")]
|
||||
public int[] availablePorts = new int[] { 40001, 40002, 40003, 40004, 40005 };
|
||||
[Tooltip("현재 활성화된 포트 인덱스 (0~4)")]
|
||||
[Range(0, 4)]
|
||||
public int activePortIndex = 0;
|
||||
public int LOCAL_PORT => availablePorts != null && activePortIndex < availablePorts.Length ? availablePorts[activePortIndex] : 49983;
|
||||
|
||||
// 데이터 필터링 설정
|
||||
[Header("Data Filtering")]
|
||||
@ -37,7 +36,7 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
public bool enableFiltering = true;
|
||||
[Tooltip("스무딩 강도 (0=필터없음, 1=최대 스무딩). 프레임레이트 독립적으로 동작")]
|
||||
[Range(0f, 0.95f)]
|
||||
public float smoothingFactor = 0.5f;
|
||||
public float smoothingFactor = 0.1f;
|
||||
[Tooltip("프레임 간 최대 허용 변화량 (BlendShape, 0~100 스케일)")]
|
||||
[Range(1f, 100f)]
|
||||
public float maxBlendShapeDelta = 30f;
|
||||
@ -53,14 +52,10 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
|
||||
// 필터링용 이전 값 저장
|
||||
private Dictionary<string, float> prevBlendShapeValues = new Dictionary<string, float>();
|
||||
private Dictionary<string, Vector3> prevBoneRotations = new Dictionary<string, Vector3>();
|
||||
private Vector3 prevHeadPosition = Vector3.zero;
|
||||
private bool hasFirstFrame = false;
|
||||
|
||||
// 연속 스파이크 추적 (같은 방향으로 연속이면 실제 움직임)
|
||||
private Dictionary<string, int> blendShapeSpikeCount = new Dictionary<string, int>();
|
||||
private Dictionary<string, float> blendShapeSpikeDirection = new Dictionary<string, float>();
|
||||
private Dictionary<string, int> boneSpikeCount = new Dictionary<string, int>();
|
||||
|
||||
// 빠르게 변하는 BlendShape 목록 (눈 깜빡임, 입 등)
|
||||
private static readonly HashSet<string> FastBlendShapes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
@ -74,6 +69,28 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
"mouthfrownright", "mouthfrownleft",
|
||||
};
|
||||
|
||||
// 페이셜 개별 강도 조절
|
||||
[Header("Facial Intensity")]
|
||||
[Tooltip("전체 BlendShape 강도 배율 (1.0 = 원본)")]
|
||||
[Range(0f, 3f)]
|
||||
public float globalIntensity = 1.0f;
|
||||
|
||||
[Tooltip("개별 BlendShape 강도 오버라이드 (이름, 배율). 여기에 없는 항목은 globalIntensity 적용")]
|
||||
public List<BlendShapeIntensityOverride> blendShapeIntensityOverrides = new List<BlendShapeIntensityOverride>();
|
||||
|
||||
[System.Serializable]
|
||||
public class BlendShapeIntensityOverride
|
||||
{
|
||||
[Tooltip("ARKit BlendShape 이름 (예: EyeBlinkLeft, JawOpen, MouthSmileLeft 등)")]
|
||||
public string blendShapeName;
|
||||
[Range(0f, 3f)]
|
||||
public float intensity = 1.0f;
|
||||
}
|
||||
|
||||
// 런타임 빠른 조회용 Dictionary
|
||||
private Dictionary<string, float> intensityOverrideMap = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
|
||||
private bool intensityMapDirty = true;
|
||||
|
||||
// 프레임레이트 독립 스무딩을 위한 기준 FPS
|
||||
private const float ReferenceFPS = 60f;
|
||||
|
||||
@ -83,8 +100,6 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
private readonly char[] splitPipe = new char[] { '|' };
|
||||
private readonly char[] splitAnd = new char[] { '&' };
|
||||
private readonly char[] splitDash = new char[] { '-' };
|
||||
private readonly char[] splitHash = new char[] { '#' };
|
||||
private readonly char[] splitComma = new char[] { ',' };
|
||||
|
||||
// Mirror mode용 정적 매핑 테이블
|
||||
private static readonly Dictionary<string, string> EyeMirrorMap = new Dictionary<string, string>() {
|
||||
@ -129,12 +144,6 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
// BlendShape 인덱스 캐싱 초기화
|
||||
InitializeBlendShapeCache();
|
||||
|
||||
//Send to iOS
|
||||
if (gameStartWithConnect == true)
|
||||
{
|
||||
Connect_to_iOS_App();
|
||||
}
|
||||
|
||||
//Recieve udp from iOS
|
||||
CreateUdpServer();
|
||||
}
|
||||
@ -206,57 +215,41 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator WaitProcess(float WaitTime)
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
yield return new WaitForSeconds(WaitTime);
|
||||
intensityMapDirty = true;
|
||||
}
|
||||
|
||||
void Connect_to_iOS_App()
|
||||
void RebuildIntensityOverrideMap()
|
||||
{
|
||||
try
|
||||
intensityOverrideMap.Clear();
|
||||
foreach (var entry in blendShapeIntensityOverrides)
|
||||
{
|
||||
//iFacialMocap
|
||||
SendMessage_to_iOSapp("iFacialMocap_sahuasouryya9218sauhuiayeta91555dy3719", 49983);
|
||||
|
||||
//Facemotion3d
|
||||
SendMessage_to_iOSapp("FACEMOTION3D_OtherStreaming", 49993);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[iFacialMocap] iOS 앱 연결 실패: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
void StopStreaming_iOS_App()
|
||||
{
|
||||
SendMessage_to_iOSapp("StopStreaming_FACEMOTION3D", 49993);
|
||||
}
|
||||
|
||||
//iOSアプリに通信開始のメッセージを送信
|
||||
//Send a message to the iOS application to start streaming
|
||||
void SendMessage_to_iOSapp(string sendMessage, int send_port)
|
||||
{
|
||||
try
|
||||
{
|
||||
client = new UdpClient();
|
||||
client.Connect(iOS_IPAddress, send_port);
|
||||
byte[] dgram = Encoding.UTF8.GetBytes(sendMessage);
|
||||
|
||||
// 메시지 전송 시도
|
||||
for (int i = 0; i < 5; i++)
|
||||
if (!string.IsNullOrEmpty(entry.blendShapeName))
|
||||
{
|
||||
client.Send(dgram, dgram.Length);
|
||||
intensityOverrideMap[entry.blendShapeName] = entry.intensity;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[iFacialMocap] 메시지 전송 실패: {e.Message}");
|
||||
}
|
||||
intensityMapDirty = false;
|
||||
}
|
||||
|
||||
float GetBlendShapeIntensity(string normalizedName)
|
||||
{
|
||||
if (intensityOverrideMap.TryGetValue(normalizedName, out float val))
|
||||
return val * globalIntensity;
|
||||
return globalIntensity;
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
// 강도 오버라이드 맵 갱신 (인스펙터 변경 반영)
|
||||
if (intensityMapDirty)
|
||||
{
|
||||
RebuildIntensityOverrideMap();
|
||||
}
|
||||
|
||||
// 메시지가 변경되었을 때만 처리 (성능 최적화)
|
||||
if (!string.IsNullOrEmpty(messageString) && messageString != lastProcessedMessage)
|
||||
{
|
||||
@ -284,6 +277,10 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
// 정규화된 이름으로 캐시 검색
|
||||
string normalizedName = NormalizeBlendShapeName(shapeName).ToLowerInvariant();
|
||||
|
||||
// 강도 배율 적용
|
||||
weight *= GetBlendShapeIntensity(normalizedName);
|
||||
weight = Mathf.Clamp(weight, 0f, 100f);
|
||||
|
||||
// 필터링 적용
|
||||
if (enableFiltering)
|
||||
{
|
||||
@ -375,74 +372,7 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// 본 회전 처리
|
||||
string[] boneMessages = strArray1[1].Split(splitPipe, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (string message in boneMessages)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message)) continue;
|
||||
|
||||
string[] strArray2 = message.Split(splitHash, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (strArray2.Length == 2)
|
||||
{
|
||||
string[] commaList = strArray2[1].Split(splitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 파싱 한 번만 수행
|
||||
if (commaList.Length < 3) continue;
|
||||
|
||||
float x = float.Parse(commaList[0], CultureInfo.InvariantCulture);
|
||||
float y = float.Parse(commaList[1], CultureInfo.InvariantCulture);
|
||||
float z = float.Parse(commaList[2], CultureInfo.InvariantCulture);
|
||||
|
||||
if (mirrorMode)
|
||||
{
|
||||
y = -y; // Y축 회전 반전
|
||||
}
|
||||
|
||||
Vector3 rotation = new Vector3(x, y, z);
|
||||
|
||||
// 본 회전 필터링 적용
|
||||
if (enableFiltering)
|
||||
{
|
||||
rotation = FilterBoneRotation(strArray2[0], rotation);
|
||||
}
|
||||
|
||||
switch (strArray2[0])
|
||||
{
|
||||
case "head" when headBone != null:
|
||||
headBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, -rotation.z);
|
||||
|
||||
if (headPositionObject != null && commaList.Length >= 6)
|
||||
{
|
||||
float posX = -float.Parse(commaList[3], CultureInfo.InvariantCulture);
|
||||
float posY = float.Parse(commaList[4], CultureInfo.InvariantCulture);
|
||||
float posZ = float.Parse(commaList[5], CultureInfo.InvariantCulture);
|
||||
|
||||
if (mirrorMode)
|
||||
{
|
||||
posX = -posX;
|
||||
}
|
||||
|
||||
Vector3 newPos = new Vector3(posX, posY, posZ);
|
||||
if (enableFiltering)
|
||||
{
|
||||
newPos = FilterHeadPosition(newPos);
|
||||
}
|
||||
headPositionObject.localPosition = newPos;
|
||||
}
|
||||
break;
|
||||
|
||||
case "rightEye" when rightEyeBone != null:
|
||||
rightEyeBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, rotation.z);
|
||||
break;
|
||||
|
||||
case "leftEye" when leftEyeBone != null:
|
||||
leftEyeBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, rotation.z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -529,11 +459,6 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
|
||||
public void StopUDP()
|
||||
{
|
||||
if (gameStartWithConnect == true)
|
||||
{
|
||||
StopStreaming_iOS_App();
|
||||
}
|
||||
|
||||
// 안전한 스레드 종료
|
||||
isThreadRunning = false;
|
||||
|
||||
@ -557,6 +482,22 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 포트 핫스왑: 지정된 인덱스의 포트로 즉시 전환
|
||||
/// </summary>
|
||||
public void SwitchToPort(int portIndex)
|
||||
{
|
||||
if (availablePorts == null || portIndex < 0 || portIndex >= availablePorts.Length)
|
||||
{
|
||||
Debug.LogError($"[iFacialMocap] 잘못된 포트 인덱스: {portIndex}");
|
||||
return;
|
||||
}
|
||||
|
||||
activePortIndex = portIndex;
|
||||
Debug.Log($"[iFacialMocap] 포트 전환: {availablePorts[portIndex]}");
|
||||
Reconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 페이셜 모션 캡처 재접속
|
||||
/// </summary>
|
||||
@ -666,92 +607,9 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 본 회전 필터링: 연속 스파이크 판별 + 프레임독립 EMA
|
||||
/// </summary>
|
||||
Vector3 FilterBoneRotation(string boneName, Vector3 rawRotation)
|
||||
{
|
||||
if (prevBoneRotations.TryGetValue(boneName, out Vector3 prevRot))
|
||||
{
|
||||
float delta = Vector3.Distance(rawRotation, prevRot);
|
||||
|
||||
if (delta > maxRotationDelta)
|
||||
{
|
||||
int count = 0;
|
||||
boneSpikeCount.TryGetValue(boneName, out count);
|
||||
count++;
|
||||
boneSpikeCount[boneName] = count;
|
||||
|
||||
// 연속이면 실제 움직임
|
||||
if (count >= spikeToleranceFrames)
|
||||
{
|
||||
boneSpikeCount[boneName] = 0;
|
||||
prevBoneRotations[boneName] = rawRotation;
|
||||
return rawRotation;
|
||||
}
|
||||
|
||||
Vector3 clamped = Vector3.MoveTowards(prevRot, rawRotation, maxRotationDelta);
|
||||
prevBoneRotations[boneName] = clamped;
|
||||
return clamped;
|
||||
}
|
||||
|
||||
boneSpikeCount[boneName] = 0;
|
||||
|
||||
float alpha = GetFrameIndependentSmoothing();
|
||||
Vector3 smoothed = Vector3.Lerp(rawRotation, prevRot, alpha);
|
||||
prevBoneRotations[boneName] = smoothed;
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
prevBoneRotations[boneName] = rawRotation;
|
||||
return rawRotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 머리 위치 필터링
|
||||
/// </summary>
|
||||
Vector3 FilterHeadPosition(Vector3 rawPos)
|
||||
{
|
||||
if (hasFirstFrame)
|
||||
{
|
||||
float maxPosDelta = maxRotationDelta * 0.01f;
|
||||
float delta = Vector3.Distance(rawPos, prevHeadPosition);
|
||||
|
||||
if (delta > maxPosDelta)
|
||||
{
|
||||
Vector3 clamped = Vector3.MoveTowards(prevHeadPosition, rawPos, maxPosDelta);
|
||||
prevHeadPosition = clamped;
|
||||
return clamped;
|
||||
}
|
||||
|
||||
float alpha = GetFrameIndependentSmoothing();
|
||||
Vector3 smoothed = Vector3.Lerp(rawPos, prevHeadPosition, alpha);
|
||||
prevHeadPosition = smoothed;
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
hasFirstFrame = true;
|
||||
prevHeadPosition = rawPos;
|
||||
return rawPos;
|
||||
}
|
||||
|
||||
private bool HasBlendShapes(SkinnedMeshRenderer skin)
|
||||
{
|
||||
if (!skin.sharedMesh)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skin.sharedMesh.blendShapeCount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
public static class FM3D_and_iFacialMocap_GetAllChildren
|
||||
public static class StreamingleFacialReceiverExtensions
|
||||
{
|
||||
public static List<GameObject> GetAll(this GameObject obj)
|
||||
{
|
||||
Loading…
x
Reference in New Issue
Block a user