447 lines
19 KiB
C#

using Rokoko.Core;
using Rokoko.Helper;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Rokoko.Inputs
{
public class Actor : MonoBehaviour
{
[System.Serializable]
public enum BoneMappingEnum
{
Animator,
Custom
}
[System.Serializable]
public enum RotationSpace
{
Offset,
World,
Self
}
[HideInInspector] public string profileName = "DemoProfile";
[HideInInspector] public BoneMappingEnum boneMapping;
[HideInInspector] public Animator animator;
[HideInInspector] public HumanBoneMapping customBoneMapping;
[Header("Convert Space")]
[Tooltip("Convert Studio data to Unity position space")]
public Space positionSpace = Space.Self;
[Tooltip("Convert Studio data to Unity rotation space")]
public RotationSpace rotationSpace = RotationSpace.Offset;
[Space(10)]
[Tooltip("Calculate Model's height comparing to Actor's and position the Hips accordingly.\nGreat tool to align with the floor")]
public bool adjustHipHeightBasedOnStudioActor = false;
[Space(10)]
[Header("Thumb Offset")]
[Tooltip("Apply additional rotation offset to thumb proximal bones after all updates")]
public bool applyThumbOffset = true;
[Tooltip("Additional rotation offset for left thumb proximal bone (in degrees)")]
public Vector3 leftThumbProximalOffset = new Vector3(0, -30, -30);
[Tooltip("Additional rotation offset for right thumb proximal bone (in degrees)")]
public Vector3 rightThumbProximalOffset = new Vector3(0, 30, 30);
[HideInInspector] public Face face = null;
[Header("Log extra info")]
public bool debug = false;
[HideInInspector]
public HumanTPoseDictionary characterTPose = new HumanTPoseDictionary();
[HideInInspector]
public bool isValidTpose = false;
protected Dictionary<HumanBodyBones, Transform> animatorHumanBones = new Dictionary<HumanBodyBones, Transform>();
private Dictionary<HumanBodyBones, Quaternion> offsets = new Dictionary<HumanBodyBones, Quaternion>();
private float hipHeight = 0;
#region Initialize
protected virtual void Awake()
{
if(animator == null)
{
Debug.LogError($"Actor {this.name} isn't configured", this.transform);
return;
}
// OptitrackSkeletonAnimator_Mingle 컴포넌트에서 스켈레톤 이름 가져오기 시도
var optitrackComponent = this.GetComponent("OptitrackSkeletonAnimator_Mingle");
if (optitrackComponent != null)
{
// Reflection을 사용하여 SkeletonAssetName 속성 가져오기
var skeletonNameField = optitrackComponent.GetType().GetField("SkeletonAssetName");
if (skeletonNameField != null && skeletonNameField.GetValue(optitrackComponent) is string skeletonName)
{
profileName = skeletonName;
}
}
if (!animator.isHuman)
{
Debug.LogError("Model is not marked as Humanoid. Please go in model inspector, under Rig tab and select AnimationType as Humanoid.", this.transform);
return;
}
InitializeAnimatorHumanBones();
InitializeBoneOffsets();
// Get the Hip height independent of parent transformations
hipHeight = GetBone(HumanBodyBones.Hips).parent.InverseTransformVector(GetBone(HumanBodyBones.Hips).localPosition).y;
hipHeight = Mathf.Abs(hipHeight);
if (characterTPose.Count == 0)
Debug.LogError($"Character {this.name} is not set to TPose. Please ensure you assign a valid TPose in Editor before playing", this.transform);
}
/// <summary>
/// Register Actor override in StudioManager.
/// </summary>
private void Start()
{
if (animator != null && !animator.isHuman) return;
if (!string.IsNullOrEmpty(profileName))
StudioManager.AddActorOverride(this);
}
[ContextMenu("CalcualteTPose")]
public void CalculateTPose()
{
InitializeAnimatorHumanBones();
InitializeCharacterTPose();
isValidTpose = IsValidTPose();
}
private void InitializeBonesIfNeeded()
{
if (boneMapping == BoneMappingEnum.Animator && animatorHumanBones.Count == 0)
InitializeAnimatorHumanBones();
}
/// <summary>
/// Store Character's T Pose.
/// </summary>
protected void InitializeCharacterTPose()
{
characterTPose.Clear();
foreach (HumanBodyBones bone in RokokoHelper.HumanBodyBonesArray)
{
if (bone == HumanBodyBones.LastBone) break;
Transform boneTransform = GetBone(bone);
if (boneTransform == null) continue;
characterTPose.Add(bone, boneTransform.rotation);
}
}
/// <summary>
/// Calculate Character's offset based on its T Pose and Newton's T Pose.
/// </summary>
protected void InitializeBoneOffsets()
{
// Calculate offsets based on Smartsuit T pose
offsets = CalculateRotationOffsets();
}
/// <summary>
/// Cache the bone transforms from Animator.
/// </summary>
protected void InitializeAnimatorHumanBones()
{
if (boneMapping != BoneMappingEnum.Animator) return;
if (animator == null || !animator.isHuman) return;
animatorHumanBones.Clear();
foreach (HumanBodyBones bone in RokokoHelper.HumanBodyBonesArray)
{
if (bone == HumanBodyBones.LastBone) break;
animatorHumanBones.Add(bone, animator.GetBoneTransform(bone));
}
}
#endregion
#region Public Methods
/// <summary>
/// Update Skeleton and Face data based on ActorFrame.
/// </summary>
public virtual void UpdateActor(ActorFrame actorFrame)
{
if (animator == null || !animator.isHuman) return;
profileName = actorFrame.name;
bool updateBody = actorFrame.meta.hasBody || actorFrame.meta.hasGloves;
// Update skeleton from data
if (updateBody)
UpdateSkeleton(actorFrame);
// Update face from data
if (actorFrame.meta.hasFace)
face?.UpdateFace(actorFrame.face);
}
/// <summary>
/// Create Idle/Default Actor.
/// </summary>
public virtual void CreateIdle(string actorName)
{
this.profileName = actorName;
}
public float GetActorHeight()
{
InitializeBonesIfNeeded();
Transform head = GetBone(HumanBodyBones.Head);
Transform foot = GetBone(HumanBodyBones.LeftFoot);
if (head == null || foot == null)
return 1.8f;
// Add space for head mesh
return Vector3.Distance(head.position, foot.position) + 0.25f;
}
#endregion
#region Internal Logic
private bool IsValidTPose()
{
InitializeBonesIfNeeded();
Transform rightHand = GetBone(HumanBodyBones.RightHand);
Transform leftHand = GetBone(HumanBodyBones.LeftHand);
Transform spine = GetBone(HumanBodyBones.Spine);
Transform chest = GetBone(HumanBodyBones.Chest);
if(rightHand == null || leftHand == null || spine == null || chest == null)
{
Debug.LogError("Cant validate actor height. Bone is missing", this.transform);
return false;
}
Vector3 armsDirection = rightHand.position - leftHand.position;
armsDirection.Normalize();
Vector3 spineDirection = chest.position - spine.position;
spineDirection.Normalize();
return Vector3.Dot(armsDirection, Vector3.right) > 0.99f &&
Vector3.Dot(spineDirection, Vector3.up) > 0.99f;
}
/// <summary>
/// Get Transform from a given HumanBodyBones.
/// </summary>
private Transform GetBone(HumanBodyBones bone)
{
switch (boneMapping)
{
case BoneMappingEnum.Animator:
return animatorHumanBones[bone];
case BoneMappingEnum.Custom:
return customBoneMapping.customBodyBones[(int)bone];
}
return null;
}
/// <summary>
/// Update Humanoid Skeleton based on BodyData.
/// </summary>
protected void UpdateSkeleton(ActorFrame actorFrame)
{
foreach (HumanBodyBones bone in RokokoHelper.HumanBodyBonesArray)
{
if (bone == HumanBodyBones.LastBone) break;
ActorJointFrame? boneFrame = actorFrame.body.GetBoneFrame(bone);
if (boneFrame != null)
{
bool shouldUpdatePosition = bone == HumanBodyBones.Hips;
Quaternion worldRotation = boneFrame.Value.rotation.ToQuaternion();
Vector3 worldPosition = boneFrame.Value.position.ToVector3();
// Offset Hip bone
if (shouldUpdatePosition && adjustHipHeightBasedOnStudioActor)
worldPosition = new Vector3(worldPosition.x, worldPosition.y - (actorFrame.dimensions.hipHeight - hipHeight), worldPosition.z);
UpdateBone(bone, worldPosition, worldRotation, shouldUpdatePosition, positionSpace, rotationSpace);
}
}
// Apply thumb offsets after all bone updates are complete
if (applyThumbOffset)
{
ApplyThumbOffsets();
}
}
/// <summary>
/// Apply additional rotation offsets to thumb proximal bones.
/// </summary>
protected void ApplyThumbOffsets()
{
// Apply left thumb offset
Transform leftThumbTransform = GetBone(HumanBodyBones.LeftThumbProximal);
if (leftThumbTransform != null)
{
Quaternion leftOffset = Quaternion.Euler(leftThumbProximalOffset);
leftThumbTransform.rotation *= leftOffset;
}
// Apply right thumb offset
Transform rightThumbTransform = GetBone(HumanBodyBones.RightThumbProximal);
if (rightThumbTransform != null)
{
Quaternion rightOffset = Quaternion.Euler(rightThumbProximalOffset);
rightThumbTransform.rotation *= rightOffset;
}
}
/// <summary>
/// Update Human bone.
/// </summary>
protected void UpdateBone(HumanBodyBones bone, Vector3 worldPosition, Quaternion worldRotation, bool updatePosition, Space positionSpace, RotationSpace rotationSpace)
{
// Find Humanoid bone
Transform boneTransform = GetBone(bone);
// Check if bone is valid
if (boneTransform == null)
{
if (debug)
Debug.LogWarning($"Couldn't find Transform for bone:{bone} in {boneMapping}Mapping component", this.transform);
return;
}
// Update position
if (updatePosition)
{
if (positionSpace == Space.World || boneTransform.parent == null)
{
boneTransform.position = worldPosition;
}
else
{
boneTransform.position = boneTransform.parent.rotation * worldPosition + boneTransform.parent.position;
}
}
// Update Rotation
if (rotationSpace == RotationSpace.World)
{
boneTransform.rotation = worldRotation;
}
else if (rotationSpace == RotationSpace.Self)
{
if (transform.parent != null)
boneTransform.rotation = this.transform.parent.rotation * worldRotation;
else
boneTransform.rotation = worldRotation;
}
else
{
boneTransform.rotation = GetBone(HumanBodyBones.Hips).parent.rotation * worldRotation * offsets[bone];
}
}
#endregion
/// <summary>
/// Get the rotational difference between 2 humanoid T poses.
/// </summary>
private Dictionary<HumanBodyBones, Quaternion> CalculateRotationOffsets()
{
Dictionary<HumanBodyBones, Quaternion> offsets = new Dictionary<HumanBodyBones, Quaternion>();
foreach (HumanBodyBones bone in RokokoHelper.HumanBodyBonesArray)
{
if (!characterTPose.Contains(bone)) continue;
Quaternion rotation = Quaternion.Inverse(SmartsuitTPose[bone]) * characterTPose[bone];
offsets.Add(bone, rotation);
}
return offsets;
}
/// <summary>
/// Get Smartsuit T pose data
/// </summary>
private static Dictionary<HumanBodyBones, Quaternion> SmartsuitTPose = new Dictionary<HumanBodyBones, Quaternion>() {
{HumanBodyBones.Hips, new Quaternion(0.000f, 0.000f, 0.000f, 1.000f)},
{HumanBodyBones.LeftUpperLeg, new Quaternion(0.000f, 0.707f, 0.000f, 0.707f)},
{HumanBodyBones.RightUpperLeg, new Quaternion(0.000f, -0.707f, 0.000f, 0.707f)},
{HumanBodyBones.LeftLowerLeg, new Quaternion(0.000f, 0.707f, 0.000f, 0.707f)},
{HumanBodyBones.RightLowerLeg, new Quaternion(0.000f, -0.707f, 0.000f, 0.707f)},
{HumanBodyBones.LeftFoot, new Quaternion(0.000f, 0.707f, -0.707f, 0.000f)},
{HumanBodyBones.RightFoot, new Quaternion(0.000f, -0.707f, 0.707f, 0.000f)},
{HumanBodyBones.Spine, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)},
{HumanBodyBones.Chest, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)},
{HumanBodyBones.Neck, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)},
{HumanBodyBones.Head, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)},
{HumanBodyBones.LeftShoulder, new Quaternion(0.000f, 0.000f, 0.707f, -0.707f)},
{HumanBodyBones.RightShoulder, new Quaternion(0.000f, 0.000f, 0.707f, 0.707f)},
{HumanBodyBones.LeftUpperArm, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.RightUpperArm, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.LeftLowerArm, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.RightLowerArm, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.LeftHand, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.RightHand, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.LeftToes, new Quaternion(0.000f, 0.707f, -0.707f, 0.000f)},
{HumanBodyBones.RightToes, new Quaternion(0.000f, -0.707f, 0.707f, 0.000f)},
{HumanBodyBones.LeftEye, new Quaternion(0.000f, 0.000f, 0.000f, 0.000f)},
{HumanBodyBones.RightEye, new Quaternion(0.000f, 0.000f, 0.000f, 0.000f)},
{HumanBodyBones.Jaw, new Quaternion(0.000f, 0.000f, 0.000f, 0.000f)},
{HumanBodyBones.LeftThumbProximal, new Quaternion(-0.561f, -0.701f, 0.430f, -0.092f)},
{HumanBodyBones.LeftThumbIntermediate, new Quaternion(-0.653f, -0.653f, 0.271f, -0.271f)},
{HumanBodyBones.LeftThumbDistal, new Quaternion(-0.653f, -0.653f, 0.271f, -0.271f)},
{HumanBodyBones.LeftIndexProximal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftIndexIntermediate, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftIndexDistal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftMiddleProximal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftMiddleIntermediate, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftMiddleDistal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftRingProximal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftRingIntermediate, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftRingDistal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftLittleProximal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftLittleIntermediate, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.LeftLittleDistal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)},
{HumanBodyBones.RightThumbProximal, new Quaternion(0.561f, -0.701f, 0.430f, 0.092f)},
{HumanBodyBones.RightThumbIntermediate, new Quaternion(0.653f, -0.653f, 0.271f, 0.271f)},
{HumanBodyBones.RightThumbDistal, new Quaternion(0.653f, -0.653f, 0.271f, 0.271f)},
{HumanBodyBones.RightIndexProximal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightIndexIntermediate, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightIndexDistal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightMiddleProximal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightMiddleIntermediate, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightMiddleDistal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightRingProximal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightRingIntermediate, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightRingDistal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightLittleProximal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightLittleIntermediate, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.RightLittleDistal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)},
{HumanBodyBones.UpperChest, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)}
};
}
}