VR4RoboticArm2/VR4RoboticArm/Library/PackageCache/com.meta.xr.sdk.interaction/Editor/Utils/HandGrabPoseWizard.cs
IonutMocanu d7aba243a2 Main
2025-09-08 11:04:02 +03:00

350 lines
13 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 Oculus.Interaction.Utils;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.HandGrab.Editor
{
public class HandGrabPoseWizard : EditorWindow
{ /// <summary>
/// The Hand being used for recording HandGrabPoses
/// </summary>
[SerializeField]
private Hand _hand;
[SerializeField]
private int _handID = 0;
public Hand Hand
{
get
{
if (_hand == null && _handID != 0)
{
_hand = EditorUtility.InstanceIDToObject(_handID) as Hand;
_handID = 0;
}
return _hand;
}
set
{
_hand = value;
if (_hand != null)
{
_handID = value.GetInstanceID();
}
else
{
_handID = 0;
}
}
}
/// <summary>
/// The Gameobject that the user is recording the HandGrabPose for. e.g. a key
/// </summary>
[SerializeField]
private Rigidbody _item;
/// <summary>
/// References the hand prototypes used to represent the HandGrabInteractables. These are the
/// static hands placed around the interactable to visualize the different holding hand-poses.
/// Not mandatory.
/// </summary>
[SerializeField]
private HandGhostProvider _ghostProvider;
private HandGhostProvider GhostProvider
{
#if ISDK_OPENXR_HAND
get => _handGhostProvider;
#else
get => _ghostProvider;
#endif
}
#if ISDK_OPENXR_HAND
/// <summary>
/// References the hand prototypes used to represent the HandGrabInteractables. These are the
/// static hands placed around the interactable to visualize the different holding hand-poses.
/// Not mandatory.
/// </summary>
[SerializeField]
[InspectorName("Ghost Provider")]
private HandGhostProvider _handGhostProvider;
#endif
/// <summary>
/// This ScriptableObject stores the HandGrabInteractables generated at Play-Mode so it survives
/// the Play-Edit cycle.
/// Create a collection and assign it even in Play Mode and make sure to store here the
/// interactables, then restore it in Edit-Mode to be serialized.
/// </summary>
[SerializeField]
private HandGrabInteractableDataCollection _posesCollection;
/// <summary>
/// The keyboard key that can be pressed for recording a hand grab pose
/// </summary>
[SerializeField]
private KeyCode _recordKey = KeyCode.Space;
private GUIStyle _richTextStyle;
private Vector2 _scrollPos = Vector2.zero;
[MenuItem("Meta/Interaction/Hand Grab Pose Recorder")]
private static void CreateWizard()
{
HandGrabPoseWizard window = EditorWindow.GetWindow<HandGrabPoseWizard>();
window.titleContent = new GUIContent("Hand Grab Pose Recorder");
window.Show();
}
private void OnEnable()
{
_richTextStyle = EditorGUIUtility.GetBuiltinSkin(EditorGUIUtility.isProSkin ? EditorSkin.Scene : EditorSkin.Inspector).label;
_richTextStyle.richText = true;
_richTextStyle.wordWrap = true;
if (GhostProvider == null)
{
#if ISDK_OPENXR_HAND
HandGhostProviderUtils.TryGetDefaultProvider(out _handGhostProvider);
#else
HandGhostProviderUtils.TryGetDefaultProvider(out _ghostProvider);
#endif
}
}
private void OnGUI()
{
Event e = Event.current;
if (e.type == EventType.KeyDown
&& e.keyCode == _recordKey)
{
RecordPose();
e.Use();
}
GUILayout.Label("Generate HandGrabPoses for grabbing an item <b>using your Hand in Play Mode</b>.\nThen Store and retrieve them in Edit Mode to persist and tweak them.", _richTextStyle);
_scrollPos = GUILayout.BeginScrollView(_scrollPos);
GUILayout.Space(20);
GUILayout.Label("<size=20>1</size>\nAssign the hand that will be tracked and the item for which you want to record HandGrabPoses", _richTextStyle);
GUILayout.Label("Hand used for recording poses:");
Hand = EditorGUILayout.ObjectField(Hand, typeof(Hand), true) as Hand;
GUILayout.Label("GameObject to record the hand grab poses for:");
GenerateObjectField(ref _item);
GUILayout.Label("Prefabs provider for the hands (ghosts) to visualize the recorded poses:");
#if ISDK_OPENXR_HAND
if (HandAnimationUtils.GenerateObjectField(ref _handGhostProvider))
#else
if (HandAnimationUtils.GenerateObjectField(ref _ghostProvider))
#endif
{
HandGhostProviderUtils.SetLastDefaultProvider(GhostProvider);
}
GUILayout.Space(20);
GUILayout.Label("<size=20>2</size>\nGo to <b>Play Mode</b> and record as many poses as you need.", _richTextStyle);
GUILayout.Label($"Press the big <b>Record</b> button with your free hand\nor the <b>{_recordKey}</b> key to record a HandGrabPose <b>(requires focus on this window)</b>.", _richTextStyle);
_recordKey = (KeyCode)EditorGUILayout.EnumPopup(_recordKey);
if (GUILayout.Button("Record HandGrabPose", GUILayout.Height(100)))
{
RecordPose();
}
GUILayout.Space(20);
GUILayout.Label("<size=20>3</size>\nStore your poses before exiting <b>Play Mode</b>.\nIf no collection is provided <b>it will autogenerate one</b>", _richTextStyle);
GenerateObjectField(ref _posesCollection);
if (GUILayout.Button("Save To Collection"))
{
SaveToAsset();
}
GUILayout.Space(20);
GUILayout.Label("<size=20>4</size>\nNow load the poses from the PosesCollection in <b>Edit Mode</b> to tweak and persist them as gameobjects", _richTextStyle);
if (GUILayout.Button("Load From Collection"))
{
LoadFromAsset();
}
GUILayout.EndScrollView();
}
private void GenerateObjectField<T>(ref T obj) where T : Object
{
obj = EditorGUILayout.ObjectField(obj, typeof(T), true) as T;
}
/// <summary>
/// Finds the nearest object that can be snapped to and adds a new HandGrabInteractable to
/// it with the user hand representation.
/// </summary>
public void RecordPose()
{
if (!Application.isPlaying)
{
Debug.LogError("Recording tracked hands only works in Play Mode!", this);
return;
}
if (Hand == null)
{
Debug.LogError("Missing Hand reference.", this);
return;
}
if (_item == null)
{
Debug.LogError("Missing recordable item", this);
return;
}
HandPose trackedHandPose = TrackedPose();
if (trackedHandPose == null)
{
Debug.LogError("Tracked Pose could not be retrieved", this);
return;
}
if (!Hand.GetRootPose(out Pose handRoot))
{
Debug.LogError("Hand Root pose could not be retrieved", this);
return;
}
Pose gripPoint = PoseUtils.DeltaScaled(_item.transform, handRoot);
HandGrabPose point = AddHandGrabPose(trackedHandPose, gripPoint);
AttachGhost(point);
}
private HandPose TrackedPose()
{
if (!Hand.GetJointPosesLocal(out ReadOnlyHandJointPoses localJoints))
{
return null;
}
HandPose result = new HandPose(Hand.Handedness);
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; ++i)
{
HandJointId jointID = FingersMetadata.HAND_JOINT_IDS[i];
result.JointRotations[i] = localJoints[jointID].rotation;
}
return result;
}
private void AttachGhost(HandGrabPose point)
{
if (GhostProvider == null)
{
return;
}
HandGhost ghostPrefab = GhostProvider.GetHand(Hand.Handedness);
HandGhost ghost = GameObject.Instantiate(ghostPrefab, point.transform);
ghost.SetPose(point);
}
/// <summary>
/// Creates a new HandGrabInteractable at the exact pose of a given hand.
/// Mostly used with Hand-Tracking at Play-Mode
/// </summary>
/// <param name="rawPose">The user controlled hand pose.</param>
/// <param name="snapPoint">The user controlled hand pose.</param>
/// <returns>The generated HandGrabPose.</returns>
private HandGrabPose AddHandGrabPose(HandPose rawPose, Pose snapPoint)
{
HandGrabInteractable interactable = HandGrabUtils.CreateHandGrabInteractable(_item.transform);
var pointData = new HandGrabUtils.HandGrabPoseData()
{
handPose = rawPose,
scale = Hand.Scale / interactable.RelativeTo.lossyScale.x,
gripPose = snapPoint,
};
return HandGrabUtils.LoadHandGrabPose(interactable, pointData);
}
/// <summary>
/// Creates a new HandGrabInteractable from the stored data.
/// Mostly used to restore a HandGrabInteractable that was stored during Play-Mode.
/// </summary>
/// <param name="data">The data of the HandGrabInteractable.</param>
/// <returns>The generated HandGrabInteractable.</returns>
private HandGrabInteractable LoadHandGrabInteractable(HandGrabUtils.HandGrabInteractableData data)
{
HandGrabInteractable interactable = HandGrabUtils.CreateHandGrabInteractable(_item.transform);
HandGrabUtils.LoadData(interactable, data);
return interactable;
}
/// <summary>
/// Stores the interactables to a SerializedObject (the empty object must be
/// provided in the inspector or one will be auto-generated). First it translates the HandGrabInteractable to a serialized
/// form HandGrabbableData).
/// This method is called from a button in the Inspector.
/// </summary>
private void SaveToAsset()
{
if (_posesCollection == null)
{
GenerateCollectionAsset();
}
var savedPoses = new List<HandGrabUtils.HandGrabInteractableData>();
foreach (HandGrabInteractable snap in _item.GetComponentsInChildren<HandGrabInteractable>(false))
{
savedPoses.Add(HandGrabUtils.SaveData(snap));
}
_posesCollection.StoreInteractables(savedPoses);
}
/// <summary>
/// Load the HandGrabInteractable from a Collection.
/// This method is called from a button in the Inspector and will load the posesCollection.
/// </summary>
private void LoadFromAsset()
{
if (_posesCollection == null)
{
return;
}
foreach (var handPose in _posesCollection.InteractablesData)
{
LoadHandGrabInteractable(handPose);
}
}
public void GenerateCollectionAsset()
{
_posesCollection = ScriptableObject.CreateInstance<HandGrabInteractableDataCollection>();
string parentDir = Path.Combine("Assets", "HandGrabInteractableDataCollection");
if (!Directory.Exists(parentDir))
{
Directory.CreateDirectory(parentDir);
}
string name = _item != null ? _item.name : "Auto";
AssetDatabase.CreateAsset(_posesCollection, Path.Combine(parentDir, $"{name}_HandGrabCollection.asset"));
AssetDatabase.SaveAssets();
}
}
}