using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace UDE_HAND_INTERACTION { [CustomEditor(typeof(HandInteraction))] public class HandInteractionEditor : Editor { private HandInteraction _handInteraction; private VirtualHand virtualHand; public HandPerfabResource _HPResource; private Handedness _lastHandedness; private const float GIZMO_SCALE = 0.007f; private void Awake() { _handInteraction = target as HandInteraction; } private void OnEnable() { AssignMissingGhostProvider(); } private void OnDisable() { DestroyGhost(); } public override void OnInspectorGUI() { base.OnInspectorGUI(); _handInteraction._relativeTo = _handInteraction.transform.parent; DrawGhostMenu(_handInteraction.HandPose); serializedObject.ApplyModifiedProperties(); } public void OnSceneGUI() { if (SceneView.currentDrawingSceneView == null) { return; } if (virtualHand == null) { return; } EditFingers(); } private void DrawGhostMenu(HandPose handPose) { HandPerfabResource resource = _handInteraction._HPResource; //EditorGUILayout.ObjectField("Ghost Provider", _HPResource, typeof(HandPerfabResource), false) as HandPerfabResource; if (virtualHand == null || _HPResource != resource || _lastHandedness != handPose.Handedness) { RegenerateGhost(); } _HPResource = resource; _lastHandedness = handPose.Handedness; } private void EditFingers() { DollHand puppet = virtualHand.GetComponent(); if (puppet != null && puppet.JointSets != null) { DrawBonesRotator(puppet.JointSets); } } private void DrawBonesRotator(List bones) { bool anyChanged = false; for (int i = 0; i < FingersData.HAND_JOINT_IDS.Length; i++) { bool changed = false; HandJointId joint = FingersData.HAND_JOINT_IDS[i]; HandFinger finger = FingersData.JOINT_TO_FINGER[(int)joint]; if (_handInteraction.HandPose.FingersFreedom[(int)finger] == JointFreedom.Free) { continue; } HandJointsSet jointSet = bones.Find(b => b.id == joint); if (jointSet == null) { continue; } Transform transform = jointSet.transform; transform.localRotation = jointSet.RotationOffset * _handInteraction.HandPose.JointRotations[i]; float scale = GIZMO_SCALE * _handInteraction.transform.lossyScale.x; Handles.color = EditorConstants.PRIMARY_COLOR; Quaternion entryRotation = transform.rotation; Quaternion rotation = Handles.Disc(entryRotation, transform.position, transform.forward, scale, false, 0); if (rotation != entryRotation) { changed = true; } if (FingersData.HAND_JOINT_CAN_SPREAD[i]) { Handles.color = EditorConstants.SECONDARY_COLOR; Quaternion curlRotation = rotation; rotation = Handles.Disc(curlRotation, transform.position, transform.up, scale, false, 0); if (rotation != curlRotation) { changed = true; } } if (!changed) { continue; } transform.rotation = rotation; Undo.RecordObject(_handInteraction, "Bone Rotation"); _handInteraction.HandPose.JointRotations[i] = jointSet.TrackedRotation; anyChanged = true; } if (anyChanged) { EditorUtility.SetDirty(_handInteraction); } } private void RegenerateGhost() { DestroyGhost(); CreateGhost(); } private void CreateGhost() { if (_HPResource == null) { return; } Transform relativeTo = _handInteraction.RelativeTo; VirtualHand ghostPrototype = _HPResource.GetHand(_handInteraction.HandPose.Handedness); if(relativeTo != null) { virtualHand = Instantiate(ghostPrototype, _handInteraction.transform); } else { virtualHand = Instantiate(ghostPrototype); } virtualHand.gameObject.hideFlags = HideFlags.HideAndDontSave; Pose relativePose = _handInteraction.RelativePose; Pose pose = PoseManager.GlobalPoseScaled(relativeTo, relativePose); virtualHand.SetPose(_handInteraction.HandPose, pose); } private void DestroyGhost() { if (virtualHand == null) { return; } DestroyImmediate(virtualHand.gameObject); } void AssignMissingGhostProvider() { if (_HPResource != null) { return; } TryGetDefaultResProvider(out _HPResource); } bool TryGetDefaultResProvider(out HandPerfabResource provider) { provider = null; HandPerfabResource[] providers = Resources.FindObjectsOfTypeAll(); if (providers != null && providers.Length > 0) { provider = providers[0]; return true; } string[] assets = AssetDatabase.FindAssets($"t:{nameof(HandPerfabResource)}"); if (assets != null && assets.Length > 0) { string pathPath = AssetDatabase.GUIDToAssetPath(assets[0]); provider = AssetDatabase.LoadAssetAtPath(pathPath); } return provider != null; } [MenuItem("UDE Interaction/Clear All Redundant Virtual Hand")] static void Clear() { var VirtualHands = FindObjectsOfType(); foreach (var VirtualHand in VirtualHands) { if (VirtualHand.name.Split("_")[0] == "vr" && VirtualHand.transform.parent.name.Contains("(Clone)")) { DestroyImmediate(VirtualHand); } } } } public class EditorConstants { public static readonly Color PRIMARY_COLOR = new Color(0f, 1f, 1f, 0.5f); public static readonly Color PRIMARY_COLOR_DISABLED = new Color(0f, 1f, 1f, 0.1f); public static readonly Color RED_COLOR = new Color(1f, 0f, 0f, 1f); public static readonly Color YELLOW_COLOR = new Color(1f, 1f, 0f, 1f); public static readonly Color SECONDARY_COLOR = new Color(0.5f, 0.3f, 1f, 0.5f); public static readonly Color SECONDARY_COLOR_DISABLED = new Color(0.5f, 0.3f, 1f, 0.1f); public static readonly float LINE_THICKNESS = 2f; public static readonly float ROW_HEIGHT = 20f; } [CustomPropertyDrawer(typeof(HandPose))] public class HandPoseEditor : PropertyDrawer { private bool _foldedFreedom = true; private bool _foldedRotations = false; public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { float multiplier = 4; if (_foldedFreedom) { multiplier += Constants.NUM_FINGERS; } if (_foldedRotations) { multiplier += FingersData.HAND_JOINT_IDS.Length; } return EditorConstants.ROW_HEIGHT * multiplier; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); Rect labelPos = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); EditorGUI.indentLevel++; Rect rowRect = new Rect(position.x, labelPos.y + EditorConstants.ROW_HEIGHT, position.width, EditorConstants.ROW_HEIGHT); DrawFlagProperty(property, rowRect, "Handedness:", "_handedness", false); rowRect.y += EditorConstants.ROW_HEIGHT; rowRect = DrawFingersFreedomMenu(property, rowRect); rowRect = DrawJointAngles(property, rowRect); EditorGUI.indentLevel--; EditorGUI.EndProperty(); } private Rect DrawFingersFreedomMenu(SerializedProperty property, Rect position) { _foldedFreedom = EditorGUI.Foldout(position, _foldedFreedom, "Fingers Freedom", true); position.y += EditorConstants.ROW_HEIGHT; if (_foldedFreedom) { SerializedProperty fingersFreedom = property.FindPropertyRelative("_fingersFreedom"); EditorGUI.indentLevel++; for (int i = 0; i < Constants.NUM_FINGERS; i++) { SerializedProperty finger = fingersFreedom.GetArrayElementAtIndex(i); HandFinger fingerID = (HandFinger)i; JointFreedom current = (JointFreedom)finger.intValue; JointFreedom selected = (JointFreedom)EditorGUI.EnumPopup(position, $"{fingerID}", current); finger.intValue = (int)selected; position.y += EditorConstants.ROW_HEIGHT; } EditorGUI.indentLevel--; } return position; } private Rect DrawJointAngles(SerializedProperty property, Rect position) { _foldedRotations = EditorGUI.Foldout(position, _foldedRotations, "Joint Angles", true); position.y += EditorConstants.ROW_HEIGHT; if (_foldedRotations) { SerializedProperty jointRotations = property.FindPropertyRelative("_jointRotations"); EditorGUI.indentLevel++; for (int i = 0; i < FingersData.HAND_JOINT_IDS.Length; i++) { SerializedProperty finger = jointRotations.GetArrayElementAtIndex(i); HandJointId jointID = FingersData.HAND_JOINT_IDS[i]; Vector3 current = finger.quaternionValue.eulerAngles; Vector3 rotation = EditorGUI.Vector3Field(position, $"{jointID}", current); finger.quaternionValue = Quaternion.Euler(rotation); position.y += EditorConstants.ROW_HEIGHT; } EditorGUI.indentLevel--; } return position; } private void DrawFlagProperty(SerializedProperty parentProperty, Rect position, string title, string fieldName, bool isFlags) where TEnum : Enum { SerializedProperty fieldProperty = parentProperty.FindPropertyRelative(fieldName); TEnum value = (TEnum)Enum.ToObject(typeof(TEnum), fieldProperty.intValue); Enum selectedValue = isFlags ? EditorGUI.EnumFlagsField(position, title, value) : EditorGUI.EnumPopup(position, title, value); fieldProperty.intValue = (int)Enum.ToObject(typeof(TEnum), selectedValue); } } }