using System; using System.Collections.Generic; using System.Linq; using HurricaneVR.Framework.ControllerInput; using HurricaneVR.Framework.Core; using HurricaneVR.Framework.Core.HandPoser; using HurricaneVR.Framework.Core.HandPoser.Data; using HurricaneVR.Framework.Core.Utils; using HurricaneVR.Framework.Shared; using HurricaneVR.Framework.Shared.Utilities; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; using Object = UnityEngine.Object; #if UNITY_2021_2_OR_NEWER using UnityEditor.SceneManagement; #else using UnityEditor.Experimental.SceneManagement; #endif namespace HurricaneVR.Editor { [CustomEditor(typeof(HVRHandPoser))] public class HVRHandPoserEditor : UnityEditor.Editor { private SerializedProperty SP_LeftHandPreview; private SerializedProperty SP_RightHandPreview; private SerializedProperty SP_BodyPreview; private SerializedProperty SP_PreviewLeft; private SerializedProperty SP_PreviewRight; private SerializedProperty SP_LeftAutoPose; private SerializedProperty SP_RightAutoPose; private SerializedProperty SP_SelectionIndex; private SerializedProperty SP_PoseNames; private SerializedProperty SP_PrimaryPose; private SerializedProperty SP_Blends; private VisualElement _root; private VisualTreeAsset _tree; private HVRHandPoser Poser; private IntegerField _selectionIndexField; private string _leftInstanceId; private string _rightInstanceId; private string _bodyId; private HVRPhysicsPoser _leftPhysicsPoser; private HVRPhysicsPoser _rightPhysicsPoser; public ObjectField SelectedPoseField { get; set; } public ListView PosesListView { get; set; } public Toggle PreviewRightToggle { get; set; } public Toggle PreviewLeftToggle { get; set; } public bool FullBody => HVRSettings.Instance.InverseKinematics; protected int SelectedIndex { get => SP_SelectionIndex?.intValue ?? 0; set { if (SP_SelectionIndex != null) SP_SelectionIndex.intValue = value; serializedObject.ApplyModifiedProperties(); } } public bool PreviewLeft => SP_PreviewLeft.boolValue; public bool PreviewRight => SP_PreviewRight.boolValue; protected GameObject BodyPreview { get { if (SP_BodyPreview == null || SP_BodyPreview.objectReferenceValue == null) return null; return SP_BodyPreview.objectReferenceValue as GameObject; } set { if (SP_BodyPreview != null) SP_BodyPreview.objectReferenceValue = value; } } protected GameObject LeftHandPreview { get { if (SP_LeftHandPreview == null || SP_LeftHandPreview.objectReferenceValue == null) return null; return SP_LeftHandPreview.objectReferenceValue as GameObject; } set { if (SP_LeftHandPreview != null) SP_LeftHandPreview.objectReferenceValue = value; } } protected GameObject RightHandPreview { get { if (SP_RightHandPreview == null || SP_RightHandPreview.objectReferenceValue == null) return null; return SP_RightHandPreview.objectReferenceValue as GameObject; } set { if (SP_RightHandPreview != null) SP_RightHandPreview.objectReferenceValue = value; } } private HVRHandPoseBlend PrimaryPose { get { return Poser.PrimaryPose; } set { Poser.PrimaryPose = value; serializedObject.ApplyModifiedProperties(); } } public HVRHandPose SelectedPose { get { return SelectedBlendPose?.Pose; } set { if (SelectedBlendPose == null) return; SelectedBlendPose.Pose = value; } } public HVRHandPoseBlend SelectedBlendPose { get { if (CurrentPoseIndex <= PrimaryIndex) return Poser.PrimaryPose; return Poser.Blends[BlendIndex]; } } public SerializedProperty SerializedSelectedPose { get { if (CurrentPoseIndex <= PrimaryIndex) return SP_PrimaryPose; return SP_Blends.GetArrayElementAtIndex(BlendIndex); } } public int PrimaryIndex => 0; public int CurrentPoseIndex { get => PosesListView?.selectedIndex ?? 0; } public int BlendIndex { get => CurrentPoseIndex - 1 - PrimaryIndex; } private static readonly Dictionary _map = new Dictionary(); private static readonly List cleanup = new List(); private class PreviewState { public Transform Target; public Transform SelectedBone; public int SelectedFinger; public HVRPosableHand Hand; public bool DrawRotation; public bool FullBody; public Transform Body; public bool IsLeft; } static HVRHandPoserEditor() { SceneView.duringSceneGui -= OnSceneGUI2; SceneView.duringSceneGui += OnSceneGUI2; } private static void OnSceneGUI2(SceneView view) { cleanup.Clear(); foreach (var kvp in _map) { var state = kvp.Value; if (state.Target) { if (!state.Hand) { if (state.FullBody) { if (state.Body) state.Hand = state.Body.GetComponentsInChildren().FirstOrDefault(e => e == state.IsLeft); else { cleanup.Add(kvp.Key); state.Target = null; } } else { state.Target.TryGetComponent(out state.Hand); } } DrawHandHandles(state.Target, ref state.DrawRotation); SetupBoneButtons(state.Hand, ref state.SelectedBone, ref state.SelectedFinger); DrawRotationHandle(state.SelectedBone); } else { if (!Application.isPlaying) cleanup.Add(kvp.Key); } } foreach (var c in cleanup) _map.Remove(c); } private void GetHands(ref HVRPosableHand leftHand, ref HVRPosableHand rightHand) { leftHand = null; rightHand = null; if (FullBody) { if (BodyPreview) { leftHand = BodyPreview.GetComponentsInChildren().FirstOrDefault(e => e.IsLeft); rightHand = BodyPreview.GetComponentsInChildren().FirstOrDefault(e => e.IsRight); } } else { if (LeftHandPreview) { leftHand = LeftHandPreview.GetComponent(); } if (RightHandPreview) { rightHand = RightHandPreview.GetComponent(); } } } private void GetPhysicsPosers() { if (FullBody) { if (BodyPreview) { if (!_leftPhysicsPoser) { _leftPhysicsPoser = BodyPreview.GetComponentsInChildren().FirstOrDefault(e => e.Hand && e.Hand.IsLeft); } if (!_rightPhysicsPoser) { _rightPhysicsPoser = BodyPreview.GetComponentsInChildren().FirstOrDefault(e => e.Hand && e.Hand.IsRight); } } else { _leftPhysicsPoser = null; _rightPhysicsPoser = null; } } else { if (LeftHandPreview) { if (!_leftPhysicsPoser) { _leftPhysicsPoser = LeftHandPreview.GetComponent(); } } else { _leftPhysicsPoser = null; } if (RightHandPreview) { _rightPhysicsPoser = RightHandPreview.GetComponent(); } else { _rightPhysicsPoser = null; } } if (_leftPhysicsPoser) { _leftPhysicsPoser.transform.SetLayerRecursive(HVRLayers.Hand); _leftPhysicsPoser.CurrentMask = ~LayerMask.GetMask("Hand"); if (_leftPhysicsPoser.Validate()) _leftPhysicsPoser.Setup(); } if (_rightPhysicsPoser) { _rightPhysicsPoser.transform.SetLayerRecursive(HVRLayers.Hand); _rightPhysicsPoser.CurrentMask = ~LayerMask.GetMask("Hand"); if (_rightPhysicsPoser.Validate()) _rightPhysicsPoser.Setup(); } } private static void DrawHandHandles(Transform handleTarget, ref bool drawRotation) { if (!handleTarget) return; Handles.color = Color.yellow; var capMethod = (Handles.CapFunction)Handles.CubeHandleCap; if (drawRotation) { capMethod = Handles.SphereHandleCap; } if (Handles.Button(handleTarget.position + new Vector3(0, 0.075f, 0.0f), Quaternion.identity, .01f, .01f, capMethod)) { drawRotation = !drawRotation; } var offset = HVRSettings.Instance.HandPoseHandleOffset; if (!drawRotation) { var previous = handleTarget.position + offset; var newPosition = Handles.PositionHandle(previous, Quaternion.identity); if (newPosition != previous) { Undo.RecordObject(handleTarget, "Hand Moved"); handleTarget.position = newPosition - offset; } } else { var newRotation = Handles.RotationHandle(handleTarget.rotation, handleTarget.position + offset); if (newRotation != handleTarget.rotation) { Undo.RecordObject(handleTarget, "hand Rotated"); handleTarget.rotation = newRotation; } } } private static void DrawRotationHandle(Transform bone) { if (bone) { var newRotation = Handles.RotationHandle(bone.rotation, bone.position); if (newRotation != bone.rotation) { Undo.RecordObject(bone, "Bone Rotated"); bone.rotation = newRotation; } } } private static void SetupBoneButtons(HVRPosableHand hand, ref Transform selectedBone, ref int selectedFinger) { if (hand) { for (var i = 0; i < hand.Fingers.Length; i++) { Color color = Color.blue; switch (i) { case 0: color = Color.blue; break; case 1: color = Color.green; break; case 2: color = Color.red; break; case 3: color = Color.cyan; break; case 4: color = Color.magenta; break; } color.a = .5f; Handles.color = color; var finger = hand.Fingers[i]; for (var j = 0; j < finger.Bones.Count; j++) { if (HVRSettings.Instance.PoserShowsOneFinger && i != selectedFinger && j > 0) { break; } var bone = finger.Bones[j]; if (Handles.Button(bone.Transform.position, bone.Transform.rotation, .005f, .001f, Handles.SphereHandleCap)) { if (j == 0) { selectedFinger = i; } selectedBone = bone.Transform; } } } } else { selectedBone = null; } } private bool _inPrefabMode; private bool _active; private void OnEnable() { Poser = target as HVRHandPoser; _leftInstanceId = "LeftPreview_" + target.GetInstanceID(); _rightInstanceId = "RightPreview_" + target.GetInstanceID(); _bodyId = "Body_" + target.GetInstanceID(); SP_LeftHandPreview = serializedObject.FindProperty("LeftHandPreview"); SP_RightHandPreview = serializedObject.FindProperty("RightHandPreview"); SP_BodyPreview = serializedObject.FindProperty("BodyPreview"); SP_PrimaryPose = serializedObject.FindProperty("PrimaryPose"); SP_Blends = serializedObject.FindProperty("Blends"); SP_PreviewLeft = serializedObject.FindProperty("PreviewLeft"); SP_PreviewRight = serializedObject.FindProperty("PreviewRight"); SP_LeftAutoPose = serializedObject.FindProperty("LeftAutoPose"); SP_RightAutoPose = serializedObject.FindProperty("RightAutoPose"); SP_SelectionIndex = serializedObject.FindProperty("SelectionIndex"); SP_PoseNames = serializedObject.FindProperty("PoseNames"); _root = new VisualElement(); _tree = UnityEngine.Resources.Load("HVRHandPoserEditor"); var stage = PrefabStageUtility.GetPrefabStage(Poser.gameObject); _inPrefabMode = stage != null; _active = true; var s = _root.schedule.Execute(EditorUpdate); s.Every(1000); s.Until(() => !_active); } private void EditorUpdate() { CheckRigidBody(); } private void OnDisable() { _active = false; } private void CreatePoseIfNeeded() { if (PrimaryPose.Pose == null && HVRSettings.Instance.OpenHandPose) { _root.schedule.Execute(() => { PrimaryPose.SetDefaults(); serializedObject.ApplyModifiedProperties(); }); var poseProperty = SP_PrimaryPose.FindPropertyRelative("Pose"); var clone = poseProperty.objectReferenceValue = HVRSettings.Instance.OpenHandPose.DeepCopy(); clone.name = "Unsaved!"; serializedObject.ApplyModifiedProperties(); } } public override VisualElement CreateInspectorGUI() { _root.Clear(); _tree.CloneTree(_root); CreatePoseIfNeeded(); blendEditorRoot = _root.Q("BlendEditorRoot"); blendEditorRoot.Q("Pose").objectType = typeof(HVRHandPose); _root.Add(blendEditorRoot); SetupAddButton(); SetupDeleteButton(); SetupSelectedPose(blendEditorRoot); SetupPosesListView(); SetupNewButton(); SetupSaveAsButton(); SetupSaveButton(); SetupMirrorButtons(); SetupHandButtons(); SetupAutoPoseButtons(); //can't remember why I would add this field on the ui... //_selectionIndexField = new IntegerField("SelectedIndex"); //_selectionIndexField.bindingPath = "SelectionIndex"; //_selectionIndexField.RegisterValueChangedCallback(evt => //{ // if (PosesListView.selectedIndex != evt.newValue) PosesListView.selectedIndex = evt.newValue; //}); //_root.Add(_selectionIndexField); if (_inPrefabMode) { _root.Q("MirrorAxis").style.display = DisplayStyle.None; _root.Q("boxPreview").style.display = DisplayStyle.None; } else { _root.Q("warning").style.display = DisplayStyle.None; PreviewLeftToggle = _root.Q("PreviewLeft"); PreviewLeftToggle.BindProperty(SP_PreviewLeft); PreviewRightToggle = _root.Q("PreviewRight"); PreviewRightToggle.BindProperty(SP_PreviewRight); PreviewLeftToggle.RegisterValueChangedCallback(OnPreviewLeftChanged); PreviewRightToggle.RegisterValueChangedCallback(OnPreviewRightChanged); ToggleLeftAutoPose = _root.Q("LeftAutoPose"); ToggleLeftAutoPose.BindProperty(SP_LeftAutoPose); ToggleRightAutoPose = _root.Q("RightAutoPose"); ToggleRightAutoPose.BindProperty(SP_RightAutoPose); ToggleLeftAutoPose.RegisterValueChangedCallback(OnLeftAutoPoseChanged); ToggleRightAutoPose.RegisterValueChangedCallback(OnRightAutoPoseChanged); if (SelectedIndex >= PosesListView.itemsSource.Count + PrimaryIndex) { Debug.Log($"Stored SelectedIndex is higher than pose count."); SelectedIndex = PosesListView.itemsSource.Count - PrimaryIndex - 1; serializedObject.ApplyModifiedProperties(); } PosesListView.selectedIndex = SelectedIndex; GetPhysicsPosers(); if (FullBody) { var body = GameObject.Find(_bodyId); if (body) { SP_BodyPreview.objectReferenceValue = body; } UpdateBodyPreview(SelectedPose != null ? SelectedPose.LeftHand : null, SelectedPose != null ? SelectedPose.RightHand : null, PreviewLeft, PreviewRight); } else { SP_PreviewLeft.boolValue = SP_LeftHandPreview.objectReferenceValue != null; SP_PreviewRight.boolValue = SP_RightHandPreview.objectReferenceValue != null; if (!SP_PreviewLeft.boolValue) { FindPreviewHand(true, out var left); if (left) { SP_PreviewLeft.boolValue = true; SP_LeftHandPreview.objectReferenceValue = left; } } if (!SP_PreviewRight.boolValue) { FindPreviewHand(false, out var right); if (right) { SP_PreviewRight.boolValue = true; SP_RightHandPreview.objectReferenceValue = right; } } UpdatePreview(false, SP_PreviewRight.boolValue, SelectedPose != null ? SelectedPose.LeftHand : null); UpdatePreview(true, SP_PreviewLeft.boolValue, SelectedPose != null ? SelectedPose.RightHand : null); } if (_leftPhysicsPoser) { SP_LeftAutoPose.boolValue = _leftPhysicsPoser.LiveUpdate; } else { SP_LeftAutoPose.boolValue = false; } if (_rightPhysicsPoser) { SP_RightAutoPose.boolValue = _rightPhysicsPoser.LiveUpdate; } else { SP_RightAutoPose.boolValue = false; } } serializedObject.ApplyModifiedProperties(); return _root; } private void SetupAutoPoseButtons() { _root.Q