316 lines
12 KiB
C#
316 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using System.Collections;
|
|
|
|
[System.Serializable]
|
|
public enum MotionApplicationScope
|
|
{
|
|
All, // 전신 적용
|
|
ExcludeFingersOnly, // 손가락만 제외
|
|
ExcludeHandsAndFingers // 손목 + 손가락 제외
|
|
}
|
|
|
|
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<string, HumanBodyBones> m_optitrackToHumanBoneMap;
|
|
|
|
private string previousSkeletonName;
|
|
|
|
[HideInInspector]
|
|
public bool isSkeletonFound = false;
|
|
|
|
private float updateInterval = 0.1f;
|
|
|
|
void Start()
|
|
{
|
|
TargetAnimator = GetComponent<Animator>();
|
|
if (TargetAnimator == null)
|
|
{
|
|
//Debug.LogError("이 게임오브젝트에서 Animator 컴포넌트를 찾을 수 없습니다.", this);
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
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))
|
|
{
|
|
// 위치는 항상 업데이트 (Hip 등 루트 본의 경우)
|
|
boneTransform.localPosition = bonePose.Position;
|
|
|
|
// 회전 업데이트
|
|
boneTransform.localRotation = bonePose.Orientation;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitializeStreamingClient()
|
|
{
|
|
if (StreamingClient == null)
|
|
{
|
|
// 씬에서 OptitrackStreamingClient 찾기
|
|
StreamingClient = FindObjectOfType<OptitrackStreamingClient>();
|
|
if (StreamingClient == null)
|
|
{
|
|
//Debug.LogWarning("씬에서 OptiTrack Streaming Client를 찾을 수 없습니다. 다음 프레임에서 다시 시도합니다.", this);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("OptiTrack Streaming Client를 찾았습니다.", this);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitializeBoneMapping()
|
|
{
|
|
m_optitrackToHumanBoneMap = new Dictionary<string, HumanBodyBones>();
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|