693 lines
29 KiB
C#
693 lines
29 KiB
C#
/**
|
|
[EasyMotionRecorder]
|
|
|
|
Copyright (c) 2018 Duo.inc
|
|
|
|
This software is released under the MIT License.
|
|
http://opensource.org/licenses/mit-license.php
|
|
*/
|
|
|
|
using UnityEngine;
|
|
using System;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.IO;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
using EasyMotionRecorder;
|
|
|
|
namespace Entum
|
|
{
|
|
[Serializable]
|
|
public class MotionDataSettings
|
|
{
|
|
public enum Rootbonesystem
|
|
{
|
|
Hipbone,
|
|
Objectroot
|
|
}
|
|
|
|
/// <summary>
|
|
/// Humanoid用のMuscleマッピング
|
|
/// </summary>
|
|
public static Dictionary<string, string> TraitPropMap = new Dictionary<string, string>
|
|
{
|
|
{"Left Thumb 1 Stretched", "LeftHand.Thumb.1 Stretched"},
|
|
{"Left Thumb Spread", "LeftHand.Thumb.Spread"},
|
|
{"Left Thumb 2 Stretched", "LeftHand.Thumb.2 Stretched"},
|
|
{"Left Thumb 3 Stretched", "LeftHand.Thumb.3 Stretched"},
|
|
{"Left Index 1 Stretched", "LeftHand.Index.1 Stretched"},
|
|
{"Left Index Spread", "LeftHand.Index.Spread"},
|
|
{"Left Index 2 Stretched", "LeftHand.Index.2 Stretched"},
|
|
{"Left Index 3 Stretched", "LeftHand.Index.3 Stretched"},
|
|
{"Left Middle 1 Stretched", "LeftHand.Middle.1 Stretched"},
|
|
{"Left Middle Spread", "LeftHand.Middle.Spread"},
|
|
{"Left Middle 2 Stretched", "LeftHand.Middle.2 Stretched"},
|
|
{"Left Middle 3 Stretched", "LeftHand.Middle.3 Stretched"},
|
|
{"Left Ring 1 Stretched", "LeftHand.Ring.1 Stretched"},
|
|
{"Left Ring Spread", "LeftHand.Ring.Spread"},
|
|
{"Left Ring 2 Stretched", "LeftHand.Ring.2 Stretched"},
|
|
{"Left Ring 3 Stretched", "LeftHand.Ring.3 Stretched"},
|
|
{"Left Little 1 Stretched", "LeftHand.Little.1 Stretched"},
|
|
{"Left Little Spread", "LeftHand.Little.Spread"},
|
|
{"Left Little 2 Stretched", "LeftHand.Little.2 Stretched"},
|
|
{"Left Little 3 Stretched", "LeftHand.Little.3 Stretched"},
|
|
{"Right Thumb 1 Stretched", "RightHand.Thumb.1 Stretched"},
|
|
{"Right Thumb Spread", "RightHand.Thumb.Spread"},
|
|
{"Right Thumb 2 Stretched", "RightHand.Thumb.2 Stretched"},
|
|
{"Right Thumb 3 Stretched", "RightHand.Thumb.3 Stretched"},
|
|
{"Right Index 1 Stretched", "RightHand.Index.1 Stretched"},
|
|
{"Right Index Spread", "RightHand.Index.Spread"},
|
|
{"Right Index 2 Stretched", "RightHand.Index.2 Stretched"},
|
|
{"Right Index 3 Stretched", "RightHand.Index.3 Stretched"},
|
|
{"Right Middle 1 Stretched", "RightHand.Middle.1 Stretched"},
|
|
{"Right Middle Spread", "RightHand.Middle.Spread"},
|
|
{"Right Middle 2 Stretched", "RightHand.Middle.2 Stretched"},
|
|
{"Right Middle 3 Stretched", "RightHand.Middle.3 Stretched"},
|
|
{"Right Ring 1 Stretched", "RightHand.Ring.1 Stretched"},
|
|
{"Right Ring Spread", "RightHand.Ring.Spread"},
|
|
{"Right Ring 2 Stretched", "RightHand.Ring.2 Stretched"},
|
|
{"Right Ring 3 Stretched", "RightHand.Ring.3 Stretched"},
|
|
{"Right Little 1 Stretched", "RightHand.Little.1 Stretched"},
|
|
{"Right Little Spread", "RightHand.Little.Spread"},
|
|
{"Right Little 2 Stretched", "RightHand.Little.2 Stretched"},
|
|
{"Right Little 3 Stretched", "RightHand.Little.3 Stretched"},
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// モーションデータの中身
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class HumanoidPoses : ScriptableObject
|
|
{
|
|
[SerializeField]
|
|
public string AvatarName = ""; // 아바타 이름 저장
|
|
|
|
// 세션 ID를 가져오는 메서드 (MotionDataRecorder와 동일한 세션 ID 사용)
|
|
private string GetSessionID()
|
|
{
|
|
// MotionDataRecorder에서 세션 ID를 가져오려고 시도
|
|
var motionRecorder = FindObjectOfType<MotionDataRecorder>();
|
|
if (motionRecorder != null && !string.IsNullOrEmpty(motionRecorder.SessionID))
|
|
{
|
|
return motionRecorder.SessionID;
|
|
}
|
|
|
|
// MotionDataRecorder가 없거나 세션 ID가 없으면 현재 시간으로 생성
|
|
return DateTime.Now.ToString("yyMMdd_HHmmss");
|
|
}
|
|
#if UNITY_EDITOR
|
|
//Genericなanimファイルとして出力する
|
|
[ContextMenu("Export as Generic animation clips")]
|
|
public void ExportGenericAnim()
|
|
{
|
|
var clip = new AnimationClip { frameRate = 30 };
|
|
AnimationUtility.SetAnimationClipSettings(clip, new AnimationClipSettings { loopTime = false });
|
|
|
|
// 본 데이터가 있는지 확인
|
|
if (Poses.Count == 0 || Poses[0].HumanoidBones.Count == 0)
|
|
{
|
|
Debug.LogError("ExportGenericAnim: 본 데이터가 없습니다. Poses.Count=" + Poses.Count +
|
|
(Poses.Count > 0 ? ", HumanoidBones.Count=" + Poses[0].HumanoidBones.Count : ""));
|
|
return;
|
|
}
|
|
|
|
var bones = Poses[0].HumanoidBones;
|
|
|
|
for (int i = 0; i < bones.Count; i++)
|
|
{
|
|
var bone = bones[i];
|
|
|
|
// 경로가 비어있는지 확인
|
|
if (string.IsNullOrEmpty(bone.Name))
|
|
{
|
|
Debug.LogError($"본 {i}: 이름이 비어있습니다!");
|
|
continue;
|
|
}
|
|
|
|
// 경로 정리: 끝의 슬래시만 제거
|
|
string cleanPath = bone.Name.TrimEnd('/');
|
|
|
|
var positionCurveX = new AnimationCurve();
|
|
var positionCurveY = new AnimationCurve();
|
|
var positionCurveZ = new AnimationCurve();
|
|
var rotationCurveX = new AnimationCurve();
|
|
var rotationCurveY = new AnimationCurve();
|
|
var rotationCurveZ = new AnimationCurve();
|
|
var rotationCurveW = new AnimationCurve();
|
|
|
|
foreach (var p in Poses)
|
|
{
|
|
if (p.HumanoidBones.Count > i)
|
|
{
|
|
var poseBone = p.HumanoidBones[i];
|
|
positionCurveX.AddKey(p.Time, poseBone.LocalPosition.x);
|
|
positionCurveY.AddKey(p.Time, poseBone.LocalPosition.y);
|
|
positionCurveZ.AddKey(p.Time, poseBone.LocalPosition.z);
|
|
rotationCurveX.AddKey(p.Time, poseBone.LocalRotation.x);
|
|
rotationCurveY.AddKey(p.Time, poseBone.LocalRotation.y);
|
|
rotationCurveZ.AddKey(p.Time, poseBone.LocalRotation.z);
|
|
rotationCurveW.AddKey(p.Time, poseBone.LocalRotation.w);
|
|
}
|
|
}
|
|
|
|
//path는 계층
|
|
//http://mebiustos.hatenablog.com/entry/2015/09/16/230000
|
|
var binding = new EditorCurveBinding
|
|
{
|
|
path = cleanPath,
|
|
type = typeof(Transform),
|
|
propertyName = "m_LocalPosition.x"
|
|
};
|
|
|
|
AnimationUtility.SetEditorCurve(clip, binding, positionCurveX);
|
|
AnimationUtility.SetEditorCurve(clip,
|
|
new EditorCurveBinding
|
|
{
|
|
path = cleanPath,
|
|
type = typeof(Transform),
|
|
propertyName = "m_LocalPosition.y"
|
|
}, positionCurveY);
|
|
AnimationUtility.SetEditorCurve(clip,
|
|
new EditorCurveBinding
|
|
{
|
|
path = cleanPath,
|
|
type = typeof(Transform),
|
|
propertyName = "m_LocalPosition.z"
|
|
}, positionCurveZ);
|
|
|
|
AnimationUtility.SetEditorCurve(clip,
|
|
new EditorCurveBinding
|
|
{
|
|
path = cleanPath,
|
|
type = typeof(Transform),
|
|
propertyName = "m_LocalRotation.x"
|
|
}, rotationCurveX);
|
|
AnimationUtility.SetEditorCurve(clip,
|
|
new EditorCurveBinding
|
|
{
|
|
path = cleanPath,
|
|
type = typeof(Transform),
|
|
propertyName = "m_LocalRotation.y"
|
|
}, rotationCurveY);
|
|
AnimationUtility.SetEditorCurve(clip,
|
|
new EditorCurveBinding
|
|
{
|
|
path = cleanPath,
|
|
type = typeof(Transform),
|
|
propertyName = "m_LocalRotation.z"
|
|
}, rotationCurveZ);
|
|
AnimationUtility.SetEditorCurve(clip,
|
|
new EditorCurveBinding
|
|
{
|
|
path = cleanPath,
|
|
type = typeof(Transform),
|
|
propertyName = "m_LocalRotation.w"
|
|
}, rotationCurveW);
|
|
}
|
|
|
|
clip.EnsureQuaternionContinuity();
|
|
|
|
// 세션 ID 사용 (MotionDataRecorder와 동일한 세션 ID 사용)
|
|
string sessionID = GetSessionID();
|
|
|
|
// 아바타 이름이 있으면 포함, 없으면 기본값 사용
|
|
string avatarName = !string.IsNullOrEmpty(AvatarName) ? AvatarName : "Unknown";
|
|
|
|
// 에셋 파일의 경로를 기반으로 저장 경로 결정
|
|
string savePath = "Assets/Resources"; // 기본값
|
|
string fileName = $"{sessionID}_{avatarName}_Generic.anim";
|
|
|
|
// 현재 에셋 파일의 경로 가져오기
|
|
string assetPath = AssetDatabase.GetAssetPath(this);
|
|
if (!string.IsNullOrEmpty(assetPath))
|
|
{
|
|
string directory = Path.GetDirectoryName(assetPath);
|
|
if (!string.IsNullOrEmpty(directory))
|
|
{
|
|
savePath = directory;
|
|
}
|
|
}
|
|
|
|
MotionDataRecorder.SafeCreateDirectory(savePath);
|
|
|
|
var path = Path.Combine(savePath, fileName);
|
|
var uniqueAssetPath = AssetDatabase.GenerateUniqueAssetPath(path);
|
|
|
|
AssetDatabase.CreateAsset(clip, uniqueAssetPath);
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
|
|
Debug.Log($"제네릭 애니메이션 파일이 저장되었습니다: {uniqueAssetPath}");
|
|
}
|
|
|
|
//Humanoidなanimファイルとして出力する。
|
|
[ContextMenu("Export as Humanoid animation clips")]
|
|
public void ExportHumanoidAnim()
|
|
{
|
|
// 데이터 검증
|
|
if (Poses == null || Poses.Count == 0)
|
|
{
|
|
Debug.LogError("ExportHumanoidAnim: Poses 데이터가 없습니다. Poses.Count=" + (Poses?.Count ?? 0));
|
|
return;
|
|
}
|
|
|
|
Debug.Log($"ExportHumanoidAnim: Poses.Count={Poses.Count}, 첫 번째 포즈 시간={Poses[0].Time}, 마지막 포즈 시간={Poses[Poses.Count-1].Time}");
|
|
|
|
// 첫 번째 포즈 데이터 검증
|
|
var firstPose = Poses[0];
|
|
Debug.Log($"첫 번째 포즈: BodyPosition={firstPose.BodyPosition}, Muscles.Length={firstPose.Muscles?.Length ?? 0}");
|
|
|
|
if (firstPose.Muscles == null || firstPose.Muscles.Length == 0)
|
|
{
|
|
Debug.LogError("ExportHumanoidAnim: Muscles 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
var clip = new AnimationClip { frameRate = 30 };
|
|
|
|
// 시작할 때 설정을 적용 (커브 데이터 추가 전)
|
|
var settings = new AnimationClipSettings
|
|
{
|
|
loopTime = false, // Loop Time: false
|
|
cycleOffset = 0, // Cycle Offset: 0
|
|
loopBlend = false, // Loop Blend: false
|
|
loopBlendOrientation = true, // Root Transform Rotation - Bake Into Pose: true
|
|
loopBlendPositionY = true, // Root Transform Position (Y) - Bake Into Pose: true
|
|
loopBlendPositionXZ = true, // Root Transform Position (XZ) - Bake Into Pose: true
|
|
keepOriginalOrientation = true, // Root Transform Rotation - Based Upon: Original
|
|
keepOriginalPositionY = true, // Root Transform Position (Y) - Based Upon: Original
|
|
keepOriginalPositionXZ = true, // Root Transform Position (XZ) - Based Upon: Original
|
|
heightFromFeet = false, // Height From Feet: false
|
|
mirror = false // Mirror: false
|
|
};
|
|
|
|
AnimationUtility.SetAnimationClipSettings(clip, settings);
|
|
|
|
// body position
|
|
{
|
|
var curveX = new AnimationCurve();
|
|
var curveY = new AnimationCurve();
|
|
var curveZ = new AnimationCurve();
|
|
foreach (var item in Poses)
|
|
{
|
|
curveX.AddKey(item.Time, item.BodyPosition.x);
|
|
curveY.AddKey(item.Time, item.BodyPosition.y);
|
|
curveZ.AddKey(item.Time, item.BodyPosition.z);
|
|
}
|
|
|
|
Debug.Log($"Body Position 커브: 키 개수 X={curveX.length}, Y={curveY.length}, Z={curveZ.length}");
|
|
|
|
const string muscleX = "RootT.x";
|
|
clip.SetCurve("", typeof(Animator), muscleX, curveX);
|
|
const string muscleY = "RootT.y";
|
|
clip.SetCurve("", typeof(Animator), muscleY, curveY);
|
|
const string muscleZ = "RootT.z";
|
|
clip.SetCurve("", typeof(Animator), muscleZ, curveZ);
|
|
}
|
|
// Leftfoot position
|
|
{
|
|
var curveX = new AnimationCurve();
|
|
var curveY = new AnimationCurve();
|
|
var curveZ = new AnimationCurve();
|
|
foreach (var item in Poses)
|
|
{
|
|
curveX.AddKey(item.Time, item.LeftfootIK_Pos.x);
|
|
curveY.AddKey(item.Time, item.LeftfootIK_Pos.y);
|
|
curveZ.AddKey(item.Time, item.LeftfootIK_Pos.z);
|
|
}
|
|
|
|
const string muscleX = "LeftFootT.x";
|
|
clip.SetCurve("", typeof(Animator), muscleX, curveX);
|
|
const string muscleY = "LeftFootT.y";
|
|
clip.SetCurve("", typeof(Animator), muscleY, curveY);
|
|
const string muscleZ = "LeftFootT.z";
|
|
clip.SetCurve("", typeof(Animator), muscleZ, curveZ);
|
|
}
|
|
// Rightfoot position
|
|
{
|
|
var curveX = new AnimationCurve();
|
|
var curveY = new AnimationCurve();
|
|
var curveZ = new AnimationCurve();
|
|
foreach (var item in Poses)
|
|
{
|
|
curveX.AddKey(item.Time, item.RightfootIK_Pos.x);
|
|
curveY.AddKey(item.Time, item.RightfootIK_Pos.y);
|
|
curveZ.AddKey(item.Time, item.RightfootIK_Pos.z);
|
|
}
|
|
|
|
const string muscleX = "RightFootT.x";
|
|
clip.SetCurve("", typeof(Animator), muscleX, curveX);
|
|
const string muscleY = "RightFootT.y";
|
|
clip.SetCurve("", typeof(Animator), muscleY, curveY);
|
|
const string muscleZ = "RightFootT.z";
|
|
clip.SetCurve("", typeof(Animator), muscleZ, curveZ);
|
|
}
|
|
// body rotation
|
|
{
|
|
var curveX = new AnimationCurve();
|
|
var curveY = new AnimationCurve();
|
|
var curveZ = new AnimationCurve();
|
|
var curveW = new AnimationCurve();
|
|
foreach (var item in Poses)
|
|
{
|
|
curveX.AddKey(item.Time, item.BodyRotation.x);
|
|
curveY.AddKey(item.Time, item.BodyRotation.y);
|
|
curveZ.AddKey(item.Time, item.BodyRotation.z);
|
|
curveW.AddKey(item.Time, item.BodyRotation.w);
|
|
}
|
|
|
|
const string muscleX = "RootQ.x";
|
|
clip.SetCurve("", typeof(Animator), muscleX, curveX);
|
|
const string muscleY = "RootQ.y";
|
|
clip.SetCurve("", typeof(Animator), muscleY, curveY);
|
|
const string muscleZ = "RootQ.z";
|
|
clip.SetCurve("", typeof(Animator), muscleZ, curveZ);
|
|
const string muscleW = "RootQ.w";
|
|
clip.SetCurve("", typeof(Animator), muscleW, curveW);
|
|
}
|
|
// Leftfoot rotation
|
|
{
|
|
var curveX = new AnimationCurve();
|
|
var curveY = new AnimationCurve();
|
|
var curveZ = new AnimationCurve();
|
|
var curveW = new AnimationCurve();
|
|
foreach (var item in Poses)
|
|
{
|
|
curveX.AddKey(item.Time, item.LeftfootIK_Rot.x);
|
|
curveY.AddKey(item.Time, item.LeftfootIK_Rot.y);
|
|
curveZ.AddKey(item.Time, item.LeftfootIK_Rot.z);
|
|
curveW.AddKey(item.Time, item.LeftfootIK_Rot.w);
|
|
}
|
|
|
|
const string muscleX = "LeftFootQ.x";
|
|
clip.SetCurve("", typeof(Animator), muscleX, curveX);
|
|
const string muscleY = "LeftFootQ.y";
|
|
clip.SetCurve("", typeof(Animator), muscleY, curveY);
|
|
const string muscleZ = "LeftFootQ.z";
|
|
clip.SetCurve("", typeof(Animator), muscleZ, curveZ);
|
|
const string muscleW = "LeftFootQ.w";
|
|
clip.SetCurve("", typeof(Animator), muscleW, curveW);
|
|
}
|
|
// Rightfoot rotation
|
|
{
|
|
var curveX = new AnimationCurve();
|
|
var curveY = new AnimationCurve();
|
|
var curveZ = new AnimationCurve();
|
|
var curveW = new AnimationCurve();
|
|
foreach (var item in Poses)
|
|
{
|
|
curveX.AddKey(item.Time, item.RightfootIK_Rot.x);
|
|
curveY.AddKey(item.Time, item.RightfootIK_Rot.y);
|
|
curveZ.AddKey(item.Time, item.RightfootIK_Rot.z);
|
|
curveW.AddKey(item.Time, item.RightfootIK_Rot.w);
|
|
}
|
|
|
|
const string muscleX = "RightFootQ.x";
|
|
clip.SetCurve("", typeof(Animator), muscleX, curveX);
|
|
const string muscleY = "RightFootQ.y";
|
|
clip.SetCurve("", typeof(Animator), muscleY, curveY);
|
|
const string muscleZ = "RightFootQ.z";
|
|
clip.SetCurve("", typeof(Animator), muscleZ, curveZ);
|
|
const string muscleW = "RightFootQ.w";
|
|
clip.SetCurve("", typeof(Animator), muscleW, curveW);
|
|
}
|
|
|
|
// muscles
|
|
for (int i = 0; i < HumanTrait.MuscleCount; i++)
|
|
{
|
|
var curve = new AnimationCurve();
|
|
foreach (var item in Poses)
|
|
{
|
|
curve.AddKey(item.Time, item.Muscles[i]);
|
|
}
|
|
|
|
var muscle = HumanTrait.MuscleName[i];
|
|
if (MotionDataSettings.TraitPropMap.ContainsKey(muscle))
|
|
{
|
|
muscle = MotionDataSettings.TraitPropMap[muscle];
|
|
}
|
|
|
|
clip.SetCurve("", typeof(Animator), muscle, curve);
|
|
}
|
|
|
|
clip.EnsureQuaternionContinuity();
|
|
|
|
// 세션 ID 사용 (MotionDataRecorder와 동일한 세션 ID 사용)
|
|
string sessionID = GetSessionID();
|
|
|
|
// 아바타 이름이 있으면 포함, 없으면 기본값 사용
|
|
string avatarName = !string.IsNullOrEmpty(AvatarName) ? AvatarName : "Unknown";
|
|
|
|
// 에셋 파일의 경로를 기반으로 저장 경로 결정
|
|
string savePath = "Assets/Resources"; // 기본값
|
|
string fileName = $"{sessionID}_{avatarName}_Humanoid.anim";
|
|
|
|
// 현재 에셋 파일의 경로 가져오기
|
|
string assetPath = AssetDatabase.GetAssetPath(this);
|
|
if (!string.IsNullOrEmpty(assetPath))
|
|
{
|
|
string directory = Path.GetDirectoryName(assetPath);
|
|
if (!string.IsNullOrEmpty(directory))
|
|
{
|
|
savePath = directory;
|
|
}
|
|
}
|
|
|
|
MotionDataRecorder.SafeCreateDirectory(savePath);
|
|
|
|
var path = Path.Combine(savePath, fileName);
|
|
var uniqueAssetPath = AssetDatabase.GenerateUniqueAssetPath(path);
|
|
|
|
AssetDatabase.CreateAsset(clip, uniqueAssetPath);
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
|
|
Debug.Log($"휴머노이드 애니메이션 파일이 저장되었습니다: {uniqueAssetPath}");
|
|
}
|
|
|
|
// BVH export 기능 제거 - 새로운 시스템으로 대체 예정
|
|
|
|
// BVH 관련 메서드들 제거 - 새로운 시스템으로 대체 예정
|
|
|
|
// BVH 관련 헬퍼 메서드들 제거 - 새로운 시스템으로 대체 예정
|
|
#endif
|
|
|
|
[Serializable]
|
|
public class SerializeHumanoidPose
|
|
{
|
|
public Vector3 BodyRootPosition;
|
|
public Quaternion BodyRootRotation;
|
|
|
|
public Vector3 BodyPosition;
|
|
public Quaternion BodyRotation;
|
|
public Vector3 LeftfootIK_Pos;
|
|
public Quaternion LeftfootIK_Rot;
|
|
public Vector3 RightfootIK_Pos;
|
|
public Quaternion RightfootIK_Rot;
|
|
|
|
public float[] Muscles;
|
|
|
|
//フレーム数
|
|
public int FrameCount;
|
|
|
|
//記録開始後の経過時間。処理落ち対策
|
|
public float Time;
|
|
|
|
[Serializable]
|
|
public class HumanoidBone
|
|
{
|
|
public string Name;
|
|
public Vector3 LocalPosition;
|
|
public Quaternion LocalRotation;
|
|
|
|
private static Dictionary<Transform, string> _pathCache = new Dictionary<Transform, string>();
|
|
|
|
private static string BuildRelativePath(Transform root, Transform target)
|
|
{
|
|
var path = "";
|
|
_pathCache.TryGetValue(target, out path);
|
|
if(path != null) return path;
|
|
|
|
var current = target;
|
|
var pathList = new List<string>();
|
|
|
|
// 타겟이 루트와 같은 경우 빈 문자열 반환
|
|
if (current == root)
|
|
{
|
|
path = "";
|
|
_pathCache.Add(target, path);
|
|
return path;
|
|
}
|
|
|
|
// 루트까지 올라가면서 경로 구성
|
|
while (current != null && current != root)
|
|
{
|
|
pathList.Add(current.name);
|
|
current = current.parent;
|
|
}
|
|
|
|
if (current == null)
|
|
{
|
|
Debug.LogError($"{target.name}는 {root.name}의 자식이 아닙니다.");
|
|
throw new Exception(target.name + "는" + root.name + "의 자식이 아닙니다");
|
|
}
|
|
|
|
// 경로를 역순으로 조합 (Unity 애니메이션 경로 형식)
|
|
pathList.Reverse();
|
|
path = string.Join("/", pathList);
|
|
|
|
// 경로 끝의 슬래시 제거
|
|
path = path.TrimEnd('/');
|
|
|
|
// Unity 애니메이션 시스템에서 루트 오브젝트 이름을 제거하는 경우를 대비
|
|
// 루트가 "Bip001"인 경우, 경로에서 "Bip001/" 부분을 제거
|
|
if (root.name == "Bip001" && path.StartsWith("Bip001/"))
|
|
{
|
|
path = path.Substring("Bip001/".Length);
|
|
}
|
|
|
|
_pathCache.Add(target, path);
|
|
return path;
|
|
}
|
|
|
|
public void Set(Transform root, Transform t)
|
|
{
|
|
Name = BuildRelativePath(root, t);
|
|
|
|
// 루트 본인지 확인 (이름이 비어있거나 루트와 같은 경우)
|
|
bool isRoot = string.IsNullOrEmpty(Name) || t == root;
|
|
|
|
if (isRoot)
|
|
{
|
|
// 루트 본: 월드 좌표계 사용
|
|
LocalPosition = t.position; // 월드 위치
|
|
LocalRotation = t.rotation; // 월드 회전
|
|
}
|
|
else
|
|
{
|
|
// 자식 본: 로컬 좌표계 사용
|
|
LocalPosition = t.localPosition;
|
|
LocalRotation = t.localRotation;
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField, HideInInspector]
|
|
public List<HumanoidBone> HumanoidBones = new List<HumanoidBone>();
|
|
|
|
//CSVシリアライズ
|
|
public string SerializeCSV()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
SerializeVector3(sb, BodyRootPosition);
|
|
SerializeQuaternion(sb, BodyRootRotation);
|
|
SerializeVector3(sb, BodyPosition);
|
|
SerializeQuaternion(sb, BodyRotation);
|
|
foreach (var muscle in Muscles)
|
|
{
|
|
sb.Append(muscle);
|
|
sb.Append(",");
|
|
}
|
|
sb.Append(FrameCount);
|
|
sb.Append(",");
|
|
sb.Append(Time);
|
|
sb.Append(",");
|
|
foreach (var humanoidBone in HumanoidBones)
|
|
{
|
|
sb.Append(humanoidBone.Name);
|
|
sb.Append(",");
|
|
SerializeVector3(sb, humanoidBone.LocalPosition);
|
|
SerializeQuaternion(sb, humanoidBone.LocalRotation);
|
|
}
|
|
sb.Length = sb.Length - 1; //最後のカンマ削除
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static void SerializeVector3(StringBuilder sb, Vector3 vec)
|
|
{
|
|
sb.Append(vec.x);
|
|
sb.Append(",");
|
|
sb.Append(vec.y);
|
|
sb.Append(",");
|
|
sb.Append(vec.z);
|
|
sb.Append(",");
|
|
}
|
|
|
|
private static void SerializeQuaternion(StringBuilder sb, Quaternion q)
|
|
{
|
|
sb.Append(q.x);
|
|
sb.Append(",");
|
|
sb.Append(q.y);
|
|
sb.Append(",");
|
|
sb.Append(q.z);
|
|
sb.Append(",");
|
|
sb.Append(q.w);
|
|
sb.Append(",");
|
|
}
|
|
|
|
//CSVデシリアライズ
|
|
public void DeserializeCSV(string str)
|
|
{
|
|
string[] dataString = str.Split(',');
|
|
BodyRootPosition = DeserializeVector3(dataString, 0);
|
|
BodyRootRotation = DeserializeQuaternion(dataString, 3);
|
|
BodyPosition = DeserializeVector3(dataString, 7);
|
|
BodyRotation = DeserializeQuaternion(dataString, 10);
|
|
Muscles = new float[HumanTrait.MuscleCount];
|
|
for (int i = 0; i < HumanTrait.MuscleCount; i++)
|
|
{
|
|
Muscles[i] = float.Parse(dataString[i + 14]);
|
|
}
|
|
FrameCount = int.Parse(dataString[14 + HumanTrait.MuscleCount]);
|
|
Time = float.Parse(dataString[15 + HumanTrait.MuscleCount]);
|
|
var boneValues = Enum.GetValues(typeof(HumanBodyBones)) as HumanBodyBones[];
|
|
for (int i = 0; i < boneValues.Length; i++)
|
|
{
|
|
int startIndex = 16 + HumanTrait.MuscleCount + (i * 8);
|
|
if (dataString.Length <= startIndex)
|
|
{
|
|
break;
|
|
}
|
|
|
|
HumanoidBone bone = new HumanoidBone();
|
|
bone.Name = dataString[startIndex];
|
|
bone.LocalPosition = DeserializeVector3(dataString, startIndex + 1);
|
|
bone.LocalRotation = DeserializeQuaternion(dataString, startIndex + 4);
|
|
}
|
|
}
|
|
|
|
private static Vector3 DeserializeVector3(IList<string> str, int startIndex)
|
|
{
|
|
return new Vector3(float.Parse(str[startIndex]), float.Parse(str[startIndex + 1]), float.Parse(str[startIndex + 2]));
|
|
}
|
|
|
|
private static Quaternion DeserializeQuaternion(IList<string> str, int startIndex)
|
|
{
|
|
return new Quaternion(float.Parse(str[startIndex]), float.Parse(str[startIndex + 1]), float.Parse(str[startIndex + 2]), float.Parse(str[startIndex + 3]));
|
|
}
|
|
|
|
}
|
|
|
|
[SerializeField, HideInInspector]
|
|
public List<SerializeHumanoidPose> Poses = new List<SerializeHumanoidPose>();
|
|
|
|
// 인스펙터 최적화를 위한 요약 정보
|
|
[System.Serializable]
|
|
public class SummaryInfo
|
|
{
|
|
public int TotalPoses;
|
|
public float TotalTime;
|
|
public int TotalBones;
|
|
public int TotalMuscles;
|
|
public float AverageFPS;
|
|
}
|
|
|
|
[SerializeField]
|
|
public SummaryInfo Summary = new SummaryInfo();
|
|
}
|
|
}
|