418 lines
15 KiB
C#
418 lines
15 KiB
C#
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
|
||
|
|
||
|
namespace BNG {
|
||
|
|
||
|
[ExecuteInEditMode]
|
||
|
public class HandPoser : MonoBehaviour {
|
||
|
public bool ShowGizmos = true;
|
||
|
|
||
|
[Tooltip("Path of the directory where handposes should be stored. Tip : Keep these in a 'Resources' directory so you can use Resources.Load().")]
|
||
|
public string ResourcePath = "Assets/BNG Framework/HandPoser/Poses/Resources/";
|
||
|
|
||
|
public string PoseName = "Default";
|
||
|
|
||
|
[Tooltip("The currently selected hand pose. Change this to automatically update the pose in Update")]
|
||
|
public HandPose CurrentPose;
|
||
|
|
||
|
[Header("Animation Properties")]
|
||
|
[Tooltip("The speed at which to lerp the bones when changing hand poses")]
|
||
|
public float AnimationSpeed = 15f;
|
||
|
|
||
|
[Tooltip("If true the local rotation of each bone will be updated while changing hand poses. This should generally be true if you are adjusting a hand pose.")]
|
||
|
public bool UpdateJointRotations = true;
|
||
|
|
||
|
[Tooltip("If true the local position of each bone will be updated while changing hand poses. Typically this will be false as joints only adjust their rotations.")]
|
||
|
public bool UpdateJointPositions = false;
|
||
|
|
||
|
[Tooltip("If true the local position of the wrist will be updated. Useful if you need to move the entire hand.")]
|
||
|
public bool UpdateWristPosition = true;
|
||
|
|
||
|
// Hand Pose Transform Definitions
|
||
|
public HandPoseDefinition HandPoseJoints {
|
||
|
get {
|
||
|
return GetHandPoseDefinition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Transform WristJoint;
|
||
|
public List<Transform> ThumbJoints;
|
||
|
public List<Transform> IndexJoints;
|
||
|
public List<Transform> MiddleJoints;
|
||
|
public List<Transform> RingJoints;
|
||
|
public List<Transform> PinkyJoints;
|
||
|
public List<Transform> OtherJoints;
|
||
|
|
||
|
HandPose previousPose;
|
||
|
bool doSingleAnimation;
|
||
|
|
||
|
// Continuously update pose state
|
||
|
public bool ContinuousUpdate = false;
|
||
|
|
||
|
void Start() {
|
||
|
// Trigger a pose change to start the animation
|
||
|
OnPoseChanged();
|
||
|
}
|
||
|
|
||
|
// This is also run in the editor
|
||
|
void Update() {
|
||
|
|
||
|
// Check for pose change event
|
||
|
CheckForPoseChange();
|
||
|
|
||
|
// Lerp to Hand Pose
|
||
|
if (ContinuousUpdate || doSingleAnimation) {
|
||
|
DoPoseUpdate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void CheckForPoseChange() {
|
||
|
if (previousPose == null || (CurrentPose != null && previousPose != null && previousPose.name != CurrentPose.name && CurrentPose != null)) {
|
||
|
OnPoseChanged();
|
||
|
previousPose = CurrentPose;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void OnPoseChanged() {
|
||
|
|
||
|
// Allow pose to change animation
|
||
|
editorAnimationTime = 0;
|
||
|
doSingleAnimation = true;
|
||
|
}
|
||
|
|
||
|
public FingerJoint GetWristJoint() {
|
||
|
return GetJointFromTransform(WristJoint);
|
||
|
}
|
||
|
|
||
|
public List<FingerJoint> GetThumbJoints() {
|
||
|
return GetJointsFromTransforms(ThumbJoints);
|
||
|
}
|
||
|
|
||
|
public List<FingerJoint> GetIndexJoints() {
|
||
|
return GetJointsFromTransforms(IndexJoints);
|
||
|
}
|
||
|
|
||
|
public List<FingerJoint> GetMiddleJoints() {
|
||
|
return GetJointsFromTransforms(MiddleJoints);
|
||
|
}
|
||
|
|
||
|
public List<FingerJoint> GetRingJoints() {
|
||
|
return GetJointsFromTransforms(RingJoints);
|
||
|
}
|
||
|
|
||
|
public List<FingerJoint> GetPinkyJoints() {
|
||
|
return GetJointsFromTransforms(PinkyJoints);
|
||
|
}
|
||
|
|
||
|
public List<FingerJoint> GetOtherJoints() {
|
||
|
return GetJointsFromTransforms(OtherJoints);
|
||
|
}
|
||
|
|
||
|
public int GetTotalJointsCount() {
|
||
|
|
||
|
if(ThumbJoints == null || IndexJoints == null) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return ThumbJoints.Count + IndexJoints.Count + MiddleJoints.Count + RingJoints.Count + PinkyJoints.Count + OtherJoints.Count + (WristJoint != null ? 1 : 0);
|
||
|
}
|
||
|
|
||
|
public Transform GetTip(List<Transform> transforms) {
|
||
|
if(transforms != null) {
|
||
|
return transforms[transforms.Count - 1];
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public Transform GetThumbTip() { return GetTip(ThumbJoints); }
|
||
|
public Transform GetIndexTip() { return GetTip(IndexJoints); }
|
||
|
public Transform GetMiddleTip() { return GetTip(MiddleJoints); }
|
||
|
public Transform GetRingTip() { return GetTip(RingJoints); }
|
||
|
public Transform GetPinkyTip() { return GetTip(PinkyJoints); }
|
||
|
|
||
|
public static Vector3 GetFingerTipPositionWithOffset(List<Transform> jointTransforms, float tipRadius) {
|
||
|
|
||
|
if(jointTransforms == null || jointTransforms.Count == 0) {
|
||
|
return Vector3.zero;
|
||
|
}
|
||
|
|
||
|
// Not available
|
||
|
if(jointTransforms[jointTransforms.Count - 1] == null) {
|
||
|
return Vector3.zero;
|
||
|
}
|
||
|
|
||
|
Vector3 tipPosition = jointTransforms[jointTransforms.Count - 1].position;
|
||
|
|
||
|
if(jointTransforms.Count == 1) {
|
||
|
return tipPosition;
|
||
|
}
|
||
|
|
||
|
return tipPosition + (jointTransforms[jointTransforms.Count - 2].position - tipPosition).normalized * tipRadius;
|
||
|
}
|
||
|
|
||
|
public virtual List<FingerJoint> GetJointsFromTransforms(List<Transform> jointTransforms) {
|
||
|
List<FingerJoint> joints = new List<FingerJoint>();
|
||
|
|
||
|
// Check for empty joints
|
||
|
if(jointTransforms == null) {
|
||
|
return joints;
|
||
|
}
|
||
|
|
||
|
// Add any joint information to the list
|
||
|
for (int x = 0; x < jointTransforms.Count; x++) {
|
||
|
if(jointTransforms[x] != null) {
|
||
|
joints.Add(GetJointFromTransform(jointTransforms[x]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return joints;
|
||
|
}
|
||
|
|
||
|
public virtual FingerJoint GetJointFromTransform(Transform jointTransform) {
|
||
|
|
||
|
// Null check
|
||
|
if(jointTransform == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return new FingerJoint() {
|
||
|
TransformName = jointTransform.name,
|
||
|
LocalPosition = jointTransform.localPosition,
|
||
|
LocalRotation = jointTransform.localRotation
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public virtual void UpdateHandPose(HandPose handPose, bool lerp) {
|
||
|
UpdateHandPose(handPose.Joints, lerp);
|
||
|
}
|
||
|
|
||
|
public virtual void UpdateHandPose(HandPoseDefinition pose, bool lerp) {
|
||
|
UpdateJoint(pose.WristJoint, WristJoint, lerp, UpdateWristPosition, UpdateJointRotations);
|
||
|
UpdateJoints(pose.ThumbJoints, ThumbJoints, lerp);
|
||
|
UpdateJoints(pose.IndexJoints, IndexJoints, lerp);
|
||
|
UpdateJoints(pose.MiddleJoints, MiddleJoints, lerp);
|
||
|
UpdateJoints(pose.RingJoints, RingJoints, lerp);
|
||
|
UpdateJoints(pose.PinkyJoints, PinkyJoints, lerp);
|
||
|
UpdateJoints(pose.OtherJoints, OtherJoints, lerp);
|
||
|
}
|
||
|
|
||
|
public virtual void UpdateJoint(FingerJoint fromJoint, Transform toTransform, bool doLerp, bool updatePosition, bool updateRotation) {
|
||
|
UpdateJoint(fromJoint, toTransform, doLerp ? Time.deltaTime * AnimationSpeed : 1, updatePosition, updateRotation);
|
||
|
}
|
||
|
|
||
|
public virtual void UpdateJoint(FingerJoint fromJoint, Transform toTransform, float lerpAmount, bool updatePosition, bool updateRotation) {
|
||
|
|
||
|
// Invalid joint
|
||
|
if (fromJoint == null || toTransform == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Lerp
|
||
|
if (lerpAmount != 1) {
|
||
|
if(updatePosition) {
|
||
|
toTransform.localPosition = Vector3.Lerp(toTransform.localPosition, fromJoint.LocalPosition, lerpAmount);
|
||
|
}
|
||
|
if(UpdateJointRotations) {
|
||
|
toTransform.localRotation = Quaternion.Lerp(toTransform.localRotation, fromJoint.LocalRotation, lerpAmount);
|
||
|
}
|
||
|
}
|
||
|
// Instantaneous can directly set the local position and rotation
|
||
|
else {
|
||
|
if (UpdateJointPositions) {
|
||
|
toTransform.localPosition = fromJoint.LocalPosition;
|
||
|
}
|
||
|
if (UpdateJointRotations) {
|
||
|
toTransform.localRotation = fromJoint.LocalRotation;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void UpdateJoints(List<FingerJoint> joints, List<Transform> toTransforms, bool doLerp) {
|
||
|
UpdateJoints(joints, toTransforms, doLerp ? Time.deltaTime * AnimationSpeed : 1);
|
||
|
}
|
||
|
|
||
|
public virtual void UpdateJoints(List<FingerJoint> joints, List<Transform> toTransforms, float lerpAmount) {
|
||
|
|
||
|
// Sanity check
|
||
|
if (joints == null || toTransforms == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Cache the count of our lists
|
||
|
int jointCount = joints.Count;
|
||
|
int transformsCount = toTransforms.Count;
|
||
|
|
||
|
// If our joint counts don't add up, then make sure the names match before applying any changes
|
||
|
// Otherwise there is a good chance the wwrong transforms are being updated
|
||
|
bool verifyTransformName = jointCount != transformsCount;
|
||
|
for (int x = 0; x < jointCount; x++) {
|
||
|
// Make sure the indexes match
|
||
|
if (x < toTransforms.Count) {
|
||
|
|
||
|
// Joint may have not been assigned or destroyed
|
||
|
if (joints[x] == null || toTransforms[x] == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (verifyTransformName && joints[x].TransformName == toTransforms[x].name) {
|
||
|
UpdateJoint(joints[x], toTransforms[x], lerpAmount, UpdateJointPositions, UpdateJointRotations);
|
||
|
}
|
||
|
else if (verifyTransformName == false) {
|
||
|
UpdateJoint(joints[x], toTransforms[x], lerpAmount, UpdateJointPositions, UpdateJointRotations);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual HandPoseDefinition GetHandPoseDefinition() {
|
||
|
return new HandPoseDefinition() {
|
||
|
WristJoint = GetWristJoint(),
|
||
|
ThumbJoints = GetThumbJoints(),
|
||
|
IndexJoints = GetIndexJoints(),
|
||
|
MiddleJoints = GetMiddleJoints(),
|
||
|
RingJoints = GetRingJoints(),
|
||
|
PinkyJoints = GetPinkyJoints(),
|
||
|
OtherJoints = GetOtherJoints()
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Saves the current hand configuration as a Unity Scriptable object
|
||
|
/// Pose will be saved to the provided 'HandPosePath' (Typically within in a Resources folder)
|
||
|
/// *Scriptable Objects can only be saved from within the UnityEditor.
|
||
|
/// </summary>
|
||
|
/// <param name="poseName"></param>
|
||
|
/// <returns></returns>
|
||
|
public virtual void SavePoseAsScriptablObject(string poseName) {
|
||
|
#if UNITY_EDITOR
|
||
|
|
||
|
string fileName = poseName + ".asset";
|
||
|
|
||
|
var poseObject = GetHandPoseScriptableObject();
|
||
|
|
||
|
// Creates the file in the folder path
|
||
|
//string fullPath = Application.dataPath + directory + fileName;
|
||
|
string fullPath = ResourcePath + fileName;
|
||
|
bool exists = System.IO.File.Exists(fullPath);
|
||
|
|
||
|
// Delete if asset already exists
|
||
|
if (exists) {
|
||
|
UnityEditor.AssetDatabase.DeleteAsset(fullPath);
|
||
|
}
|
||
|
|
||
|
UnityEditor.AssetDatabase.CreateAsset(poseObject, fullPath);
|
||
|
|
||
|
UnityEditor.AssetDatabase.SaveAssets();
|
||
|
|
||
|
UnityEditor.AssetDatabase.Refresh(UnityEditor.ImportAssetOptions.ForceUpdate);
|
||
|
|
||
|
UnityEditor.EditorUtility.SetDirty(poseObject);
|
||
|
|
||
|
if (exists) {
|
||
|
Debug.Log("Updated Hand Pose : " + poseName);
|
||
|
}
|
||
|
else {
|
||
|
Debug.Log("Created new Hand Pose : " + poseName);
|
||
|
}
|
||
|
#else
|
||
|
Debug.Log("Scriptable Objects can only be saved from within the Unity Editor. Consider storing in another format like JSON instead.");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Will create an original handpose with the specified PoseName. If the provided poseName is already taken, a number will be added to the name until a unique name is found.
|
||
|
/// For example, if you provide the PoseName of "Default" and there are already two poses named "Default" and "Default 1", then a new pose named "Default 2" will be created.
|
||
|
/// </summary>
|
||
|
/// <param name="poseName"></param>
|
||
|
public virtual void CreateUniquePose(string poseName) {
|
||
|
|
||
|
// Don't allow empty pose names
|
||
|
if(string.IsNullOrEmpty(poseName)) {
|
||
|
poseName = "Pose";
|
||
|
}
|
||
|
|
||
|
string formattedPoseName = poseName;
|
||
|
string fullPath = ResourcePath + formattedPoseName + ".asset";
|
||
|
bool exists = System.IO.File.Exists(fullPath);
|
||
|
int checkCount = 0;
|
||
|
// Find a path that doesn't exist
|
||
|
while(exists) {
|
||
|
// Ex : "path/Pose 5.asset"
|
||
|
formattedPoseName = poseName + " " + checkCount;
|
||
|
exists = System.IO.File.Exists(ResourcePath + formattedPoseName + ".asset");
|
||
|
|
||
|
checkCount++;
|
||
|
}
|
||
|
|
||
|
// Save the new pose
|
||
|
SavePoseAsScriptablObject(formattedPoseName);
|
||
|
}
|
||
|
|
||
|
public virtual HandPose GetHandPoseScriptableObject() {
|
||
|
#if UNITY_EDITOR
|
||
|
var poseObject = UnityEditor.Editor.CreateInstance<HandPose>();
|
||
|
poseObject.PoseName = PoseName;
|
||
|
|
||
|
poseObject.Joints = new HandPoseDefinition();
|
||
|
poseObject.Joints.WristJoint = GetWristJoint();
|
||
|
poseObject.Joints.ThumbJoints = GetThumbJoints();
|
||
|
poseObject.Joints.IndexJoints = GetIndexJoints();
|
||
|
poseObject.Joints.MiddleJoints = GetMiddleJoints();
|
||
|
poseObject.Joints.RingJoints = GetRingJoints();
|
||
|
poseObject.Joints.PinkyJoints = GetPinkyJoints();
|
||
|
poseObject.Joints.OtherJoints = GetOtherJoints();
|
||
|
|
||
|
return poseObject;
|
||
|
#else
|
||
|
return null;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
// How long to check for animations while in the editor mode
|
||
|
float editorAnimationTime = 0f;
|
||
|
float maxEditorAnimationTime = 2f;
|
||
|
|
||
|
public virtual void DoPoseUpdate() {
|
||
|
|
||
|
if (CurrentPose != null) {
|
||
|
UpdateHandPose(CurrentPose.Joints, true);
|
||
|
}
|
||
|
|
||
|
// Are we done requesting a single animation?
|
||
|
if (doSingleAnimation) {
|
||
|
editorAnimationTime += Time.deltaTime;
|
||
|
|
||
|
// Reset
|
||
|
if (editorAnimationTime >= maxEditorAnimationTime) {
|
||
|
editorAnimationTime = 0;
|
||
|
doSingleAnimation = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void ResetEditorHandles() {
|
||
|
EditorHandle[] handles = GetComponentsInChildren<EditorHandle>();
|
||
|
for (int x = 0; x < handles.Length; x++) {
|
||
|
if(handles[x] != null && handles[x].gameObject != null) {
|
||
|
GameObject.DestroyImmediate((handles[x]));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnDrawGizmos() {
|
||
|
#if UNITY_EDITOR
|
||
|
// Update every frame even while in editor
|
||
|
if (!Application.isPlaying) {
|
||
|
UnityEditor.EditorApplication.QueuePlayerLoopUpdate();
|
||
|
UnityEditor.SceneView.RepaintAll();
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|