Add : 모션 녹화 시스템 업데이트 패치

This commit is contained in:
qsxft258@gmail.com 2025-08-22 07:02:00 +09:00
parent da526dc72b
commit c73c7cbc18
23 changed files with 304 additions and 47 deletions

View File

@ -267,7 +267,7 @@ namespace Entum
FrameIndex++;
}
private void RecordStart()
public void RecordStart()
{
if (_recording)
{
@ -421,7 +421,7 @@ namespace Entum
Debug.Log("T-포즈 데이터가 저장되었습니다.");
}
private void RecordEnd()
public void RecordEnd()
{
if (!_recording)
{

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6f705ffb70fa1a140bf73185aef44529
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4b1499ea416adce4daaed7b0b3b2b001
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3c4b192ca3a426b48819a1106d1d927b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9000000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9a46274a4e01e9d45bc0a3f2c25d5d02
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f5c6e42d92f8225488a96dfaf88b9051
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d1094b1299f0ba44b9f618231c354db5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 23aa04f3a9d0bb84bba05888737ec16e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 4300000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e40a686a525bda34ebf267268d8b2616
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c470e4106f9329048b690874ec8ef603
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -21,7 +21,7 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
[Tooltip("Motive의 스켈레톤 에셋 이름")]
public string SkeletonAssetName = "Skeleton1";
private Animator TargetAnimator;
public Animator TargetAnimator;
[Header("모션 적용 범위")]
[Tooltip("모션 캡처 데이터를 적용할 범위 선택")]
@ -37,18 +37,8 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
private float updateInterval = 0.1f;
// Actor 컴포넌트 참조 (Rokoko Actor와 동기화용)
private Rokoko.Inputs.Actor m_actor;
void Start()
{
TargetAnimator = GetComponent<Animator>();
if (TargetAnimator == null)
{
//Debug.LogError("이 게임오브젝트에서 Animator 컴포넌트를 찾을 수 없습니다.", this);
enabled = false;
return;
}
InitializeStreamingClient();
@ -61,9 +51,6 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
InitializeBoneMapping();
// Actor 컴포넌트 찾기 및 프로파일 이름 동기화
SyncActorProfileName();
// 주기적으로 스켈레톤 연결 상태를 확인하는 코루틴 시작
StartCoroutine(CheckSkeletonConnectionPeriodically());
}
@ -136,8 +123,12 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
{
if (skelState.BonePoses.TryGetValue(bone.Id, out OptitrackPose bonePose))
{
// 위치는 항상 업데이트 (Hip 등 루트 본의 경우)
boneTransform.localPosition = bonePose.Position;
// 손가락의 경우 로컬 포지션 데이터를 받지 않음
if (!IsFingerBone(optitrackBoneName))
{
// 위치는 항상 업데이트 (Hip 등 루트 본의 경우)
boneTransform.localPosition = bonePose.Position;
}
// 회전 업데이트
boneTransform.localRotation = bonePose.Orientation;
@ -178,27 +169,6 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
// 스켈레톤 에셋 이름을 제외한 기본 매핑 설정
SetupBoneNameMapping();
}
/// <summary>
/// Actor 컴포넌트의 프로파일 이름을 OptiTrack 스켈레톤 이름과 동기화
/// </summary>
private void SyncActorProfileName()
{
// 같은 게임오브젝트에서 Actor 컴포넌트 찾기
m_actor = GetComponent<Rokoko.Inputs.Actor>();
if (m_actor != null)
{
// Actor의 프로파일 이름을 OptiTrack 스켈레톤 이름으로 설정
m_actor.profileName = this.SkeletonAssetName;
Debug.Log($"[OptiTrack] Actor 프로파일 이름을 '{SkeletonAssetName}'으로 동기화했습니다.", this);
}
else
{
Debug.LogWarning("[OptiTrack] 같은 게임오브젝트에서 Actor 컴포넌트를 찾을 수 없습니다.", this);
}
}
private void SetupBoneNameMapping()
{
// 기본 본 매핑 (스켈레톤 에셋 이름 없이)

Binary file not shown.

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e0123bef6b5bef14abc800ff6ed2ded4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,192 @@
using System.Collections.Generic;
using UnityEngine;
namespace KindRetargeting
{
/// <summary>
/// 아바타 간의 회전 오프셋을 계산하고 포즈 데이터를 복사하는 기능을 제공합니다.
/// </summary>
public class OffsetTransfer : MonoBehaviour
{
[Header("아바타 설정")]
[SerializeField] public Animator sourceAnimator; // 원본 아바타의 Animator
[SerializeField] public Animator targetAnimator; // 대상 아바타의 Animator
[Header("오프셋 설정")]
[SerializeField] private bool autoCalculateOnStart = true; // 시작 시 자동 계산 여부
[SerializeField] private bool autoCopyPose = true; // 매 프레임마다 자동으로 포즈 복사 여부
// 본별 회전 오프셋을 저장하는 딕셔너리
private Dictionary<HumanBodyBones, Quaternion> rotationOffsets = new Dictionary<HumanBodyBones, Quaternion>();
#region
/// <summary>
/// 계산된 회전 오프셋 딕셔너리를 반환합니다.
/// </summary>
public Dictionary<HumanBodyBones, Quaternion> RotationOffsets => rotationOffsets;
#endregion
#region
private void Start()
{
if (autoCalculateOnStart)
{
CalculateRotationOffsets();
}
}
#endregion
#region
private void Update()
{
if (autoCopyPose)
{
CopyPoseToTarget();
}
}
private void LateUpdate()
{
if (autoCopyPose)
{
CopyPoseToTarget();
}
}
#endregion
#region
/// <summary>
/// 원본과 대상 아바타의 각 본 간 회전 오프셋을 계산하여 저장합니다.
/// </summary>
public void CalculateRotationOffsets()
{
if (sourceAnimator == null || targetAnimator == null)
{
Debug.LogError("소스 또는 타겟 Animator가 설정되지 않았습니다.");
return;
}
// Dictionary가 null이면 초기화
if (rotationOffsets == null)
{
rotationOffsets = new Dictionary<HumanBodyBones, Quaternion>();
}
// 모든 본에 대해 오프셋 계산 (기본 몸체 본 + 손가락 본 + UpperChest)
for (int i = 0; i <= 54; i++)
{
HumanBodyBones bone = (HumanBodyBones)i;
Transform sourceBone = sourceAnimator.GetBoneTransform(bone);
Transform targetBone = targetAnimator.GetBoneTransform(bone);
if (sourceBone != null && targetBone != null)
{
Quaternion offset = Quaternion.Inverse(sourceBone.rotation) * targetBone.rotation;
if (rotationOffsets.ContainsKey(bone))
{
rotationOffsets[bone] = offset;
}
else
{
rotationOffsets.Add(bone, offset);
}
}
}
Debug.Log($"회전 오프셋 계산 완료: {rotationOffsets.Count}개 본");
}
/// <summary>
/// 특정 본의 회전 오프셋을 반환합니다.
/// </summary>
/// <param name="bone">본 타입</param>
/// <returns>회전 오프셋 (없으면 Identity)</returns>
public Quaternion GetRotationOffset(HumanBodyBones bone)
{
if (rotationOffsets.ContainsKey(bone))
{
return rotationOffsets[bone];
}
return Quaternion.identity;
}
/// <summary>
/// 특정 본에 회전 오프셋을 적용합니다.
/// </summary>
/// <param name="bone">본 타입</param>
/// <param name="originalRotation">원본 회전값</param>
/// <returns>오프셋이 적용된 회전값</returns>
public Quaternion ApplyRotationOffset(HumanBodyBones bone, Quaternion originalRotation)
{
if (rotationOffsets.ContainsKey(bone))
{
return originalRotation * rotationOffsets[bone];
}
return originalRotation;
}
#endregion
#region
/// <summary>
/// 원본 아바타의 포즈를 대상 아바타에 복사합니다.
/// </summary>
public void CopyPoseToTarget()
{
if (sourceAnimator == null || targetAnimator == null)
{
Debug.LogError("소스 또는 타겟 Animator가 설정되지 않았습니다.");
return;
}
// 모든 본에 대해 포즈 복사
for (int i = 0; i <= 54; i++)
{
HumanBodyBones bone = (HumanBodyBones)i;
Transform sourceBone = sourceAnimator.GetBoneTransform(bone);
Transform targetBone = targetAnimator.GetBoneTransform(bone);
if (sourceBone != null && targetBone != null)
{
// 회전은 오프셋 적용, 위치는 그대로 복사
Quaternion targetRotation = ApplyRotationOffset(bone, sourceBone.rotation);
targetBone.rotation = targetRotation;
targetBone.position = sourceBone.position;
}
}
}
/// <summary>
/// 특정 본의 포즈를 복사합니다.
/// </summary>
/// <param name="bone">복사할 본</param>
public void CopyBonePose(HumanBodyBones bone)
{
if (sourceAnimator == null || targetAnimator == null)
{
Debug.LogError("소스 또는 타겟 Animator가 설정되지 않았습니다.");
return;
}
Transform sourceBone = sourceAnimator.GetBoneTransform(bone);
Transform targetBone = targetAnimator.GetBoneTransform(bone);
if (sourceBone != null && targetBone != null)
{
Quaternion targetRotation = ApplyRotationOffset(bone, sourceBone.rotation);
targetBone.rotation = targetRotation;
}
}
#endregion
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2252970a315b77e4eacbd7dcfb654842