Files
HDRobotics/Assets/Oculus/Interaction/Editor/Grab/HandGrab/HandGrabScaleKeysEditor.cs

325 lines
12 KiB
C#

/*
* 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<TInteractable>
where TInteractable : MonoBehaviour, IRelativeToRef
{
private MonoBehaviour _target;
private SerializedObject _serializedObject;
private List<HandGrabPose> _handGrabPoses;
private bool _markAsOptional;
private SerializedProperty _posesProperty;
private ReorderableList _list;
private HashSet<float> _scalesSet = new HashSet<float>();
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<HandGrabPose> 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<HandGrabPose>();
_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
}
}