/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using Oculus.Interaction.HandGrab.Visuals; using Oculus.Interaction.Input; using System; using System.Collections.Generic; using UnityEditor; using UnityEditorInternal; using UnityEngine; namespace Oculus.Interaction.HandGrab.Editor { public class HandGrabScaleKeysEditor where TInteractable : MonoBehaviour, IRelativeToRef { private MonoBehaviour _target; private SerializedObject _serializedObject; private List _handGrabPoses; private bool _markAsOptional; private SerializedProperty _posesProperty; private ReorderableList _list; private HashSet _scalesSet = new HashSet(); private IRelativeToRef _relativeToRef; private HandGhostProvider _ghostVisualsProvider; private HandGhost _handGhost; private HandPose _ghostHandPose = new HandPose(); private Handedness _lastHandedness; private float _handScale = 1f; private const float POSE_RECT_WIDTH = 40f; private const float LEFT_MARGIN = 15; private const float RIGHT_MARGIN = 65f; private const float MIN_SCALE = 0.5f; private const float MAX_SCALE = 2f; public HandGrabScaleKeysEditor(SerializedObject serializedObject, List handGrabPoses, string collectionName, bool markAsOptional) { _target = serializedObject.targetObject as MonoBehaviour; _relativeToRef = serializedObject.targetObject as IRelativeToRef; _posesProperty = serializedObject.FindProperty(collectionName); _handGrabPoses = handGrabPoses; _markAsOptional = markAsOptional; HandGhostProviderUtils.TryGetDefaultProvider(out _ghostVisualsProvider); _list = new ReorderableList(serializedObject, _posesProperty, draggable: true, displayHeader: false, displayAddButton: true, displayRemoveButton: true); InitializeListDrawers(_list); } public void TearDown() { DestroyGhost(); } public void DrawInspector() { EditorGUILayout.LabelField($"{(_markAsOptional ? "[Optional]" : "")} Scaled Hand Grab Poses"); Rect startRect = GUILayoutUtility.GetLastRect(); _list.DoLayoutList(); ScaledHandPoseSlider(); CheckUniqueScales(); CheckUniqueHandedness(); DrawGenerationMenu(); Rect endRect = GUILayoutUtility.GetLastRect(); DrawBox( new Vector2(startRect.position.x, startRect.position.y + startRect.size.y), endRect.position + endRect.size); UpdateGhost(); } private void DrawBox(Vector2 start, Vector2 end) { float margin = 5f; float height = end.y - start.y; Rect topBar = new Rect(start.x - margin, start.y, margin, 1f); Rect bottomBar = new Rect(start.x- margin, end.y, margin, 1f); Rect leftBar = new Rect(start.x - margin, start.y, 1f, height); EditorGUI.DrawRect(topBar, Color.gray); EditorGUI.DrawRect(bottomBar, Color.gray); EditorGUI.DrawRect(leftBar, Color.gray); } private void InitializeListDrawers(ReorderableList list) { list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { rect.height = EditorGUIUtility.singleLineHeight; SerializedProperty itemProperty = list.serializedProperty.GetArrayElementAtIndex(index); HandGrabPose grabPose = itemProperty.objectReferenceValue as HandGrabPose; float scale = float.NaN; if (grabPose != null) { scale = grabPose.transform.lossyScale.x / _relativeToRef.RelativeTo.lossyScale.x; } GUIContent objectLabel = new GUIContent($"Scale x{scale.ToString("F2")}"); EditorGUI.PropertyField(rect, itemProperty, objectLabel); }; } private void ScaledHandPoseSlider() { float minScale = MIN_SCALE; float maxScale = MAX_SCALE; for (int i = 0; i < _handGrabPoses.Count; i++) { HandGrabPose grabPose = _handGrabPoses[i]; float scale = grabPose.transform.lossyScale.x; if (scale < minScale) { minScale = scale; } if (scale > maxScale) { maxScale = scale; } } _handScale = EditorGUILayout.Slider(_handScale, minScale, maxScale); Rect backRect = GUILayoutUtility.GetLastRect(); backRect.x += LEFT_MARGIN; backRect.width -= RIGHT_MARGIN; for (int i = 0; i < _handGrabPoses.Count; i++) { HandGrabPose grabPose = _handGrabPoses[i]; if (grabPose == null) { continue; } float x = backRect.x + Mathf.InverseLerp(minScale, maxScale, grabPose.transform.lossyScale.x) * backRect.width; Rect poseRect = new Rect(x - POSE_RECT_WIDTH * 0.5f, backRect.y, POSE_RECT_WIDTH, backRect.height); EditorGUI.LabelField(poseRect, EditorGUIUtility.IconContent("curvekeyframeselected")); } } private void CheckUniqueScales() { _scalesSet.Clear(); for (int i = 0; i < _handGrabPoses.Count; i++) { HandGrabPose grabPose = _handGrabPoses[i]; if (grabPose == null) { continue; } float scale = grabPose.transform.lossyScale.x / _relativeToRef.RelativeTo.lossyScale.x; if (_scalesSet.Contains(scale)) { EditorGUILayout.HelpBox( $"Duplicated {nameof(HandGrabPose)} of scale {scale} at index {i}.", MessageType.Warning); } _scalesSet.Add(scale); } } private void CheckUniqueHandedness() { bool handednessSet = false; Handedness validHandedness = Handedness.Left; for (int i = 0; i < _handGrabPoses.Count; i++) { HandGrabPose grabPose = _handGrabPoses[i]; if (grabPose == null || grabPose.HandPose == null) { continue; } Handedness grabPoseHandedness = grabPose.HandPose.Handedness; if (!handednessSet) { handednessSet = true; validHandedness = grabPoseHandedness; } else if (grabPoseHandedness != validHandedness) { EditorGUILayout.HelpBox($"Different Handedness at index {i}. " + $"Ensure all HandGrabPoses have the same Handedness", MessageType.Warning); } } } private void DrawGenerationMenu() { if (GUILayout.Button($"Add HandGrabPose Key with Scale {_handScale.ToString("F2")}")) { AddHandGrabPose(_handScale); } if (GUILayout.Button("Refresh HandGrab Poses")) { RefreshHandPoses(); } } private void AddHandGrabPose(float scale) { HandGrabPose handGrabPose = HandGrabUtils.CreateHandGrabPose(_target.transform, _relativeToRef.RelativeTo); float relativeScale = scale / _relativeToRef.RelativeTo.lossyScale.x; bool rangeFound = GrabPoseFinder.FindInterpolationRange(relativeScale, _handGrabPoses, out HandGrabPose from, out HandGrabPose to, out float t); handGrabPose.transform.localScale = Vector3.one * relativeScale; _handGrabPoses.Add(handGrabPose); EditorUtility.SetDirty(_target); if (!rangeFound) { return; } Pose relativePose = Pose.identity; PoseUtils.Lerp(from.RelativePose, to.RelativePose, t, ref relativePose); Pose rootPose = PoseUtils.GlobalPoseScaled(_relativeToRef.RelativeTo, relativePose); HandPose resultHandPose = new HandPose(from.HandPose); HandPose.Lerp(from.HandPose, to.HandPose, t, ref resultHandPose); Grab.GrabSurfaces.IGrabSurface surface = from.SnapSurface?.CreateDuplicatedSurface(handGrabPose.gameObject); handGrabPose.InjectAllHandGrabPose(_relativeToRef.RelativeTo); handGrabPose.InjectOptionalHandPose(resultHandPose); handGrabPose.InjectOptionalSurface(surface); handGrabPose.transform.SetPose(rootPose); } private void RefreshHandPoses() { _handGrabPoses.Clear(); HandGrabPose[] handGrabPoses = _target.GetComponentsInChildren(); _handGrabPoses.AddRange(handGrabPoses); EditorUtility.SetDirty(_target); } #region Ghost private void UpdateGhost() { if (_handGrabPoses.Count == 0 || _handGrabPoses[0].HandPose == null) { DestroyGhost(); return; } Transform relativeTo = _relativeToRef.RelativeTo; Pose rootPose = Pose.identity; float relativeScale = _handScale / relativeTo.lossyScale.x; bool rangeFound = GrabPoseFinder.FindInterpolationRange(relativeScale, _handGrabPoses, out HandGrabPose from, out HandGrabPose to, out float t); if (!rangeFound) { DestroyGhost(); return; } HandPose.Lerp(from.HandPose, to.HandPose, t, ref _ghostHandPose); PoseUtils.Lerp(from.RelativePose, to.RelativePose, t, ref rootPose); rootPose = PoseUtils.GlobalPoseScaled(relativeTo, rootPose); DisplayGhost(_ghostHandPose, rootPose, relativeScale); } private void DisplayGhost(HandPose handPose, Pose rootPose, float scale) { if (_handGhost != null && _lastHandedness != handPose.Handedness) { DestroyGhost(); } _lastHandedness = handPose.Handedness; if (_handGhost == null) { HandGhost ghostPrototype = _ghostVisualsProvider.GetHand(_lastHandedness); _handGhost = GameObject.Instantiate(ghostPrototype, _target.transform); _handGhost.gameObject.hideFlags = HideFlags.HideAndDontSave; } _handGhost.transform.localScale = Vector3.one * scale; _handGhost.SetPose(handPose, rootPose); } private void DestroyGhost() { if (_handGhost == null) { return; } GameObject.DestroyImmediate(_handGhost.gameObject); _handGhost = null; } #endregion } }