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 ThumbJoints; public List IndexJoints; public List MiddleJoints; public List RingJoints; public List PinkyJoints; public List 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 GetThumbJoints() { return GetJointsFromTransforms(ThumbJoints); } public List GetIndexJoints() { return GetJointsFromTransforms(IndexJoints); } public List GetMiddleJoints() { return GetJointsFromTransforms(MiddleJoints); } public List GetRingJoints() { return GetJointsFromTransforms(RingJoints); } public List GetPinkyJoints() { return GetJointsFromTransforms(PinkyJoints); } public List 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 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 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 GetJointsFromTransforms(List jointTransforms) { List joints = new List(); // 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 joints, List toTransforms, bool doLerp) { UpdateJoints(joints, toTransforms, doLerp ? Time.deltaTime * AnimationSpeed : 1); } public virtual void UpdateJoints(List joints, List 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() }; } /// /// 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. /// /// /// 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 } /// /// 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. /// /// 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(); 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(); 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 } } }