using System; using System.Collections.Generic; using Unity.Mathematics; using UnityEngine; using System.Collections; [System.Serializable] public enum MotionApplicationScope { All, // 전신 적용 ExcludeFingersOnly, // 손가락만 제외 ExcludeHandsAndFingers // 손목 + 손가락 제외 } [DefaultExecutionOrder(1)] public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour { [Header("OptiTrack 설정")] [Tooltip("OptiTrackStreamingClient가 포함된 오브젝트")] public OptitrackStreamingClient StreamingClient; [Tooltip("Motive의 스켈레톤 에셋 이름")] public string SkeletonAssetName = "Skeleton1"; private Animator TargetAnimator; [Header("모션 적용 범위")] [Tooltip("모션 캡처 데이터를 적용할 범위 선택")] public MotionApplicationScope motionScope = MotionApplicationScope.All; private OptitrackSkeletonDefinition m_skeletonDef; private Dictionary m_optitrackToHumanBoneMap; private string previousSkeletonName; [HideInInspector] public bool isSkeletonFound = false; private float updateInterval = 0.1f; void Start() { TargetAnimator = GetComponent(); InitializeStreamingClient(); // StreamingClient 등록 추가 if (StreamingClient != null) { StreamingClient.RegisterSkeleton(this, this.SkeletonAssetName); //Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'이(가) 등록되었습니다."); } InitializeBoneMapping(); // 주기적으로 스켈레톤 연결 상태를 확인하는 코루틴 시작 StartCoroutine(CheckSkeletonConnectionPeriodically()); } void Update() { if (TargetAnimator == null) return; // StreamingClient 체크 if (StreamingClient == null) { InitializeStreamingClient(); return; } // 스켈레톤 이름이 변경되었을 때 if (previousSkeletonName != SkeletonAssetName) { // 새 스켈레톤 등록 StreamingClient.RegisterSkeleton(this, SkeletonAssetName); //Debug.Log($"[OptiTrack] 새 스켈레톤 '{SkeletonAssetName}' 등록"); // 스켈레톤 정의 새로 가져오기 m_skeletonDef = StreamingClient.GetSkeletonDefinitionByName(SkeletonAssetName); if (m_skeletonDef == null) { //Debug.LogWarning($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'을(를) 찾을 수 없습니다. Motive에서 올바른 스켈레톤 이름을 확인해주세요.", this); previousSkeletonName = SkeletonAssetName; // 이름 업데이트 return; } //Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'을(를) 성공적으로 찾았습니다.", this); previousSkeletonName = SkeletonAssetName; // 이름 업데이트 } // 스켈레톤 정의가 없는 경우 체크 if (m_skeletonDef == null) { m_skeletonDef = StreamingClient.GetSkeletonDefinitionByName(SkeletonAssetName); if (m_skeletonDef == null) { return; } } // 최신 스켈레톤 상태 가져오기 OptitrackSkeletonState skelState = StreamingClient.GetLatestSkeletonState(m_skeletonDef.Id); if (skelState == null) { //Debug.LogWarning($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'의 상태가 null입니다. Motive에 마커가 제대로 트래킹되고 있는지 확인해주세요.", this); return; } // 각 본 업데이트 foreach (var bone in m_skeletonDef.Bones) { string boneName = bone.Name; string optitrackBoneName = boneName.Contains("_") ? boneName.Substring(boneName.IndexOf('_') + 1) : boneName; if (m_optitrackToHumanBoneMap.TryGetValue(optitrackBoneName, out HumanBodyBones humanBone)) { // 모션 스코프에 따른 본 필터링 if (!ShouldApplyMotionToBone(optitrackBoneName)) continue; Transform boneTransform = TargetAnimator.GetBoneTransform(humanBone); if (boneTransform != null) { if (skelState.BonePoses.TryGetValue(bone.Id, out OptitrackPose bonePose)) { // 손가락의 경우 로컬 포지션 데이터를 받지 않음 if (!IsFingerBone(optitrackBoneName)) { // 위치는 항상 업데이트 (Hip 등 루트 본의 경우) boneTransform.localPosition = bonePose.Position; } // 회전 업데이트 boneTransform.localRotation = bonePose.Orientation; } } } } } private void InitializeStreamingClient() { if (StreamingClient == null) { // 씬에서 OptitrackStreamingClient 찾기 StreamingClient = FindObjectOfType(); if (StreamingClient == null) { //Debug.LogWarning("씬에서 OptiTrack Streaming Client를 찾을 수 없습니다. 다음 프레임에서 다시 시도합니다.", this); } else { Debug.Log("OptiTrack Streaming Client를 찾았습니다.", this); } } } private void InitializeBoneMapping() { m_optitrackToHumanBoneMap = new Dictionary(); if (TargetAnimator == null || !TargetAnimator.isHuman) { Debug.LogError("휴머노이드 아바타가 설정되지 않았습니다.", this); return; } // OptiTrack 본 이름을 HumanBodyBones enum과 매핑 // 스켈레톤 에셋 이름을 제외한 기본 매핑 설정 SetupBoneNameMapping(); } private void SetupBoneNameMapping() { // 기본 본 매핑 (스켈레톤 에셋 이름 없이) m_optitrackToHumanBoneMap.Add("Hip", HumanBodyBones.Hips); m_optitrackToHumanBoneMap.Add("Ab", HumanBodyBones.Spine); m_optitrackToHumanBoneMap.Add("Chest", HumanBodyBones.Chest); m_optitrackToHumanBoneMap.Add("Neck", HumanBodyBones.Neck); m_optitrackToHumanBoneMap.Add("Head", HumanBodyBones.Head); // 왼쪽 팔 m_optitrackToHumanBoneMap.Add("LShoulder", HumanBodyBones.LeftShoulder); m_optitrackToHumanBoneMap.Add("LUArm", HumanBodyBones.LeftUpperArm); m_optitrackToHumanBoneMap.Add("LFArm", HumanBodyBones.LeftLowerArm); m_optitrackToHumanBoneMap.Add("LHand", HumanBodyBones.LeftHand); // 오른쪽 팔 m_optitrackToHumanBoneMap.Add("RShoulder", HumanBodyBones.RightShoulder); m_optitrackToHumanBoneMap.Add("RUArm", HumanBodyBones.RightUpperArm); m_optitrackToHumanBoneMap.Add("RFArm", HumanBodyBones.RightLowerArm); m_optitrackToHumanBoneMap.Add("RHand", HumanBodyBones.RightHand); // 왼쪽 다리 m_optitrackToHumanBoneMap.Add("LThigh", HumanBodyBones.LeftUpperLeg); m_optitrackToHumanBoneMap.Add("LShin", HumanBodyBones.LeftLowerLeg); m_optitrackToHumanBoneMap.Add("LFoot", HumanBodyBones.LeftFoot); m_optitrackToHumanBoneMap.Add("LToe", HumanBodyBones.LeftToes); // 오른쪽 다리 m_optitrackToHumanBoneMap.Add("RThigh", HumanBodyBones.RightUpperLeg); m_optitrackToHumanBoneMap.Add("RShin", HumanBodyBones.RightLowerLeg); m_optitrackToHumanBoneMap.Add("RFoot", HumanBodyBones.RightFoot); m_optitrackToHumanBoneMap.Add("RToe", HumanBodyBones.RightToes); // 왼쪽 손가락들 m_optitrackToHumanBoneMap.Add("LThumb1", HumanBodyBones.LeftThumbProximal); m_optitrackToHumanBoneMap.Add("LThumb2", HumanBodyBones.LeftThumbIntermediate); m_optitrackToHumanBoneMap.Add("LThumb3", HumanBodyBones.LeftThumbDistal); m_optitrackToHumanBoneMap.Add("LIndex1", HumanBodyBones.LeftIndexProximal); m_optitrackToHumanBoneMap.Add("LIndex2", HumanBodyBones.LeftIndexIntermediate); m_optitrackToHumanBoneMap.Add("LIndex3", HumanBodyBones.LeftIndexDistal); m_optitrackToHumanBoneMap.Add("LMiddle1", HumanBodyBones.LeftMiddleProximal); m_optitrackToHumanBoneMap.Add("LMiddle2", HumanBodyBones.LeftMiddleIntermediate); m_optitrackToHumanBoneMap.Add("LMiddle3", HumanBodyBones.LeftMiddleDistal); m_optitrackToHumanBoneMap.Add("LRing1", HumanBodyBones.LeftRingProximal); m_optitrackToHumanBoneMap.Add("LRing2", HumanBodyBones.LeftRingIntermediate); m_optitrackToHumanBoneMap.Add("LRing3", HumanBodyBones.LeftRingDistal); m_optitrackToHumanBoneMap.Add("LPinky1", HumanBodyBones.LeftLittleProximal); m_optitrackToHumanBoneMap.Add("LPinky2", HumanBodyBones.LeftLittleIntermediate); m_optitrackToHumanBoneMap.Add("LPinky3", HumanBodyBones.LeftLittleDistal); // 오른쪽 손가락들 m_optitrackToHumanBoneMap.Add("RThumb1", HumanBodyBones.RightThumbProximal); m_optitrackToHumanBoneMap.Add("RThumb2", HumanBodyBones.RightThumbIntermediate); m_optitrackToHumanBoneMap.Add("RThumb3", HumanBodyBones.RightThumbDistal); m_optitrackToHumanBoneMap.Add("RIndex1", HumanBodyBones.RightIndexProximal); m_optitrackToHumanBoneMap.Add("RIndex2", HumanBodyBones.RightIndexIntermediate); m_optitrackToHumanBoneMap.Add("RIndex3", HumanBodyBones.RightIndexDistal); m_optitrackToHumanBoneMap.Add("RMiddle1", HumanBodyBones.RightMiddleProximal); m_optitrackToHumanBoneMap.Add("RMiddle2", HumanBodyBones.RightMiddleIntermediate); m_optitrackToHumanBoneMap.Add("RMiddle3", HumanBodyBones.RightMiddleDistal); m_optitrackToHumanBoneMap.Add("RRing1", HumanBodyBones.RightRingProximal); m_optitrackToHumanBoneMap.Add("RRing2", HumanBodyBones.RightRingIntermediate); m_optitrackToHumanBoneMap.Add("RRing3", HumanBodyBones.RightRingDistal); m_optitrackToHumanBoneMap.Add("RPinky1", HumanBodyBones.RightLittleProximal); m_optitrackToHumanBoneMap.Add("RPinky2", HumanBodyBones.RightLittleIntermediate); m_optitrackToHumanBoneMap.Add("RPinky3", HumanBodyBones.RightLittleDistal); } private IEnumerator CheckSkeletonConnectionPeriodically() { while (true) { if (StreamingClient != null) { m_skeletonDef = StreamingClient.GetSkeletonDefinitionByName(SkeletonAssetName); if (m_skeletonDef != null) { OptitrackSkeletonState skelState = StreamingClient.GetLatestSkeletonState(m_skeletonDef.Id); isSkeletonFound = (skelState != null); if (isSkeletonFound && previousSkeletonName != SkeletonAssetName) { StreamingClient.RegisterSkeleton(this, SkeletonAssetName); previousSkeletonName = SkeletonAssetName; Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}' 연결 성공"); } } else { isSkeletonFound = false; } } yield return new WaitForSeconds(updateInterval); } } private bool IsFingerBone(string boneName) { // 손가락 관련 본들 확인 (양손 모두) return boneName.Contains("Thumb") || boneName.Contains("Index") || boneName.Contains("Middle") || boneName.Contains("Ring") || boneName.Contains("Pinky"); } private bool IsHandBone(string boneName) { // 손 관련 본들 확인 (양손 모두) return boneName.Contains("LHand") || boneName.Contains("RHand"); } private bool ShouldApplyMotionToBone(string boneName) { switch (motionScope) { case MotionApplicationScope.All: return true; case MotionApplicationScope.ExcludeFingersOnly: return !IsFingerBone(boneName); case MotionApplicationScope.ExcludeHandsAndFingers: return !IsFingerBone(boneName) && !IsHandBone(boneName); default: return true; } } }