上传YomovSDK

This commit is contained in:
Sora丶kong
2026-03-03 03:15:46 +08:00
parent 9096da7e6c
commit eb97f31065
6477 changed files with 1932208 additions and 3 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ade49fa87f3cc404ea1b73cf2066c9a5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,672 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using VIVE.OpenXR.Feature;
using static VIVE.OpenXR.Feature.ViveAnchor;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
namespace VIVE.OpenXR.Toolkits.Anchor
{
public static class AnchorManager
{
static ViveAnchor feature = null;
static bool isSupported = false;
static bool isPersistedAnchorSupported = false;
static void EnsureFeature()
{
if (feature != null) return;
feature = OpenXRSettings.Instance.GetFeature<ViveAnchor>();
if (feature == null)
throw new NotSupportedException("ViveAnchor feature is not enabled");
}
static void EnsureCollection()
{
if (taskAcquirePAC != null)
{
Debug.Log("AnchorManager: Wait for AcquirePersistedAnchorCollection task.");
taskAcquirePAC.Wait();
}
if (persistedAnchorCollection == IntPtr.Zero)
throw new Exception("Should create Persisted Anchor Collection first.");
}
/// <summary>
/// Helper to get the extension feature instance.
/// </summary>
/// <returns>Instance of ViveAnchor feature.</returns>
public static ViveAnchor GetFeature()
{
if (feature != null) return feature;
feature = OpenXRSettings.Instance.GetFeature<ViveAnchor>();
return feature;
}
/// <summary>
/// Check if the extensions are supported. Should always check this before using the other functions.
/// </summary>
/// <returns>True if the extension is supported, false otherwise.</returns>
public static bool IsSupported()
{
if (GetFeature() == null) return false;
if (isSupported) return true;
var ret = false;
if (feature.GetProperties(out XrSystemAnchorPropertiesHTC properties) == XrResult.XR_SUCCESS)
{
Debug.Log("ViveAnchor: IsSupported() properties.supportedFeatures: " + properties.supportsAnchor);
ret = properties.supportsAnchor > 0;
isSupported = ret;
}
else
{
Debug.Log("ViveAnchor: IsSupported() GetSystemProperties failed.");
}
return ret;
}
/// <summary>
/// Check if the persisted anchor extension is supported and enabled.
/// Should always check this before using the other persistance function.
/// </summary>
/// <returns>True if persisted anchor extension is supported, false otherwise.</returns>
public static bool IsPersistedAnchorSupported()
{
if (GetFeature() == null) return false;
if (isPersistedAnchorSupported) return true;
else
isPersistedAnchorSupported = feature.IsPersistedAnchorSupported();
return isPersistedAnchorSupported;
}
/// <summary>
/// Create a spatial anchor at tracking space (Camera Rig).
/// </summary>
/// <param name="pose">The related pose to the tracking space (Camera Rig)</param>
/// <returns>Anchor container</returns>
public static Anchor CreateAnchor(Pose pose, string name)
{
EnsureFeature();
if (string.IsNullOrEmpty(name))
throw new ArgumentException("The name should not be empty.");
XrSpace baseSpace = feature.GetTrackingSpace();
XrSpatialAnchorCreateInfoHTC createInfo = new XrSpatialAnchorCreateInfoHTC();
createInfo.type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC;
createInfo.poseInSpace = new XrPosef();
createInfo.poseInSpace.position = pose.position.ToOpenXRVector();
createInfo.poseInSpace.orientation = pose.rotation.ToOpenXRQuaternion();
createInfo.name = new XrSpatialAnchorNameHTC(name);
createInfo.space = baseSpace;
if (feature.CreateSpatialAnchor(createInfo, out XrSpace anchor) == XrResult.XR_SUCCESS)
{
return new Anchor(anchor, name);
}
return null;
}
/// <summary>
/// Get the name of the spatial anchor.
/// </summary>
/// <param name="anchor">The anchor instance.</param>
/// <param name="name">Output parameter to hold the name of the anchor.</param>
/// <returns>True if the name is successfully retrieved, false otherwise.</returns>
public static bool GetSpatialAnchorName(Anchor anchor, out string name)
{
return GetSpatialAnchorName(anchor.GetXrSpace(), out name);
}
/// <summary>
/// Get the name of the spatial anchor.
/// </summary>
/// <param name="anchor">The XrSpace representing the anchor.</param>
/// <param name="name">Output parameter to hold the name of the anchor.</param>
/// <returns>True if the name is successfully retrieved, false otherwise.</returns>
public static bool GetSpatialAnchorName(XrSpace anchor, out string name)
{
name = "";
EnsureFeature();
XrResult ret = feature.GetSpatialAnchorName(anchor, out XrSpatialAnchorNameHTC xrName);
if (ret == XrResult.XR_SUCCESS)
name = xrName.ToString();
return ret == XrResult.XR_SUCCESS;
}
/// <summary>
/// Get the XrSpace stand for current tracking space.
/// </summary>
/// <returns></returns>
public static XrSpace GetTrackingSpace()
{
EnsureFeature();
return feature.GetTrackingSpace();
}
/// <summary>
/// Get the pose related to current tracking space. Only when position and orientation are both valid, the pose is valid.
/// </summary>
/// <param name="anchor"></param>
/// <param name="pose"></param>
/// <returns>true if both position and rotation are valid.</returns>
public static bool GetTrackingSpacePose(Anchor anchor, out Pose pose)
{
var sw = SpaceWrapper.Instance;
return anchor.GetRelatedPose(feature.GetTrackingSpace(), ViveInterceptors.Instance.GetPredictTime(), out pose);
}
// Use SemaphoreSlim to make sure only one anchor's task is running at the same time.
static readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
// Use lock to make sure taskAcquirePAC and persistedAnchorCollection assignment is atomic.
static readonly object asyncLock = new object();
static FutureTask<(XrResult, IntPtr)> taskAcquirePAC = null;
static IntPtr persistedAnchorCollection = System.IntPtr.Zero;
private static (XrResult, IntPtr) CompletePAC(IntPtr future)
{
Debug.Log("AnchorManager: AcquirePersistedAnchorCollectionComplete");
var ret = feature.AcquirePersistedAnchorCollectionComplete(future, out var completion);
lock (asyncLock)
{
taskAcquirePAC = null;
if (ret == XrResult.XR_SUCCESS)
{
ret = completion.futureResult;
Debug.Log("AnchorManager: AcquirePersistedAnchorCollection: Complete");
persistedAnchorCollection = completion.persistedAnchorCollection;
return (ret, persistedAnchorCollection);
}
else
{
//Debug.LogError("AcquirePersistedAnchorCollection: Complete: PersistedAnchorCollection=" + completion.persistedAnchorCollection);
persistedAnchorCollection = System.IntPtr.Zero;
return (ret, persistedAnchorCollection);
}
}
}
/// <summary>
/// Enable the persistance anchor feature. It will acquire a persisted anchor collection.
/// The first time PAC's acquiration may take time. You can to cancel the process by calling <see cref="ReleasePersistedAnchorCollection"/>.
/// You can wait for the returned task to complete, or by calling <see cref="IsPersistedAnchorCollectionAcquired"/> to check if the collection is ready.
/// Use <see cref="ReleasePersistedAnchorCollection"/> to free resource when no any persisted anchor operations are needed.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public static FutureTask<(XrResult, IntPtr)> AcquirePersistedAnchorCollection()
{
EnsureFeature();
if (!feature.IsPersistedAnchorSupported())
return FutureTask<(XrResult, IntPtr)>.FromResult((XrResult.XR_ERROR_EXTENSION_NOT_PRESENT, IntPtr.Zero));
lock (asyncLock)
{
if (persistedAnchorCollection != System.IntPtr.Zero)
return FutureTask<(XrResult, IntPtr)>.FromResult((XrResult.XR_SUCCESS, persistedAnchorCollection));
// If the persistedAnchorCollection is not ready, and the task is started, wait for it.
if (taskAcquirePAC != null)
return taskAcquirePAC;
}
Debug.Log("ViveAnchor: AcquirePersistedAnchorCollectionAsync");
var ret = feature.AcquirePersistedAnchorCollectionAsync(out IntPtr future);
if (ret != XrResult.XR_SUCCESS)
{
Debug.LogError("AcquirePersistedAnchorCollection failed: " + ret);
return FutureTask<(XrResult, IntPtr)>.FromResult((ret, IntPtr.Zero));
}
else
{
var task = new FutureTask<(XrResult, IntPtr)>(future, CompletePAC, 10, autoComplete: true);
lock (asyncLock)
{
taskAcquirePAC = task;
}
return task;
}
}
/// <summary>
/// Check if the persisted anchor collection is acquired.
/// </summary>
/// <returns>True if the persisted anchor collection is acquired, false otherwise.</returns>
public static bool IsPersistedAnchorCollectionAcquired()
{
return persistedAnchorCollection != System.IntPtr.Zero;
}
/// <summary>
/// Call this function when no any persisted anchor operations are needed.
/// Destroy the persisted anchor collection. If task is running, the task will be canceled.
/// </summary>
public static void ReleasePersistedAnchorCollection()
{
IntPtr tmp;
if (taskAcquirePAC != null)
{
taskAcquirePAC.Cancel();
taskAcquirePAC.Dispose();
taskAcquirePAC = null;
}
lock (asyncLock)
{
if (persistedAnchorCollection == System.IntPtr.Zero) return;
tmp = persistedAnchorCollection;
persistedAnchorCollection = System.IntPtr.Zero;
}
EnsureFeature();
Task.Run(async () =>
{
Debug.Log("ViveAnchor: ReleasePersistedAnchorCollection task is started.");
await semaphoreSlim.WaitAsync();
try
{
feature?.ReleasePersistedAnchorCollection(tmp);
}
finally
{
semaphoreSlim.Release();
}
Debug.Log("ViveAnchor: ReleasePersistedAnchorCollection task is done.");
});
}
private static XrResult CompletePA(IntPtr future) {
Debug.Log("AnchorManager: CompletePA");
var ret = feature.PersistSpatialAnchorComplete(future, out var completion);
if (ret == XrResult.XR_SUCCESS)
{
return completion.futureResult;
}
else
{
Debug.LogError("AcquirePersistedAnchorCollection failed: " + ret);
}
return ret;
}
/// <summary>
/// Persist an anchor with the given name. The persistanceAnchorName should be unique.
/// The persistance might fail if the anchor is not trackable. Check the result from the task.
/// </summary>
/// <param name="anchor">The anchor instance.</param>
/// <param name="persistanceAnchorName">The name of the persisted anchor.</param>
/// <param name="cts">PersistAnchor may take time. If you want to cancel it, use cts.</param>
/// <returns>The task to get persisted anchor's result.</returns>
public static FutureTask<XrResult> PersistAnchor(Anchor anchor, string persistanceAnchorName)
{
EnsureFeature();
EnsureCollection();
if (string.IsNullOrEmpty(persistanceAnchorName))
throw new ArgumentException("The persistanceAnchorName should not be empty.");
var name = new XrSpatialAnchorNameHTC(persistanceAnchorName);
var ret = feature.PersistSpatialAnchorAsync(persistedAnchorCollection, anchor.GetXrSpace(), name, out IntPtr future);
if (ret == XrResult.XR_SUCCESS)
{
// If no auto complete, you can cancel the task and no need to free resouce.
// Once it completed, you need handle the result.
return new FutureTask<XrResult>(future, CompletePA, 10, autoComplete: false);
}
return FutureTask<XrResult>.FromResult(ret);
}
/// <summary>
/// Unpersist the anchor by the name. The anchor created from persisted anchor will still be trackable.
/// </summary>
/// <param name="persistanceAnchorName">The name of the persisted anchor to be removed.</param>
/// <returns>The result of the operation.</returns>
public static XrResult UnpersistAnchor(string persistanceAnchorName)
{
EnsureFeature();
EnsureCollection();
if (string.IsNullOrEmpty(persistanceAnchorName))
throw new ArgumentException("The persistanceAnchorName should not be empty.");
var name = new XrSpatialAnchorNameHTC(persistanceAnchorName);
var ret = feature.UnpersistSpatialAnchor(persistedAnchorCollection, name);
return ret;
}
/// <summary>
/// Get the number of persisted anchors.
/// </summary>
/// <param name="count">Output parameter to hold the number of persisted anchors.</param>
/// <returns>The result of the operation.</returns>
public static XrResult GetNumberOfPersistedAnchors(out int count)
{
EnsureFeature();
EnsureCollection();
XrSpatialAnchorNameHTC[] xrNames = null;
uint xrCount = 0;
XrResult ret = feature.EnumeratePersistedAnchorNames(persistedAnchorCollection, 0, ref xrCount, ref xrNames);
if (ret != XrResult.XR_SUCCESS)
count = 0;
else
count = (int)xrCount;
return ret;
}
/// <summary>
/// List all persisted anchors.
/// </summary>
/// <param name="names">Output parameter to hold the names of the persisted anchors.</param>
/// <returns>The result of the operation.</returns>
public static XrResult EnumeratePersistedAnchorNames(out string[] names)
{
EnsureFeature();
EnsureCollection();
XrSpatialAnchorNameHTC[] xrNames = null;
uint countOut = 0;
uint countIn = 0;
XrResult ret = feature.EnumeratePersistedAnchorNames(persistedAnchorCollection, countIn, ref countOut, ref xrNames);
if (ret != XrResult.XR_SUCCESS)
{
names = null;
return ret;
}
// If Insufficient size, try again.
do
{
countIn = countOut;
xrNames = new XrSpatialAnchorNameHTC[countIn];
ret = feature.EnumeratePersistedAnchorNames(persistedAnchorCollection, countIn, ref countOut, ref xrNames);
}
while (ret == XrResult.XR_ERROR_SIZE_INSUFFICIENT);
if (ret != XrResult.XR_SUCCESS)
{
names = null;
return ret;
}
names = new string[countIn];
for (int i = 0; i < countIn; i++)
{
string v = xrNames[i].ToString();
names[i] = v;
}
return ret;
}
private static (XrResult, Anchor) CompleteCreateSAfromPA(IntPtr future)
{
Debug.Log("AnchorManager: CompleteCreateSAfromPA");
var ret = feature.CreateSpatialAnchorFromPersistedAnchorComplete(future, out var completion);
if (ret == XrResult.XR_SUCCESS)
{
var anchor = new Anchor(completion.anchor);
anchor.isTrackable = true;
return (completion.futureResult, anchor);
}
else
{
Debug.LogError("CreateSpatialAnchorFromPersistedAnchor failed: " + ret);
return (ret, new Anchor(0));
}
}
/// <summary>
/// Create a spatial anchor from a persisted anchor. This will also mark the anchor as trackable.
/// </summary>
/// <param name="persistanceAnchorName">The name of the persisted anchor.</param>
/// <param name="spatialAnchorName">The name of the new spatial anchor.</param>
/// <param name="anchor">Output parameter to hold the new anchor instance.</param>
/// <returns>The result of the operation.</returns>
public static FutureTask<(XrResult, Anchor)> CreateSpatialAnchorFromPersistedAnchor(string persistanceAnchorName, string spatialAnchorName)
{
EnsureFeature();
EnsureCollection();
Debug.Log("AnchorManager: CreateSpatialAnchorFromPersistedAnchor: " + persistanceAnchorName + " -> " + spatialAnchorName);
if (string.IsNullOrEmpty(persistanceAnchorName) || string.IsNullOrEmpty(spatialAnchorName))
throw new ArgumentException("The persistanceAnchorName and spatialAnchorName should not be empty.");
var createInfo = new XrSpatialAnchorFromPersistedAnchorCreateInfoHTC() {
type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_INFO_HTC,
persistedAnchorCollection = persistedAnchorCollection,
persistedAnchorName = new XrSpatialAnchorNameHTC(persistanceAnchorName),
spatialAnchorName = new XrSpatialAnchorNameHTC(spatialAnchorName)
};
var ret = feature.CreateSpatialAnchorFromPersistedAnchorAsync(createInfo, out var future);
if (ret == XrResult.XR_SUCCESS)
{
// If no auto complete, you can cancel the task and no need to free resouce.
// Once it completed, you need handle the result.
return new FutureTask<(XrResult, Anchor)>(future, CompleteCreateSAfromPA, 10, autoComplete: false);
}
else
{
return FutureTask<(XrResult, Anchor)>.FromResult((ret, new Anchor(0)));
}
}
/// <summary>
/// Clear all persisted anchors. Those anchors created from or to the persisted anchor will still be trackable.
/// </summary>
/// <returns>The result of the operation.</returns>
public static XrResult ClearPersistedAnchors()
{
EnsureFeature();
EnsureCollection();
return feature.ClearPersistedAnchors(persistedAnchorCollection);
}
/// <summary>
/// Get the properties of the persisted anchor.
/// maxPersistedAnchorCount in XrPersistedAnchorPropertiesGetInfoHTC will be set to the max count of the persisted anchor.
/// </summary>
/// <param name="properties">Output parameter to hold the properties of the persisted anchor.</param>
/// <returns>The result of the operation.</returns>
public static XrResult GetPersistedAnchorProperties(out XrPersistedAnchorPropertiesGetInfoHTC properties)
{
EnsureFeature();
EnsureCollection();
return feature.GetPersistedAnchorProperties(persistedAnchorCollection, out properties);
}
/// <summary>
/// Export the persisted anchor to a buffer. The buffer can be used to import the anchor later or save it to a file.
/// Export takes time, so it is an async function. The buffer will be null if the export failed.
/// </summary>
/// <param name="persistanceAnchorName">The name of the persisted anchor to be exported.</param>
/// <param name="buffer">Output parameter to hold the buffer containing the exported anchor.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task<(XrResult, string, byte[])> ExportPersistedAnchor(string persistanceAnchorName)
{
EnsureFeature();
EnsureCollection();
if (string.IsNullOrEmpty(persistanceAnchorName))
return Task.FromResult<(XrResult, string, byte[])>((XrResult.XR_ERROR_HANDLE_INVALID, "", null));
var name = new XrSpatialAnchorNameHTC(persistanceAnchorName);
return Task.Run(async () =>
{
Debug.Log($"ExportPersistedAnchor({persistanceAnchorName}) task is started.");
XrResult ret = XrResult.XR_ERROR_VALIDATION_FAILURE;
await semaphoreSlim.WaitAsync();
try
{
lock (asyncLock)
{
if (persistedAnchorCollection == System.IntPtr.Zero)
{
return (XrResult.XR_ERROR_HANDLE_INVALID, "", null);
}
}
ret = feature.ExportPersistedAnchor(persistedAnchorCollection, name, out var buffer);
Debug.Log($"ExportPersistedAnchor({persistanceAnchorName}) task is done. ret=" + ret);
lock (asyncLock)
{
if (ret != XrResult.XR_SUCCESS)
{
buffer = null;
return (ret, "", null);
}
return (ret, persistanceAnchorName, buffer);
}
}
finally
{
semaphoreSlim.Release();
}
});
}
/// <summary>
/// Import the persisted anchor from a buffer. The buffer should be created by ExportPersistedAnchor.
/// Import takes time, so it is an async function. Check imported anchor by EnumeratePersistedAnchorNames.
/// </summary>
/// <param name="buffer">The buffer containing the persisted anchor data.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task<XrResult> ImportPersistedAnchor(byte[] buffer) {
EnsureFeature();
EnsureCollection();
return Task.Run(async () =>
{
Debug.Log($"ImportPersistedAnchor task is started.");
XrResult ret = XrResult.XR_ERROR_VALIDATION_FAILURE;
await semaphoreSlim.WaitAsync();
try
{
lock (asyncLock)
{
if (persistedAnchorCollection == System.IntPtr.Zero)
return XrResult.XR_ERROR_HANDLE_INVALID;
ret = feature.ImportPersistedAnchor(persistedAnchorCollection, buffer);
return ret;
}
}
finally
{
semaphoreSlim.Release();
Debug.Log($"ImportPersistedAnchor task is done. ret=" + ret);
}
});
}
/// <summary>
/// Get the persisted anchor name from the buffer. The buffer should be created by ExportPersistedAnchor.
/// </summary>
/// <returns>True if the name is successfully retrieved, false otherwise.</returns>
public static bool GetPersistedAnchorNameFromBuffer(byte[] buffer, out string name)
{
EnsureFeature();
EnsureCollection();
var ret = feature.GetPersistedAnchorNameFromBuffer(persistedAnchorCollection, buffer, out var xrName);
if (ret == XrResult.XR_SUCCESS)
name = xrName.ToString();
else
name = "";
return ret == XrResult.XR_SUCCESS;
}
/// <summary>
/// Anchor is a named Space. It can be used to create a spatial anchor, or get the anchor's name.
/// After use, you should call Dispose() to release the anchor.
/// IsTrackable is true if the anchor is created persisted anchor or created from persisted anchor.
/// IsPersisted is true if the anchor is ever persisted.
/// </summary>
public class Anchor : VIVE.OpenXR.Feature.Space
{
/// <summary>
/// The anchor's name
/// </summary>
string name;
/// <summary>
/// The anchor's name
/// </summary>
public string Name
{
get
{
if (string.IsNullOrEmpty(name))
name = GetSpatialAnchorName();
return name;
}
}
internal bool isTrackable = false;
/// <summary>
/// If the anchor is created persisted anchor or created from persisted anchor, it will be trackable.
/// </summary>
public bool IsTrackable => isTrackable;
internal bool isPersisted = false;
/// <summary>
/// If the anchor is ever persisted, it will be true.
/// </summary>
public bool IsPersisted => isPersisted;
internal Anchor(XrSpace anchor, string name) : base(anchor)
{
Debug.Log($"Anchor: new Anchor({anchor}, {name})"); // Remove this line later.
// Get the current tracking space.
this.name = name;
}
internal Anchor(XrSpace anchor) : base(anchor)
{
Debug.Log($"Anchor: new Anchor({anchor})"); // Remove this line later.
// Get the current tracking space.
name = GetSpatialAnchorName();
}
internal Anchor(Anchor other) : base(other.space)
{
// Get the current tracking space.
name = other.name;
isTrackable = other.isTrackable;
isPersisted = other.isPersisted;
}
/// <summary>
/// Get the anchor's name by using this anchor's handle, instead of the anchor's Name. This will update the anchor's Name.
/// </summary>
/// <returns>Anchor's name. Always return non null string.</returns>
public string GetSpatialAnchorName()
{
if (space == 0)
{
Debug.LogError("Anchor: GetSpatialAnchorName: The anchor is invalid.");
return "";
}
AnchorManager.EnsureFeature();
if (AnchorManager.GetSpatialAnchorName(this, out string name))
return name;
Debug.LogError("Anchor: GetSpatialAnchorName: Failed to get Anchor name.");
return "";
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5dd060e5d33dc942adbd03d6b9dc9bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5544ca439cd389c4ba5255504ce221a4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,885 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.XR.OpenXR;
using VIVE.OpenXR.Hand;
#if UNITY_XR_HANDS
using UnityEngine.XR.Hands;
using UnityEngine.XR.Hands.OpenXR;
#endif
namespace VIVE.OpenXR.Toolkits.Common
{
public enum DeviceCategory
{
None = 0,
HMD = 1,
CenterEye = 2,
LeftController = 3,
RightController = 4,
LeftHand = 5,
RightHand = 6,
Tracker0 = 7,
Tracker1 = 8,
Tracker2 = 9,
Tracker3 = 10,
Tracker4 = 11,
}
public enum PoseState
{
None = 0,
IsTracked = 1,
Position = 2,
Rotation = 3,
Velocity = 4,
AngularVelocity = 5,
Acceleration = 6,
AngularAcceleration = 7,
}
public enum Handedness
{
None = -1,
Right = 0,
Left = 1,
}
public enum HandEvent
{
None = 0,
PinchValue = 0x00000001,
PinchPose = 0x00000002,
GraspValue = 0x00000010,
GraspPose = 0x00000020,
}
public enum ButtonEvent
{
None = 0,
GripValue = 0x00000001,
GripPress = 0x00000002,
TriggerValue = 0x00000010,
TriggerTouch = 0x00000020,
TriggerPress = 0x00000040,
Primary2DAxisValue = 0x00000100,
Primary2DAxisTouch = 0x00000200,
Primary2DAxisPress = 0x00000400,
Secondary2DAxisValue = 0x00001000,
Secondary2DAxisTouch = 0x00002000,
Secondary2DAxisPress = 0x00004000,
PrimaryButton = 0x00010000,
SecondaryButton = 0x00020000,
ParkingTouch = 0x00100000,
Menu = 0x01000000,
}
public enum HandJointType : Int32
{
Palm = XrHandJointEXT.XR_HAND_JOINT_PALM_EXT,
Wrist = XrHandJointEXT.XR_HAND_JOINT_WRIST_EXT,
Thumb_Joint0 = XrHandJointEXT.XR_HAND_JOINT_THUMB_METACARPAL_EXT,
Thumb_Joint1 = XrHandJointEXT.XR_HAND_JOINT_THUMB_PROXIMAL_EXT,
Thumb_Joint2 = XrHandJointEXT.XR_HAND_JOINT_THUMB_DISTAL_EXT,
Thumb_Tip = XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT,
Index_Joint0 = XrHandJointEXT.XR_HAND_JOINT_INDEX_METACARPAL_EXT,
Index_Joint1 = XrHandJointEXT.XR_HAND_JOINT_INDEX_PROXIMAL_EXT,
Index_Joint2 = XrHandJointEXT.XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT,
Index_Joint3 = XrHandJointEXT.XR_HAND_JOINT_INDEX_DISTAL_EXT,
Index_Tip = XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT,
Middle_Joint0 = XrHandJointEXT.XR_HAND_JOINT_MIDDLE_METACARPAL_EXT,
Middle_Joint1 = XrHandJointEXT.XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT,
Middle_Joint2 = XrHandJointEXT.XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT,
Middle_Joint3 = XrHandJointEXT.XR_HAND_JOINT_MIDDLE_DISTAL_EXT,
Middle_Tip = XrHandJointEXT.XR_HAND_JOINT_MIDDLE_TIP_EXT,
Ring_Joint0 = XrHandJointEXT.XR_HAND_JOINT_RING_METACARPAL_EXT,
Ring_Joint1 = XrHandJointEXT.XR_HAND_JOINT_RING_PROXIMAL_EXT,
Ring_Joint2 = XrHandJointEXT.XR_HAND_JOINT_RING_INTERMEDIATE_EXT,
Ring_Joint3 = XrHandJointEXT.XR_HAND_JOINT_RING_DISTAL_EXT,
Ring_Tip = XrHandJointEXT.XR_HAND_JOINT_RING_TIP_EXT,
Pinky_Joint0 = XrHandJointEXT.XR_HAND_JOINT_LITTLE_METACARPAL_EXT,
Pinky_Joint1 = XrHandJointEXT.XR_HAND_JOINT_LITTLE_PROXIMAL_EXT,
Pinky_Joint2 = XrHandJointEXT.XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT,
Pinky_Joint3 = XrHandJointEXT.XR_HAND_JOINT_LITTLE_DISTAL_EXT,
Pinky_Tip = XrHandJointEXT.XR_HAND_JOINT_LITTLE_TIP_EXT,
Count = XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT,
}
public static class VIVEInput
{
private const string kFloatType = "float";
private const string kVector2Type = "Vector2";
private const string kVector3Type = "Vector3";
private const string kQuaternionType = "Quaternion";
private const string kPoseType = "Pose";
private struct InputActionMapping
{
public DeviceCategory device;
public PoseState poseState;
public ButtonEvent buttonEvent;
public HandEvent handEvent;
public InputAction inputAction { get; private set; }
public InputActionMapping(string in_BindingPath, DeviceCategory in_Device,
PoseState in_PoseState = PoseState.None,
ButtonEvent in_ButtonEvent = ButtonEvent.None,
HandEvent in_HandEvent = HandEvent.None,
string in_Type = "")
{
inputAction = new InputAction(binding: in_BindingPath, expectedControlType: in_Type);
inputAction.Enable();
this.device = in_Device;
this.poseState = in_PoseState;
this.buttonEvent = in_ButtonEvent;
this.handEvent = in_HandEvent;
}
public static InputActionMapping Identify => new InputActionMapping("", DeviceCategory.None);
public override bool Equals(object obj)
{
return obj is InputActionMapping inputActionMapping &&
device == inputActionMapping.device &&
poseState == inputActionMapping.poseState &&
buttonEvent == inputActionMapping.buttonEvent &&
handEvent == inputActionMapping.handEvent &&
inputAction == inputActionMapping.inputAction;
}
public override int GetHashCode()
{
return device.GetHashCode() ^ poseState.GetHashCode() ^ buttonEvent.GetHashCode() ^ handEvent.GetHashCode() ^ inputAction.GetHashCode();
}
public static bool operator ==(InputActionMapping source, InputActionMapping target) => source.Equals(target);
public static bool operator !=(InputActionMapping source, InputActionMapping target) => !(source == (target));
}
private struct JointData
{
public bool isValid { get; private set; }
public Vector3 position { get; private set; }
public Quaternion rotation { get; private set; }
public JointData(bool in_IsValid, Vector3 in_Position, Quaternion in_Rotation)
{
this.isValid = in_IsValid;
this.position = in_Position;
this.rotation = in_Rotation;
}
public static JointData Identify => new JointData(false, Vector3.zero, Quaternion.identity);
}
private struct HandData
{
public bool isTracked { get; private set; }
public int updateTime { get; private set; }
public JointData[] joints { get; private set; }
private JointData[] jointBuffer;
public HandData(JointData[] in_Joints)
{
jointBuffer = new JointData[(int)HandJointType.Count];
for (int i = 0; i < in_Joints.Length; i++)
{
jointBuffer[i] = in_Joints[i];
}
this.joints = jointBuffer;
isTracked = true;
for (int i = 0; i < this.joints.Length; i++)
{
if (!this.joints[i].isValid)
{
isTracked = false;
break;
}
}
updateTime = Time.frameCount;
}
public void Update(JointData[] in_Joints)
{
for (int i = 0; i < in_Joints.Length; i++)
{
jointBuffer[i] = in_Joints[i];
}
this.joints = jointBuffer;
isTracked = true;
for (int i = 0; i < this.joints.Length; i++)
{
if (!this.joints[i].isValid)
{
isTracked = false;
break;
}
}
updateTime = Time.frameCount;
}
public static HandData Identify
{
get
{
JointData[] newJoints = new JointData[(int)HandJointType.Count];
for (int i = 0; i < newJoints.Length; i++)
{
newJoints[i] = JointData.Identify;
}
return new HandData(newJoints);
}
}
}
private static bool m_IsInitInputActions = false;
private static bool m_IsSupportViveHand = false;
private static bool m_IsSupportXrHand = false;
private static List<InputActionMapping> s_InputActions = new List<InputActionMapping>();
private static HandData m_LeftHand = HandData.Identify;
private static HandData m_RightHand = HandData.Identify;
private static JointData[] m_JointBuffer = new JointData[(int)HandJointType.Count];
#if UNITY_XR_HANDS
private static XRHandSubsystem m_HandSubsystem = null;
private static List<XRHandSubsystem> m_HandSubsystems = new List<XRHandSubsystem>();
#endif
#region Public Interface
/// <summary>
/// Get the pose state of the specified device.
/// </summary>
/// <param name="device">The device category.</param>
/// <param name="poseState">The pose state to be retrieved.</param>
/// <param name="eventResult">The result of the event.</param>
/// <returns>True if the pose state was successfully retrieved; otherwise, false.</returns>
public static bool GetPoseState(DeviceCategory device, PoseState poseState, out bool eventResult)
{
CheckInitialize();
eventResult = false;
if ((device == DeviceCategory.LeftHand || device == DeviceCategory.RightHand) && poseState == PoseState.IsTracked)
{
eventResult = IsHandTracked(GetHandedness(device));
return true;
}
else
{
if (GetInputActionMapping(device, poseState, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kFloatType)
{
eventResult = inputActionMapping.inputAction.ReadValue<float>() > 0;
return true;
}
}
return false;
}
}
/// <summary>
/// Get the pose state of the specified device.
/// </summary>
/// <param name="device">The device category.</param>
/// <param name="poseState">The pose state to be retrieved.</param>
/// <param name="eventResult">The result of the event.</param>
/// <returns>True if the pose state was successfully retrieved; otherwise, false.</returns>
public static bool GetPoseState(DeviceCategory device, PoseState poseState, out Vector3 eventResult)
{
CheckInitialize();
eventResult = Vector3.zero;
if ((device == DeviceCategory.LeftHand || device == DeviceCategory.RightHand) && poseState == PoseState.Position)
{
GetJointPose(GetHandedness(device), HandJointType.Wrist, out Pose jointPose);
eventResult = jointPose.position;
return true;
}
else
{
if (GetInputActionMapping(device, poseState, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kVector3Type)
{
eventResult = inputActionMapping.inputAction.ReadValue<Vector3>();
return true;
}
}
return false;
}
}
/// <summary>
/// Get the pose state of the specified device.
/// </summary>
/// <param name="device">The device category.</param>
/// <param name="poseState">The pose state to be retrieved.</param>
/// <param name="eventResult">The result of the event.</param>
/// <returns>True if the pose state was successfully retrieved; otherwise, false.</returns>
public static bool GetPoseState(DeviceCategory device, PoseState poseState, out Quaternion eventResult)
{
CheckInitialize();
eventResult = Quaternion.identity;
if ((device == DeviceCategory.LeftHand || device == DeviceCategory.RightHand) && poseState == PoseState.Rotation)
{
GetJointPose(GetHandedness(device), HandJointType.Wrist, out Pose jointPose);
eventResult = jointPose.rotation;
return true;
}
else
{
if (GetInputActionMapping(device, poseState, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kQuaternionType)
{
eventResult = inputActionMapping.inputAction.ReadValue<Quaternion>();
return true;
}
}
return false;
}
}
/// <summary>
/// Check if a specified button event has toggled at this frame and return the result.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to check the button event for.</param>
/// <param name="buttonEvent">The specified button event to check.</param>
/// <param name="eventResult">Output whether the button event has toggled.</param>
/// <returns>Returns true if the button event was successfully retrieved, otherwise false.</returns>
public static bool GetButtonDown(Handedness handedness, ButtonEvent buttonEvent, out bool eventResult)
{
CheckInitialize();
eventResult = false;
if (GetInputActionMapping(GetController(handedness), buttonEvent, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kFloatType)
{
eventResult = inputActionMapping.inputAction.WasPressedThisFrame();
return true;
}
}
return false;
}
/// <summary>
/// Check if a specified button event has toggled at this frame and return the result.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to check the button event for.</param>
/// <param name="buttonEvent">The specified button event to check.</param>
/// <param name="eventResult">Output whether the button event has toggled.</param>
/// <returns>Returns true if the button event was successfully retrieved, otherwise false.</returns>
public static bool GetButtonUp(Handedness handedness, ButtonEvent buttonEvent, out bool eventResult)
{
CheckInitialize();
eventResult = false;
if (GetInputActionMapping(GetController(handedness), buttonEvent, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kFloatType)
{
eventResult = inputActionMapping.inputAction.WasReleasedThisFrame();
return true;
}
}
return false;
}
/// <summary>
/// Check if a specified button event has toggled and return the result.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to check the button event for.</param>
/// <param name="buttonEvent">The specified button event to check.</param>
/// <param name="eventResult">Output for the button event.</param>
/// <returns>Returns true if the button event was successfully retrieved, otherwise false.</returns>
public static bool GetButtonValue(Handedness handedness, ButtonEvent buttonEvent, out bool eventResult)
{
CheckInitialize();
eventResult = false;
if (GetInputActionMapping(GetController(handedness), buttonEvent, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kFloatType)
{
eventResult = inputActionMapping.inputAction.ReadValue<float>() == 1;
return true;
}
}
return false;
}
/// <summary>
/// Check if a specified button event has toggled and return the result.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to check the button event for.</param>
/// <param name="buttonEvent">The specified button event to check.</param>
/// <param name="eventResult">Output for the button event.</param>
/// <returns>Returns true if the button event was successfully retrieved, otherwise false.</returns>
public static bool GetButtonValue(Handedness handedness, ButtonEvent buttonEvent, out float eventResult)
{
CheckInitialize();
eventResult = 0f;
if (GetInputActionMapping(GetController(handedness), buttonEvent, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kFloatType)
{
eventResult = inputActionMapping.inputAction.ReadValue<float>();
return true;
}
}
return false;
}
/// <summary>
/// Check if a specified button event has toggled and return the result.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to check the button event for.</param>
/// <param name="buttonEvent">The specified button event to check.</param>
/// <param name="eventResult">Output for the button event.</param>
/// <returns>Returns true if the button event was successfully retrieved, otherwise false.</returns>
public static bool GetButtonValue(Handedness handedness, ButtonEvent buttonEvent, out Vector2 eventResult)
{
CheckInitialize();
eventResult = Vector2.zero;
if (GetInputActionMapping(GetController(handedness), buttonEvent, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kVector2Type)
{
eventResult = inputActionMapping.inputAction.ReadValue<Vector2>();
return true;
}
}
return false;
}
/// <summary>
/// Check if a specified hand event has toggled and return the result.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to check the hand event for.</param>
/// <param name="handEvent">The specified hand event to check.</param>
/// <param name="eventResult">Output for the hand event.</param>
/// <returns>Returns true if the hand event was successfully retrieved, otherwise false.</returns>
public static bool GetHandValue(Handedness handedness, HandEvent handEvent, out float eventResult)
{
CheckInitialize();
eventResult = 0;
if (GetInputActionMapping(GetHand(handedness), handEvent, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kFloatType)
{
eventResult = inputActionMapping.inputAction.ReadValue<float>();
return true;
}
}
return false;
}
/// <summary>
/// Check if a specified hand event has toggled and return the result.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to check the hand event for.</param>
/// <param name="handEvent">The specified hand event to check.</param>
/// <param name="eventResult">Output for the hand event.</param>
/// <returns>Returns true if the hand event was successfully retrieved, otherwise false.</returns>
public static bool GetHandValue(Handedness handedness, HandEvent handEvent, out Pose eventResult)
{
CheckInitialize();
eventResult = Pose.identity;
if (GetInputActionMapping(GetHand(handedness), handEvent, out InputActionMapping inputActionMapping))
{
var inputAction = inputActionMapping.inputAction;
if (inputAction != null && inputAction.enabled && inputAction.expectedControlType == kPoseType)
{
# if USE_INPUT_SYSTEM_POSE_CONTROL
UnityEngine.InputSystem.XR.PoseState pose = inputActionMapping.inputAction.ReadValue<UnityEngine.InputSystem.XR.PoseState>();
#else
UnityEngine.XR.OpenXR.Input.Pose pose = inputActionMapping.inputAction.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>();
#endif
eventResult = new Pose(pose.position, pose.rotation);
return true;
}
}
return false;
}
/// <summary>
/// Retrieves the pose of a specified hand joint for the given handedness.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to get the joint pose for.</param>
/// <param name="joint">The specific hand joint to retrieve the pose of.</param>
/// <param name="jointPose">Outputs the pose of the specified hand joint.</param>
/// <returns>Returns true if the joint pose was successfully retrieved, otherwise false.</returns>
public static bool GetJointPose(Handedness handedness, HandJointType joint, out Pose jointPose)
{
CheckHandUpdated();
jointPose = Pose.identity;
if (handedness == Handedness.Left)
{
jointPose = new Pose(m_LeftHand.joints[(int)joint].position, m_LeftHand.joints[(int)joint].rotation);
return m_LeftHand.joints[(int)joint].isValid;
}
else
{
jointPose = new Pose(m_RightHand.joints[(int)joint].position, m_RightHand.joints[(int)joint].rotation);
return m_RightHand.joints[(int)joint].isValid;
}
}
/// <summary>
/// Determines if the specified hand is currently being tracked.
/// </summary>
/// <param name="handedness">The handedness (left or right hand) to check for tracking.</param>
/// <returns>Returns true if the specified hand is being tracked, otherwise false.</returns>
public static bool IsHandTracked(Handedness handedness)
{
CheckHandUpdated();
return handedness == Handedness.Left ? m_LeftHand.isTracked : m_RightHand.isTracked;
}
public static bool IsHandValidate()
{
if (!m_IsInitInputActions)
{
ViveHandTracking viveHand = OpenXRSettings.Instance.GetFeature<ViveHandTracking>();
if (viveHand)
{
m_IsSupportViveHand = true;
}
#if UNITY_XR_HANDS
HandTracking xrHand = OpenXRSettings.Instance.GetFeature<HandTracking>();
if (xrHand)
{
m_IsSupportXrHand = true;
}
#endif
}
return m_IsSupportViveHand || m_IsSupportXrHand;
}
#endregion
[RuntimeInitializeOnLoadMethod]
private static bool CheckInitialize()
{
if (!m_IsInitInputActions)
{
Initialized();
IsHandValidate();
m_IsInitInputActions = true;
}
return m_IsInitInputActions;
}
private static void Initialized()
{
#region Head
s_InputActions.Add(new InputActionMapping("<XRHMD>/isTracked", DeviceCategory.HMD, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRHMD>/centerEyePosition", DeviceCategory.HMD, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRHMD>/centerEyeRotation", DeviceCategory.HMD, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<XRHMD>/centerEyeVelocity", DeviceCategory.HMD, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRHMD>/centerEyeAngularVelocity", DeviceCategory.HMD, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRHMD>/centerEyeAcceleration", DeviceCategory.HMD, in_PoseState: PoseState.Acceleration, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRHMD>/centerEyeAngularAcceleration", DeviceCategory.HMD, in_PoseState: PoseState.AngularAcceleration, in_Type: kVector3Type));
#endregion
#region Eye
s_InputActions.Add(new InputActionMapping("<EyeGaze>/pose/isTracked", DeviceCategory.CenterEye, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<EyeGaze>/pose/position", DeviceCategory.CenterEye, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<EyeGaze>/pose/rotation", DeviceCategory.CenterEye, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<EyeGaze>/pose/velocity", DeviceCategory.CenterEye, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<EyeGaze>/pose/angularVelocity", DeviceCategory.CenterEye, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
#endregion
#region Controller
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/isTracked", DeviceCategory.LeftController, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/pointerPosition", DeviceCategory.LeftController, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/pointerRotation", DeviceCategory.LeftController, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/deviceVelocity", DeviceCategory.LeftController, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/deviceAngularVelocity", DeviceCategory.LeftController, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/deviceAcceleration", DeviceCategory.LeftController, in_PoseState: PoseState.Acceleration, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/deviceAngularAcceleration", DeviceCategory.LeftController, in_PoseState: PoseState.AngularAcceleration, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{grip}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.GripValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{gripButton}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.GripPress, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{trigger}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.TriggerValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/triggerTouched", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.TriggerTouch, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{triggerButton}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.TriggerPress, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{primary2DAxis}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.Primary2DAxisValue, in_Type: kVector2Type));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{primary2DAxisTouch}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.Primary2DAxisTouch, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{primary2DAxisClick}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.Primary2DAxisPress, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{secondary2DAxis}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.Secondary2DAxisValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{secondary2DAxisTouch}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.Secondary2DAxisTouch, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{secondary2DAxisClick}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.Secondary2DAxisPress, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{primaryButton}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.PrimaryButton, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{secondaryButton}", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.SecondaryButton, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/parkingTouched", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.ParkingTouch, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{LeftHand}/menu", DeviceCategory.LeftController, in_ButtonEvent: ButtonEvent.Menu, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/isTracked", DeviceCategory.RightController, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/pointerPosition", DeviceCategory.RightController, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/pointerRotation", DeviceCategory.RightController, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/deviceVelocity", DeviceCategory.RightController, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/deviceAngularVelocity", DeviceCategory.RightController, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/deviceAcceleration", DeviceCategory.RightController, in_PoseState: PoseState.Acceleration, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/deviceAngularAcceleration", DeviceCategory.RightController, in_PoseState: PoseState.AngularAcceleration, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{grip}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.GripValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{gripButton}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.GripPress, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{trigger}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.TriggerValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/triggerTouched", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.TriggerTouch, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{triggerButton}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.TriggerPress, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{primary2DAxis}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.Primary2DAxisValue, in_Type: kVector2Type));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{primary2DAxisTouch}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.Primary2DAxisTouch, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{primary2DAxisClick}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.Primary2DAxisPress, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{secondary2DAxis}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.Secondary2DAxisValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{secondary2DAxisTouch}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.Secondary2DAxisTouch, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{secondary2DAxisClick}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.Secondary2DAxisPress, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{primaryButton}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.PrimaryButton, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/{secondaryButton}", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.SecondaryButton, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<XRController>{RightHand}/parkingTouched", DeviceCategory.RightController, in_ButtonEvent: ButtonEvent.ParkingTouch, in_Type: kFloatType));
#endregion
#region Hand
s_InputActions.Add(new InputActionMapping("<ViveHandInteraction>{LeftHand}/selectValue", DeviceCategory.LeftHand, in_HandEvent: HandEvent.PinchValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveHandInteraction>{LeftHand}/pointerPose", DeviceCategory.LeftHand, in_HandEvent: HandEvent.PinchPose, in_Type: kPoseType));
s_InputActions.Add(new InputActionMapping("<ViveHandInteraction>{LeftHand}/gripValue", DeviceCategory.LeftHand, in_HandEvent: HandEvent.GraspValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveHandInteraction>{LeftHand}/devicePose", DeviceCategory.LeftHand, in_HandEvent: HandEvent.GraspPose, in_Type: kPoseType));
s_InputActions.Add(new InputActionMapping("<ViveHandInteraction>{RightHand}/selectValue", DeviceCategory.RightHand, in_HandEvent: HandEvent.PinchValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveHandInteraction>{RightHand}/pointerPose", DeviceCategory.RightHand, in_HandEvent: HandEvent.PinchPose, in_Type: kPoseType));
s_InputActions.Add(new InputActionMapping("<ViveHandInteraction>{RightHand}/gripValue", DeviceCategory.RightHand, in_HandEvent: HandEvent.GraspValue, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveHandInteraction>{RightHand}/devicePose", DeviceCategory.RightHand, in_HandEvent: HandEvent.GraspPose, in_Type: kPoseType));
#endregion
#region Tracker
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/devicePose/isTracked", DeviceCategory.Tracker0, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/devicePosition", DeviceCategory.Tracker0, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/deviceRotation", DeviceCategory.Tracker0, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/devicePose/velocity", DeviceCategory.Tracker0, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/devicePose/angularVelocity", DeviceCategory.Tracker0, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/devicePose/isTracked", DeviceCategory.Tracker1, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/devicePosition", DeviceCategory.Tracker1, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/deviceRotation", DeviceCategory.Tracker1, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/devicePose/velocity", DeviceCategory.Tracker1, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/devicePose/angularVelocity", DeviceCategory.Tracker1, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/devicePose/isTracked", DeviceCategory.Tracker2, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/devicePosition", DeviceCategory.Tracker2, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/deviceRotation", DeviceCategory.Tracker2, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/devicePose/velocity", DeviceCategory.Tracker2, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/devicePose/angularVelocity", DeviceCategory.Tracker2, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/devicePose/isTracked", DeviceCategory.Tracker3, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/devicePosition", DeviceCategory.Tracker3, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/deviceRotation", DeviceCategory.Tracker3, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/devicePose/velocity", DeviceCategory.Tracker3, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/devicePose/angularVelocity", DeviceCategory.Tracker3, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/devicePose/isTracked", DeviceCategory.Tracker4, in_PoseState: PoseState.IsTracked, in_Type: kFloatType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/devicePosition", DeviceCategory.Tracker4, in_PoseState: PoseState.Position, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/deviceRotation", DeviceCategory.Tracker4, in_PoseState: PoseState.Rotation, in_Type: kQuaternionType));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/devicePose/velocity", DeviceCategory.Tracker4, in_PoseState: PoseState.Velocity, in_Type: kVector3Type));
s_InputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/devicePose/angularVelocity", DeviceCategory.Tracker4, in_PoseState: PoseState.AngularVelocity, in_Type: kVector3Type));
#endregion
}
private static bool GetInputActionMapping(DeviceCategory device, PoseState poseState, out InputActionMapping inputActionMapping)
{
inputActionMapping = default;
for (int i = 0; i < s_InputActions.Count; i++)
{
var action = s_InputActions[i];
if (action.device == device && action.poseState == poseState)
{
inputActionMapping = action;
return true;
}
}
return false;
}
private static bool GetInputActionMapping(DeviceCategory device, ButtonEvent buttonEvent, out InputActionMapping inputActionMapping)
{
inputActionMapping = default;
for (int i = 0; i < s_InputActions.Count; i++)
{
var action = s_InputActions[i];
if (action.device == device && action.buttonEvent == buttonEvent)
{
inputActionMapping = action;
return true;
}
}
return false;
}
private static bool GetInputActionMapping(DeviceCategory device, HandEvent handEvent, out InputActionMapping inputActionMapping)
{
inputActionMapping = default;
for (int i = 0; i < s_InputActions.Count; i++)
{
var action = s_InputActions[i];
if (action.device == device && action.handEvent == handEvent)
{
inputActionMapping = action;
return true;
}
}
return false;
}
private static void CheckHandUpdated()
{
int frameCount = Time.frameCount;
if (frameCount > m_LeftHand.updateTime ||
frameCount > m_RightHand.updateTime)
{
#if UNITY_XR_HANDS
if (m_IsSupportViveHand || m_IsSupportXrHand)
{
if (m_HandSubsystem == null || !m_HandSubsystem.running)
{
if (m_HandSubsystem != null)
{
m_HandSubsystem.updatedHands -= OnUpdatedHands;
m_HandSubsystem = null;
}
m_HandSubsystems.Clear();
SubsystemManager.GetSubsystems(m_HandSubsystems);
for (var i = 0; i < m_HandSubsystems.Count; ++i)
{
var xrHand = m_HandSubsystems[i];
if (xrHand.running)
{
m_HandSubsystem = xrHand;
m_HandSubsystem.updatedHands += OnUpdatedHands;
break;
}
}
}
}
#else
if (m_IsSupportViveHand)
{
UpdateViveHand(true);
UpdateViveHand(false);
}
#endif
}
}
private static void UpdateViveHand(bool isLeft)
{
bool isUpdated = XR_EXT_hand_tracking.Interop.GetJointLocations(isLeft, out XrHandJointLocationEXT[] viveJoints);
for (int i = 0; i < m_JointBuffer.Length; i++)
{
bool isValid = isUpdated &&
viveJoints[i].locationFlags.HasFlag(XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_TRACKED_BIT) &&
viveJoints[i].locationFlags.HasFlag(XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT);
Vector3 position = viveJoints[i].pose.position.ToUnityVector();
Quaternion rotation = viveJoints[i].pose.orientation.ToUnityQuaternion();
m_JointBuffer[i] = new JointData(isValid, position, rotation);
}
if (isLeft)
{
m_LeftHand.Update(m_JointBuffer);
}
else
{
m_RightHand.Update(m_JointBuffer);
}
}
#if UNITY_XR_HANDS
private static void OnUpdatedHands(XRHandSubsystem xrHnad, XRHandSubsystem.UpdateSuccessFlags flags, XRHandSubsystem.UpdateType type)
{
if (xrHnad != null && xrHnad.running)
{
UpdateXRHand(true, xrHnad, flags.HasFlag(XRHandSubsystem.UpdateSuccessFlags.LeftHandJoints));
UpdateXRHand(false, xrHnad, flags.HasFlag(XRHandSubsystem.UpdateSuccessFlags.RightHandJoints));
}
}
private static void UpdateXRHand(bool isLeft, XRHandSubsystem xrHand, bool isUpdated)
{
for (int i = 0; i < m_JointBuffer.Length; i++)
{
XRHandJointID jointId = JointTypeToXRId(i);
XRHandJoint joint = (isLeft ? xrHand.leftHand : xrHand.rightHand).GetJoint(jointId);
if (isUpdated && joint.trackingState.HasFlag(XRHandJointTrackingState.Pose))
{
joint.TryGetPose(out Pose pose);
m_JointBuffer[i] = new JointData(true, pose.position, pose.rotation);
}
else
{
m_JointBuffer[i] = new JointData(false, Vector3.zero, Quaternion.identity);
}
}
if (isLeft)
{
m_LeftHand.Update(m_JointBuffer);
}
else
{
m_RightHand.Update(m_JointBuffer);
}
}
private static XRHandJointID JointTypeToXRId(int id)
{
switch (id)
{
case 0:
return XRHandJointID.Palm;
case 1:
return XRHandJointID.Wrist;
default:
return (XRHandJointID)(id + 1);
}
}
#endif
private static DeviceCategory GetController(Handedness handedness)
{
DeviceCategory device = DeviceCategory.None;
switch (handedness)
{
case Handedness.Left:
device = DeviceCategory.LeftController;
break;
case Handedness.Right:
device = DeviceCategory.RightController;
break;
}
return device;
}
private static DeviceCategory GetHand(Handedness handedness)
{
DeviceCategory device = DeviceCategory.None;
switch (handedness)
{
case Handedness.Left:
device = DeviceCategory.LeftHand;
break;
case Handedness.Right:
device = DeviceCategory.RightHand;
break;
}
return device;
}
private static Handedness GetHandedness(DeviceCategory device)
{
Handedness handedness = Handedness.None;
switch (device)
{
case DeviceCategory.LeftHand:
handedness = Handedness.Left;
break;
case DeviceCategory.RightHand:
handedness = Handedness.Right;
break;
}
return handedness;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 744a8b8476e9f394b87927173e7994b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4c76f10664e4be14c9f57109107c63ed
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5f35db36b366ffe458967ad9820f0b34
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,155 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.CustomGesture
{
[System.Serializable]
public class CustomGesture
{
public string GestureName = "NewGesture";
public CGEnums.HandFlag TargetHand;
[HideInInspector] public CGEnums.FingerStatus ThumbStatus;
[HideInInspector] public CGEnums.FingerStatus IndexStatus;
[HideInInspector] public CGEnums.FingerStatus MiddleStatus;
[HideInInspector] public CGEnums.FingerStatus RingStatus;
[HideInInspector] public CGEnums.FingerStatus PinkyStatus;
public TargetFingerStatus ThumbStatusIs;
public TargetFingerStatus IndexStatusIs;
public TargetFingerStatus MiddleStatusIs;
public TargetFingerStatus RingStatusIs;
public TargetFingerStatus PinkyStatusIs;
public JointDistance DualHandDistance;
[Range(20, 0)]
public float DualNear = 0;
[Range(20, 0)]
public float DualFar = 0;
public JointDistance ThumbIndexDistance;
[Range(10, 0)]
public float SingleNear = 0;
[Range(10, 0)]
public float SingleFar = 0;
}
public enum JointDistance
{
DontCare = 0,
Near = 1,
Far = 2,
}
public class HandJoint
{
/// <summary>
/// Tells whether the data of this <see cref = "HandJoint">HandJoints</see> is valid or not; the data shouldn't be used if <c>isValid</c> returns false
/// </summary>
public bool isValid;
/// <summary>
/// Holds the position of the <see cref = "HandJoint">HandJoints</see>
/// </summary>
public Vector3 position;
/// <summary>
/// Holds the rotation of the <see cref = "HandJoint">HandJoints</see>
/// </summary>
public Quaternion rotation;
public HandJoint()
{
isValid = false;
position = Vector3.zero;
rotation = Quaternion.identity;
}
}
public class FingerStatusExpresstion
{
public bool Is = true;
public CGEnums.FingerStatus Status = CGEnums.FingerStatus.None; //Straight;
}
public enum TargetFingerStatus
{
DontCare = 0,
Straight = 1,
Bending = 2,
Bended = 3,
NotStraight = 4,
NotBending = 5,
NotBended = 6,
}
static class EnumExtensions
{
public static FingerStatusExpresstion ToExpresstion(this TargetFingerStatus _Status)
{
FingerStatusExpresstion _Expresstion = new FingerStatusExpresstion();
switch (_Status)
{
case TargetFingerStatus.Straight:
_Expresstion.Is = true;
_Expresstion.Status = CGEnums.FingerStatus.Straight;
break;
case TargetFingerStatus.Bending:
_Expresstion.Is = true;
_Expresstion.Status = CGEnums.FingerStatus.Bending;
break;
case TargetFingerStatus.Bended:
_Expresstion.Is = true;
_Expresstion.Status = CGEnums.FingerStatus.Bended;
break;
case TargetFingerStatus.NotStraight: //using
_Expresstion.Is = false;
_Expresstion.Status = CGEnums.FingerStatus.Straight;
break;
case TargetFingerStatus.NotBending:
_Expresstion.Is = false;
_Expresstion.Status = CGEnums.FingerStatus.Bending;
break;
case TargetFingerStatus.NotBended: //using
_Expresstion.Is = false;
_Expresstion.Status = CGEnums.FingerStatus.Bended;
break;
case TargetFingerStatus.DontCare:
_Expresstion.Is = false;//true;
_Expresstion.Status = CGEnums.FingerStatus.None;
break;
}
return _Expresstion;
}
}
[System.Serializable]
public class FingerStatusDefiner
{
[Range(180, 0)]
public float StraightDistalLowBound = 160;
[Range(180, 0)]
public float StraightIntermediateLowBound = 160;
[Range(180, 0)]
public float StraightProximalLowBound = 160;
[Range(180, 0)]
public float BendingDistalLowBound = 120;
[Range(180, 0)]
public float BendingIntermediateLowBound = 120;
[Range(180, 0)]
public float BendingProximalLowBound = 120;
[System.Serializable]
public class AngleRange
{
[Range(180, 0)]
public float LowBound = 0;
[Range(180, 0)]
public float HeighBound = 180;
public AngleRange() { }
public AngleRange(float _Heigh, float _Low)
{
HeighBound = _Heigh;
LowBound = _Low;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a35a47ea932ca8845b42e834c36d2205
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,154 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.UI;
using VIVE.OpenXR.Hand;
namespace VIVE.OpenXR.Toolkits.CustomGesture
{
[RequireComponent(typeof(CustomGestureManager))]
public class CustomGestureDefiner : MonoBehaviour
{
public List<CustomGesture> DefinedGestures = new List<CustomGesture>();
static List<CustomGesture> definedGestures = new List<CustomGesture>();
CustomGestureManager HGM;
static CustomGestureManager hGM;
void Start()
{
HGM = GetComponent<CustomGestureManager>();
definedGestures = DefinedGestures;
hGM = HGM;
}
void Update()
{
}
public static bool IsCurrentGestureTriiggered(string _GestureName, CGEnums.HandFlag _Hand)
{
bool SingleDsLeft = true, SingleDsRight = true;
CustomGesture _Gesture = definedGestures.Find(_x => _x.GestureName == _GestureName);
if(_Gesture == null)
{
Debug.LogWarning("HandGesture is not definded");
return false;
}
if (hGM == null)
{
Debug.LogError("hGM is null");
return false;
}
float SingledisXLeft = 0.0f, SingledisYLeft = 0.0f, SingledisXRight = 0.0f, SingledisYRight = 0.0f, /*SingledisZ = 0.0f,*/ SingledistanceLeft = 0.0f, SingledistanceRight = 0.0f;
HandJoint[] _JointsL, _JointsR;
switch (_Hand)//(_Gesture.TargetHand)
{
case CGEnums.HandFlag.Left:
_JointsL = CustomGestureManager.GetHandJointLocations(CGEnums.HandFlag.Left);
SingledisXLeft = _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.x - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.x;
SingledisYLeft = _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.y - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.y;
//SingledisZ = _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.z - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.z;
SingledistanceLeft = Mathf.Sqrt(Mathf.Pow(SingledisXLeft, 2) + Mathf.Pow(SingledisYLeft, 2));
//Debug.Log("Single CheckHandDistance Thumb L: " + SingledisXLeft + ", " + SingledisYLeft + ", " + SingledisZ + ", distance:" + SingledistanceLeft*100);
if (_Gesture.ThumbIndexDistance != JointDistance.DontCare)
SingleDsLeft = (_Gesture.ThumbIndexDistance == JointDistance.Far) ? ((SingledistanceLeft * 100) > _Gesture.SingleFar) : ((SingledistanceLeft * 100) < _Gesture.SingleNear);
if ((_Gesture.ThumbStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Thumb) == _Gesture.ThumbStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Thumb) != _Gesture.ThumbStatusIs.ToExpresstion().Status) &&
(_Gesture.IndexStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Index) == _Gesture.IndexStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Index) != _Gesture.IndexStatusIs.ToExpresstion().Status) &&
(_Gesture.MiddleStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Middle) == _Gesture.MiddleStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Middle) != _Gesture.MiddleStatusIs.ToExpresstion().Status) &&
(_Gesture.RingStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Ring) == _Gesture.RingStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Ring) != _Gesture.RingStatusIs.ToExpresstion().Status) &&
(_Gesture.PinkyStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Pinky) == _Gesture.PinkyStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Pinky) != _Gesture.PinkyStatusIs.ToExpresstion().Status))
{
return (true && SingleDsLeft);
}
return false;
case CGEnums.HandFlag.Right:
_JointsR = CustomGestureManager.GetHandJointLocations(CGEnums.HandFlag.Right);
SingledisXRight = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.x - _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.x;
SingledisYRight = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.y - _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.y;
//SingledisZ = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.z - _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.z;
SingledistanceRight = Mathf.Sqrt(Mathf.Pow(SingledisXRight, 2) + Mathf.Pow(SingledisYRight, 2));
//Debug.Log("Single CheckHandDistance Thumb R: " + SingledisXRight + ", " + SingledisYRight + ", " + SingledisZ + ", distance:" + SingledistanceRight*100);
if (_Gesture.ThumbIndexDistance != JointDistance.DontCare)
SingleDsRight = (_Gesture.ThumbIndexDistance == JointDistance.Far) ? ((SingledistanceRight * 100) > _Gesture.SingleFar) : ((SingledistanceRight * 100) < _Gesture.SingleNear);
if ((_Gesture.ThumbStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Thumb) == _Gesture.ThumbStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Thumb) != _Gesture.ThumbStatusIs.ToExpresstion().Status) &&
(_Gesture.IndexStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Index) == _Gesture.IndexStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Index) != _Gesture.IndexStatusIs.ToExpresstion().Status) &&
(_Gesture.MiddleStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Middle) == _Gesture.MiddleStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Middle) != _Gesture.MiddleStatusIs.ToExpresstion().Status) &&
(_Gesture.RingStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Ring) == _Gesture.RingStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Ring) != _Gesture.RingStatusIs.ToExpresstion().Status) &&
(_Gesture.PinkyStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Pinky) == _Gesture.PinkyStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Pinky) != _Gesture.PinkyStatusIs.ToExpresstion().Status))
{
return (true && SingleDsRight);
}
return false;
case CGEnums.HandFlag.Either:
if ((_Gesture.ThumbStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Thumb) == _Gesture.ThumbStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Thumb) != _Gesture.ThumbStatusIs.ToExpresstion().Status) &&
(_Gesture.IndexStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Index) == _Gesture.IndexStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Index) != _Gesture.IndexStatusIs.ToExpresstion().Status) &&
(_Gesture.MiddleStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Middle) == _Gesture.MiddleStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Middle) != _Gesture.MiddleStatusIs.ToExpresstion().Status) &&
(_Gesture.RingStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Ring) == _Gesture.RingStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Ring) != _Gesture.RingStatusIs.ToExpresstion().Status) &&
(_Gesture.PinkyStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Pinky) == _Gesture.PinkyStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Pinky) != _Gesture.PinkyStatusIs.ToExpresstion().Status))
{
return true;
}
else if ((_Gesture.ThumbStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Thumb) == _Gesture.ThumbStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Thumb) != _Gesture.ThumbStatusIs.ToExpresstion().Status) &&
(_Gesture.IndexStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Index) == _Gesture.IndexStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Index) != _Gesture.IndexStatusIs.ToExpresstion().Status) &&
(_Gesture.MiddleStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Middle) == _Gesture.MiddleStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Middle) != _Gesture.MiddleStatusIs.ToExpresstion().Status) &&
(_Gesture.RingStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Ring) == _Gesture.RingStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Ring) != _Gesture.RingStatusIs.ToExpresstion().Status) &&
(_Gesture.PinkyStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Pinky) == _Gesture.PinkyStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Pinky) != _Gesture.PinkyStatusIs.ToExpresstion().Status))
{
return true;
}
return false;
case CGEnums.HandFlag.Dual:
_JointsL = CustomGestureManager.GetHandJointLocations(CGEnums.HandFlag.Left);
_JointsR = CustomGestureManager.GetHandJointLocations(CGEnums.HandFlag.Right);
SingledisXLeft = _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.x - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.x;
SingledisYLeft = _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.y - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.y;
//SingledisZ = _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.z - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.z;
SingledistanceLeft = Mathf.Sqrt(Mathf.Pow(SingledisXLeft, 2) + Mathf.Pow(SingledisYLeft, 2));
SingledisXRight = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.x - _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.x;
SingledisYRight = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.y - _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.y;
//SingledisZ = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.z - _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_THUMB_TIP_EXT].position.z;
SingledistanceRight = Mathf.Sqrt(Mathf.Pow(SingledisXLeft, 2) + Mathf.Pow(SingledisYLeft, 2));
//Debug.Log("Single CheckHandDistance Thumb: " + SingledisYLeft + ", " + SingledisYLeft + ", " + SingledisZ + ", distance:" + SingledistanceLeft);
float DualdisX = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.x - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.x;
float DualdisY = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.y - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.y;
//float DualdisZ = _JointsR[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.z - _JointsL[(int)XrHandJointEXT.XR_HAND_JOINT_INDEX_TIP_EXT].position.z;
float Dualdistance = Mathf.Sqrt(Mathf.Pow(DualdisX, 2) + Mathf.Pow(DualdisY, 2));
//Debug.Log("Dual CheckHandDistance Index: " + DualdisX + ", " + DualdisY + ", " + DualdisZ + ", distance:" + Dualdistance);
bool LGesture = false, RGesture = false, DualDs = true;
if(_Gesture.DualHandDistance != JointDistance.DontCare)
DualDs = (_Gesture.DualHandDistance == JointDistance.Far) ? ((Dualdistance * 100) > _Gesture.DualFar) : ((Dualdistance * 100) < _Gesture.DualNear);
if(_Gesture.ThumbIndexDistance != JointDistance.DontCare)
SingleDsLeft = (_Gesture.ThumbIndexDistance == JointDistance.Far) ? ((SingledistanceLeft * 100) > _Gesture.SingleFar) : ((SingledistanceLeft * 100) < _Gesture.SingleNear);
if (_Gesture.ThumbIndexDistance != JointDistance.DontCare)
SingleDsRight = (_Gesture.ThumbIndexDistance == JointDistance.Far) ? ((SingledistanceRight * 100) > _Gesture.SingleFar) : ((SingledistanceRight * 100) < _Gesture.SingleNear);
LGesture = (_Gesture.ThumbStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Thumb) == _Gesture.ThumbStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Thumb) != _Gesture.ThumbStatusIs.ToExpresstion().Status) &&
(_Gesture.IndexStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Index) == _Gesture.IndexStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Index) != _Gesture.IndexStatusIs.ToExpresstion().Status) &&
(_Gesture.MiddleStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Middle) == _Gesture.MiddleStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Middle) != _Gesture.MiddleStatusIs.ToExpresstion().Status) &&
(_Gesture.RingStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Ring) == _Gesture.RingStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Ring) != _Gesture.RingStatusIs.ToExpresstion().Status) &&
(_Gesture.PinkyStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Pinky) == _Gesture.PinkyStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Left, CGEnums.FingerFlag.Pinky) != _Gesture.PinkyStatusIs.ToExpresstion().Status);
RGesture = (_Gesture.ThumbStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Thumb) == _Gesture.ThumbStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Thumb) != _Gesture.ThumbStatusIs.ToExpresstion().Status) &&
(_Gesture.IndexStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Index) == _Gesture.IndexStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Index) != _Gesture.IndexStatusIs.ToExpresstion().Status) &&
(_Gesture.MiddleStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Middle) == _Gesture.MiddleStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Middle) != _Gesture.MiddleStatusIs.ToExpresstion().Status) &&
(_Gesture.RingStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Ring) == _Gesture.RingStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Ring) != _Gesture.RingStatusIs.ToExpresstion().Status) &&
(_Gesture.PinkyStatusIs.ToExpresstion().Is ? hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Pinky) == _Gesture.PinkyStatusIs.ToExpresstion().Status : hGM.GetFingerStatus(CGEnums.HandFlag.Right, CGEnums.FingerFlag.Pinky) != _Gesture.PinkyStatusIs.ToExpresstion().Status);
//Debug.Log("Dual Gesture rs: "+ _GestureName + LGesture + ", " + RGesture + ", " + SingleDs + ", " + DualDs );
return (LGesture && RGesture && SingleDsLeft && DualDs && SingleDsRight);
default:
Debug.LogError("The HandFlag can only be set to Lef, Right or Both");
return false;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d622bde162fe6524aa1cf99ee5bbda11
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
// Copyright HTC Corporation All Rights Reserved.
namespace VIVE.OpenXR.Toolkits.CustomGesture
{
public class CGEnums
{
public enum HandFlag
{
/// <summary>
/// The flag indicating no hand
/// </summary>
None = 0,
/// <summary>
/// The flag indicating the left hand
/// </summary>
Left = 1,
/// <summary>
/// The flag indicating the right hand
/// </summary>
Right = 2,
Either = 3,
Dual = 4,
Num = 5,
}
public enum FingerStatus
{
None = 0,
Straight = 1,
Bending = 2,
Bended = 3,
Num = 4,
}
public enum FingerFlag
{
None = 0,
Thumb = 1,
Index = 2,
Middle = 3,
Ring = 4,
Pinky = 5,
Num = 6,
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9ff3816ca3a86354f81e7ad4d29b2937
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,249 @@
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using VIVE.OpenXR.Hand;
namespace VIVE.OpenXR.Toolkits.CustomGesture
{
//Straight = 1,
//Bending = 2,
//Bended = 3,
public class CustomGestureManager : MonoBehaviour
{
//public CGEnums.HandFlag Hand;
public FingerStatusDefiner ThumbDefiner;
public FingerStatusDefiner IndexDefiner;
public FingerStatusDefiner MiddleDefiner;
public FingerStatusDefiner RingDefiner;
public FingerStatusDefiner PinkyDefiner;
static HandJoint[] LeftHandJoints = NewHandJointArray();
static HandJoint[] RightHandJoints = NewHandJointArray();
static int LeftLastCalculationTime = -1;
static int RightLastCalculationTime = -1;
static HandJoint[] NewHandJointArray()
{
HandJoint[] _Joints = new HandJoint[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT];
for (int i = 0; i < _Joints.Length; i++)
{
_Joints[i] = new HandJoint();
}
return _Joints;
}
public static HandJoint[] GetHandJointLocations(CGEnums.HandFlag hand)
{
Update_HandJoints(hand);
return hand == CGEnums.HandFlag.Left ? LeftHandJoints : RightHandJoints;
}
static void Update_HandJoints(CGEnums.HandFlag hand)
{
if ((hand == CGEnums.HandFlag.Left ? LeftLastCalculationTime : RightLastCalculationTime) == Time.frameCount)
{
return;
}
if (hand == CGEnums.HandFlag.Left)
{
LeftLastCalculationTime = Time.frameCount;
}
else
{
RightLastCalculationTime = Time.frameCount;
}
XrHandJointLocationEXT[] jointLocations = new XrHandJointLocationEXT[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT];
ViveHandTracking feature = OpenXRSettings.Instance.GetFeature<ViveHandTracking>();
//Debug.Log("CustomGestureManager GetHandJointLocations() feat: " + feature
//+", fGetJLocLeft: " + feature.GetJointLocations(hand == CGEnums.HandFlag.Left, out jointLocations)
//+", fGetJLocRight: " + feature.GetJointLocations(hand == CGEnums.HandFlag.Right, out jointLocations));
if (feature && feature.GetJointLocations(hand == CGEnums.HandFlag.Left, out jointLocations))
{
//Debug.Log("CustomGestureManager GetHandJointLocations()!");
for (int i = 0; i < (int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT; i++)
{
if (hand == CGEnums.HandFlag.Left)
{
LeftHandJoints[i].position.x = jointLocations[i].pose.position.x;
LeftHandJoints[i].position.y = jointLocations[i].pose.position.y;
LeftHandJoints[i].position.z = -jointLocations[i].pose.position.z;
LeftHandJoints[i].rotation.x = jointLocations[i].pose.orientation.x;
LeftHandJoints[i].rotation.y = jointLocations[i].pose.orientation.y;
LeftHandJoints[i].rotation.z = -jointLocations[i].pose.orientation.z;
LeftHandJoints[i].rotation.w = -jointLocations[i].pose.orientation.w;
}
else
{
RightHandJoints[i].position.x = jointLocations[i].pose.position.x;
RightHandJoints[i].position.y = jointLocations[i].pose.position.y;
RightHandJoints[i].position.z = -jointLocations[i].pose.position.z;
RightHandJoints[i].rotation.x = jointLocations[i].pose.orientation.x;
RightHandJoints[i].rotation.y = jointLocations[i].pose.orientation.y;
RightHandJoints[i].rotation.z = -jointLocations[i].pose.orientation.z;
RightHandJoints[i].rotation.w = -jointLocations[i].pose.orientation.w;
}
//if ((jointLocations[i].locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0)
//Debug.Log("CustomGestureManager GetHandJointLocations() ORIENTATION_VALID_BIT not 0");
//if ((jointLocations[i].locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0)
//Debug.Log("CustomGestureManager GetHandJointLocations() LOCATION_POSITION_VALID_BIT not 0");
//if ((jointLocations[i].locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_TRACKED_BIT) != 0)
//Debug.Log("CustomGestureManager GetHandJointLocations() POSITION_TRACKED_BIT not 0");
//if ((jointLocations[i].locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0)
//Debug.Log("CustomGestureManager GetHandJointLocations() ORIENTATION_TRACKED_BIT not 0");
if ((jointLocations[i].locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_TRACKED_BIT) != 0 && (jointLocations[i].locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0
&& (jointLocations[i].locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0 && (jointLocations[i].locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0)
{
(hand == CGEnums.HandFlag.Left ? LeftHandJoints[i] : RightHandJoints[i]).isValid = true;
//if (hand == CGEnums.HandFlag.Left) Debug.Log("CustomGestureManager GetHandJointLocations() set isValid to true(If_BIT)! Left");
//else Debug.Log("CustomGestureManager GetHandJointLocations() set isValid to true(If_BIT)! Right");
}
else
{
(hand == CGEnums.HandFlag.Left ? LeftHandJoints[i] : RightHandJoints[i]).isValid = false;
//if (hand == CGEnums.HandFlag.Left) Debug.Log("CustomGestureManager GetHandJointLocations() set isValid to false(If)! Left");
//else Debug.Log("CustomGestureManager GetHandJointLocations() set isValid to true(If)! Right");
}
}
}
else
{
if (hand == CGEnums.HandFlag.Left)
{
for (int i = 0; i < (int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT; i++)
{
LeftHandJoints[i].isValid = false;
}
//Debug.Log("CustomGestureManager GetHandJointLocations() set isValid to false(else) Left!");
}
else
{
for (int i = 0; i < (int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT; i++)
{
RightHandJoints[i].isValid = false;
}
//Debug.Log("CustomGestureManager GetHandJointLocations() set isValid to false(else) Right!");
}
}
}
public CGEnums.FingerStatus GetFingerStatus(CGEnums.HandFlag _Hand, CGEnums.FingerFlag _Finger)
{
switch (_Finger)
{
case CGEnums.FingerFlag.Thumb:
if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_THUMB_DISTAL_EXT) == 0.0f &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_THUMB_PROXIMAL_EXT) == 0.0f)
{
return CGEnums.FingerStatus.None;
}
else if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_THUMB_DISTAL_EXT) > ThumbDefiner.StraightDistalLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_THUMB_PROXIMAL_EXT) > ThumbDefiner.StraightProximalLowBound)
{
return CGEnums.FingerStatus.Straight;
}
else if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_THUMB_DISTAL_EXT) < ThumbDefiner.BendingDistalLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_THUMB_PROXIMAL_EXT) < ThumbDefiner.BendingProximalLowBound)
{
return CGEnums.FingerStatus.Bended;
}
return CGEnums.FingerStatus.Bending;
case CGEnums.FingerFlag.Index:
if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_DISTAL_EXT) == 0.0f &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT) == 0.0f &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_PROXIMAL_EXT) == 0.0f)
{
return CGEnums.FingerStatus.None;
}
else if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_DISTAL_EXT) > IndexDefiner.StraightDistalLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT) > IndexDefiner.StraightIntermediateLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_DISTAL_EXT) > IndexDefiner.StraightProximalLowBound)
{
return CGEnums.FingerStatus.Straight;
}
else if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_DISTAL_EXT) < IndexDefiner.BendingDistalLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT) < IndexDefiner.BendingIntermediateLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_INDEX_PROXIMAL_EXT) < IndexDefiner.BendingProximalLowBound)
{
return CGEnums.FingerStatus.Bended;
}
return CGEnums.FingerStatus.Bending;
case CGEnums.FingerFlag.Middle:
if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_MIDDLE_DISTAL_EXT) > MiddleDefiner.StraightDistalLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT) > MiddleDefiner.StraightIntermediateLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_MIDDLE_DISTAL_EXT) > MiddleDefiner.StraightProximalLowBound)
{
return CGEnums.FingerStatus.Straight;
}
else if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_MIDDLE_DISTAL_EXT) < MiddleDefiner.BendingDistalLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT) < MiddleDefiner.BendingIntermediateLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT) < MiddleDefiner.BendingProximalLowBound)
{
return CGEnums.FingerStatus.Bended;
}
return CGEnums.FingerStatus.Bending;
case CGEnums.FingerFlag.Ring:
if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_RING_DISTAL_EXT) > RingDefiner.StraightDistalLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_RING_INTERMEDIATE_EXT) > RingDefiner.StraightIntermediateLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_RING_DISTAL_EXT) > RingDefiner.StraightProximalLowBound)
{
return CGEnums.FingerStatus.Straight;
}
else if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_RING_DISTAL_EXT) < RingDefiner.BendingDistalLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_RING_INTERMEDIATE_EXT) < RingDefiner.BendingIntermediateLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_RING_PROXIMAL_EXT) < RingDefiner.BendingProximalLowBound)
{
return CGEnums.FingerStatus.Bended;
}
return CGEnums.FingerStatus.Bending;
case CGEnums.FingerFlag.Pinky:
if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_LITTLE_DISTAL_EXT) > PinkyDefiner.StraightDistalLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT) > PinkyDefiner.StraightIntermediateLowBound &&
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_LITTLE_DISTAL_EXT) > PinkyDefiner.StraightProximalLowBound)
{
return CGEnums.FingerStatus.Straight;
}
else if (GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_LITTLE_DISTAL_EXT) < PinkyDefiner.BendingDistalLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT) < PinkyDefiner.BendingIntermediateLowBound ||
GetAngleofHandNode(_Hand, XrHandJointEXT.XR_HAND_JOINT_LITTLE_PROXIMAL_EXT) < PinkyDefiner.BendingProximalLowBound)
{
return CGEnums.FingerStatus.Bended;
}
return CGEnums.FingerStatus.Bending;
}
return CGEnums.FingerStatus.None;
}
static float GetAngleofHandNode(CGEnums.HandFlag _Hand, XrHandJointEXT _HandJoint)
{
Vector3 _Bone1Dir = Vector3.zero, _Bone2Dir = Vector3.zero;
if (_HandJoint != XrHandJointEXT.XR_HAND_JOINT_THUMB_DISTAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_RING_DISTAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_MIDDLE_DISTAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_LITTLE_DISTAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_INDEX_DISTAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_RING_INTERMEDIATE_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_THUMB_PROXIMAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_RING_PROXIMAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_LITTLE_PROXIMAL_EXT &&
_HandJoint != XrHandJointEXT.XR_HAND_JOINT_INDEX_PROXIMAL_EXT)
{
Debug.LogError("_HandJoint has to be a joint between two bones on the fingers!");
return (float)double.NaN;
}
HandJoint[] _Joints = GetHandJointLocations(_Hand);
_Bone1Dir = _Joints[(int)(_HandJoint + 1)].position - _Joints[(int)_HandJoint].position;
_Bone2Dir = _Joints[(int)(_HandJoint - 1)].position - _Joints[(int)_HandJoint].position;
return Vector3.Angle(_Bone1Dir, _Bone2Dir);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1fc3b0937a9ed7d4a82e0a0c262beaf4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a3a84b60570d8b243984fbe7169ca44a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,390 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VIVE.OpenXR.Feature;
using static VIVE.OpenXR.Feature.FutureWrapper;
namespace VIVE.OpenXR.Toolkits
{
/// <summary>
/// FutureTask is not a C# Task. It is a wrapper for OpenXR Future.
/// Each OpenXR Future may have its own FutureTask type because the result and the
/// complete function are different.
/// However the poll and complete are common. This class use c# Task to poll the future.
/// You can <see cref="Cancel the future"/> if you do not want to wait for the result.
/// However Cancel should be called before the Complete.
/// When <see cref="IsPollCompleted"/> is true, call <see cref="Complete"/> to complete the future and get the result.
/// </summary>
/// <typeparam name="TResult">You can customize the type depending on the complete's result.</typeparam>
public class FutureTask<TResult> : IDisposable
{
private Task<(XrResult, XrFutureStateEXT)> pollTask;
private Task autoCompleteTask;
Func<IntPtr, TResult> completeFunc;
private CancellationTokenSource cts;
private IntPtr future;
bool autoComplete = false;
private int pollIntervalMS = 10;
/// <summary>
/// Set poll inverval in milliseconds. The value will be clamped between 1 and 2000.
/// </summary>
public int PollIntervalMS {
get => pollIntervalMS;
set => pollIntervalMS = Math.Clamp(value, 1, 2000);
}
public bool IsAutoComplete => autoComplete;
bool isCompleted = false;
public bool IsCompleted => isCompleted;
public bool Debug { get; set; } = false;
/// <summary>
/// The FutureTask is used to poll and complete the future.
/// Once the FutureTask is create, poll will start running in the period of pollIntervalMS.
/// if auto complete is set, the future will be do completed once user check IsPollCompleted and IsCompleted.
/// I prefered to use non-autoComplete.
/// If no auto complete, you can cancel the task and no need to free resouce.
/// Once it completed, you need handle the result to avoid leakage.
/// </summary>
/// <param name="future"></param>
/// <param name="completeFunc"></param>
/// <param name="pollIntervalMS">Set poll inverval in milliseconds. The value will be clamped between 1 and 2000.</param>
/// <param name="autoComplete">If true, do Complete when check IsPollCompleted and IsCompleted</param>
public FutureTask(IntPtr future, Func<IntPtr, TResult> completeFunc, int pollIntervalMS = 10, bool autoComplete = false)
{
cts = new CancellationTokenSource();
this.completeFunc = completeFunc;
this.future = future;
this.pollIntervalMS = Math.Clamp(pollIntervalMS, 1, 2000);
// User may get PollTask and run. So, we need to make sure the pollTask is created.
pollTask = MakePollTask(this, cts.Token);
// will set autoComplete true in AutoComplete.
this.autoComplete = false;
if (autoComplete)
AutoComplete();
}
/// <summary>
/// AutoComplete will complete the future once the poll task is ready and success.
/// If you want to handle error, you should not use AutoComplete.
/// </summary>
public void AutoComplete()
{
if (autoComplete)
return;
autoComplete = true;
autoCompleteTask = pollTask.ContinueWith(task =>
{
// If the task is cancelled or faulted, we do not need to complete the future.
if (task.IsCanceled || task.IsFaulted)
{
isCompleted = true;
return;
}
var result = task.Result;
// Make sure call Complete only if poll task is ready and success.
if (result.Item1 == XrResult.XR_SUCCESS)
{
if (result.Item2 == XrFutureStateEXT.Ready)
{
Complete();
}
}
isCompleted = true;
});
}
/// <summary>
/// Used for create FromResult if you need return the result immediately.
/// </summary>
/// <param name="pollTask"></param>
/// <param name="completeFunc"></param>
FutureTask(Task<(XrResult, XrFutureStateEXT)> pollTask, Func<IntPtr, TResult> completeFunc)
{
this.pollTask = pollTask;
this.completeFunc = completeFunc;
this.future = IntPtr.Zero;
}
public Task<(XrResult, XrFutureStateEXT)> PollTask => pollTask;
/// <summary>
/// If AutoComplete is set, the task will be created. Otherwise, it will be null.
/// </summary>
public Task AutoCompleteTask => autoCompleteTask;
public bool IsPollCompleted => pollTask.IsCompleted;
public XrResult PollResult => pollTask.Result.Item1;
public IntPtr Future => future;
/// <summary>
/// Cancel the future. If the future is not completed yet, it will be cancelled. Otherwise, nothing will happen.
/// </summary>
public void Cancel()
{
if (!isCompleted)
{
cts?.Cancel();
FutureWrapper.Instance?.CancelFuture(future);
}
future = IntPtr.Zero;
}
/// <summary>
/// Make sure do Complete after IsPollCompleted. If the future is not poll completed yet, throw exception.
/// </summary>
/// <returns>The result of the completeFunc.</returns>
/// <exception cref="Exception">Thrown when the pollTask is not completed yet.</exception>
public TResult Complete()
{
if (isCompleted)
return result;
if (pollTask.IsCompleted)
{
if (this.Debug)
UnityEngine.Debug.Log("FutureTask is completed.");
isCompleted = true;
if (pollTask.Result.Item1 == XrResult.XR_SUCCESS)
{
result = completeFunc(future);
isCompleted = true;
return result;
}
if (this.Debug)
UnityEngine.Debug.Log("FutureTask is completed with error. Check if pollTask result error.");
return default;
}
else
{
throw new Exception("FutureTask is not completed yet.");
}
}
/// <summary>
/// Wait until poll task is completed. If the task is not completed, it will block the thread.
/// If AutoComplete is set, wait until the complete task is completed.
/// </summary>
public void Wait()
{
pollTask.Wait();
if (autoComplete)
autoCompleteTask.Wait();
}
TResult result;
private bool disposedValue;
/// <summary>
/// This Result did not block the thread. If not completed, it will return undefined value. Make sure you call it when Complete is done.
/// </summary>
public TResult Result => result;
public static FutureTask<TResult> FromResult(TResult result)
{
return new FutureTask<TResult>(Task.FromResult((XrResult.XR_SUCCESS, XrFutureStateEXT.Ready)), (future) => result);
}
/// <summary>
/// Poll until the future is ready. Caceled if the cts is cancelled. But the future will not be cancelled.
/// </summary>
/// <param name="futureTask"></param>
/// <param name="pollIntervalMS"></param>
/// <param name="cts"></param>
/// <returns></returns>
static async Task<(XrResult, XrFutureStateEXT)> MakePollTask(FutureTask<TResult> futureTask, CancellationToken ct)
{
XrFuturePollInfoEXT pollInfo = new XrFuturePollInfoEXT()
{
type = XrStructureType.XR_TYPE_FUTURE_POLL_INFO_EXT,
next = IntPtr.Zero,
future = futureTask.Future
};
do
{
ct.ThrowIfCancellationRequested();
XrResult ret = FutureWrapper.Instance.PollFuture(ref pollInfo, out FutureWrapper.XrFuturePollResultEXT pollResult);
if (ret == XrResult.XR_SUCCESS)
{
if (pollResult.state == XrFutureStateEXT.Ready)
{
if (futureTask.Debug)
UnityEngine.Debug.Log("Future is ready.");
return (XrResult.XR_SUCCESS, pollResult.state);
}
else if (pollResult.state == XrFutureStateEXT.Pending)
{
if (futureTask.Debug)
UnityEngine.Debug.Log("Wait for future.");
await Task.Delay(futureTask.pollIntervalMS);
continue;
}
}
else
{
return (ret, XrFutureStateEXT.None);
}
} while (true);
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
pollTask?.Dispose();
pollTask = null;
autoCompleteTask?.Dispose();
autoCompleteTask = null;
cts?.Dispose();
cts = null;
}
if (future != IntPtr.Zero && !isCompleted)
FutureWrapper.Instance?.CancelFuture(future);
future = IntPtr.Zero;
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Help to manage the future task. Tasks are name less. In order to manager tasks,
/// additonal information are required. And this class is used to store those information.
/// Helps to retrive the task by the identity, or retrive the identity by the task.
/// </summary>
/// <typeparam name="Identity">What the task work for. How to identify this task.</typeparam>
/// <typeparam name="Output">The task's output type, for example, XrResult or Tuple.</typeparam>
public class FutureTaskManager<Identity, TResult> : IDisposable
{
readonly List<(Identity, FutureTask<TResult>)> tasks = new List<(Identity, FutureTask<TResult>)>();
private bool disposedValue;
public FutureTaskManager() { }
public FutureTask<TResult> GetTask(Identity identity)
{
return tasks.FirstOrDefault(x => x.Item1.Equals(identity)).Item2;
}
/// <summary>
/// Add a task to the manager.
/// </summary>
/// <param name="identity"></param>
/// <param name="task"></param>
public void AddTask(Identity identity, FutureTask<TResult> task)
{
tasks.Add((identity, task));
}
/// <summary>
/// Remove keeped task and cancel it If task is not completed.
/// </summary>
/// <param name="task"></param>
public void RemoveTask(Identity identity)
{
var task = tasks.FirstOrDefault(x => x.Item1.Equals(identity));
if (task.Item2 != null)
{
task.Item2.Cancel();
task.Item2.Dispose();
}
tasks.Remove(task);
}
/// <summary>
/// Remove keeped task and cancel it If task is not completed.
/// </summary>
/// <param name="task"></param>
public void RemoveTask(FutureTask<TResult> task)
{
var t = tasks.FirstOrDefault(x => x.Item2 == task);
if (t.Item2 != null)
{
t.Item2.Cancel();
t.Item2.Dispose();
}
tasks.Remove(t);
}
/// <summary>
/// Get all tasks's list.
/// </summary>
/// <returns></returns>
public List<(Identity, FutureTask<TResult>)> GetTasks()
{
return tasks;
}
/// <summary>
/// Check if has any task.
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
return tasks.Count == 0;
}
/// <summary>
/// Clear all tasks and cancel them. If tasks are auto completed, make sure their results handled.
/// Otherwise, the resource will be leaked.
/// </summary>
/// <param name="cancelTask"></param>
public void Clear(bool cancelTask = true)
{
if (cancelTask)
{
foreach (var task in tasks)
{
if (task.Item2 != null)
{
task.Item2.Cancel();
task.Item2.Dispose();
}
}
}
tasks.Clear();
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
Clear();
}
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f234725d9eefc7540843fd691e94553a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 10e073adb659279408a85f7b31ed9d91
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,591 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using System.Linq;
namespace VIVE.OpenXR.Passthrough
{
public static class PassthroughAPI
{
#region LOG
const string LOG_TAG = "VIVE.OpenXR.Passthrough.PassthroughAPI";
static void DEBUG(string msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
static void WARNING(string msg) { Debug.LogWarningFormat("{0} {1}", LOG_TAG, msg); }
static void ERROR(string msg) { Debug.LogErrorFormat("{0} {1}", LOG_TAG, msg); }
#endregion
private static VivePassthrough passthroughFeature = null;
private static bool AssertFeature()
{
if (passthroughFeature == null) { passthroughFeature = OpenXRSettings.Instance.GetFeature<VivePassthrough>(); }
if (passthroughFeature) { return true; }
return false;
}
private static Dictionary<XrPassthroughHTC, PassthroughLayer> layersDict = new Dictionary<XrPassthroughHTC, PassthroughLayer>();
#region Public APIs
/// <summary>
/// Creates a fullscreen passthrough.
/// Passthroughs will be destroyed automatically when the current <see cref="XrSession"/> is destroyed.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="layerType">The <see cref="LayerType"/> specifies whether the passthrough is an overlay or underlay.</param>
/// <param name="onDestroyPassthroughSessionHandler">A <see cref="VivePassthrough.OnPassthroughSessionDestroyDelegate">delegate</see> will be invoked when the current OpenXR Session is going to be destroyed.</param>
/// <param name="alpha">The alpha value of the passthrough layer within the range [0, 1] where 1 (Opaque) is default.</param>
/// <param name="compositionDepth">The composition depth relative to other composition layers if present where 0 is default.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static XrResult CreatePlanarPassthrough(out XrPassthroughHTC passthrough, CompositionLayer.LayerType layerType, VivePassthrough.OnPassthroughSessionDestroyDelegate onDestroyPassthroughSessionHandler = null, float alpha = 1f, uint compositionDepth = 0)
{
passthrough = 0;
XrResult res = XrResult.XR_ERROR_RUNTIME_FAILURE;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return res;
}
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
#if UNITY_ANDROID
IntPtr.Zero,
#else
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
#endif
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PLANAR_HTC
);
res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
if(res == XrResult.XR_SUCCESS)
{
PassthroughLayer layer = new PassthroughLayer(passthrough, layerType);
var xrLayer = PassthroughLayer.MakeEmptyLayer();
xrLayer.passthrough = passthrough;
xrLayer.layerFlags = (UInt64)XrCompositionLayerFlagBits.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
xrLayer.color.alpha = alpha;
layer.SetLayer(xrLayer);
layersDict.Add(passthrough, layer);
}
if (res == XrResult.XR_SUCCESS)
{
SetPassthroughAlpha(passthrough, alpha);
}
return res;
}
/// <summary>
/// Creates a projected passthrough (i.e. Passthrough is only partially visible). Visible region of the projected passthrough is determined by the mesh and its transform.
/// Passthroughs will be destroyed automatically when the current <see cref="XrSession"/> is destroyed.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="layerType">The <see cref="LayerType"/> specifies whether the passthrough is an overlay or underlay.</param>
/// <param name="vertexBuffer">Positions of the vertices in the mesh.</param>
/// <param name="indexBuffer">List of triangles represented by indices into the <paramref name="vertexBuffer"/>.</param>
/// <param name="spaceType">The projected passthrough's <see cref="ProjectedPassthroughSpaceType"/></param>
/// <param name="meshPosition">Position of the mesh.</param>
/// <param name="meshOrientation">Orientation of the mesh.</param>
/// <param name="meshScale">Scale of the mesh.</param>
/// <param name="onDestroyPassthroughSessionHandler">A <see cref="VivePassthrough.OnPassthroughSessionDestroyDelegate">delegate</see> will be invoked when the current OpenXR Session is going to be destroyed.</param>
/// <param name="alpha">The alpha value of the passthrough layer within the range [0, 1] where 1 (Opaque) is default.</param>
/// <param name="compositionDepth">The composition depth relative to other composition layers if present where 0 is default.</param>
/// <param name="trackingToWorldSpace">Specify whether or not the position and rotation of the mesh transform have to be converted from tracking space to world space.</param>
/// <param name="convertFromUnityToOpenXR">Specify whether or not the parameters <paramref name="vertexBuffer"/>, <paramref name="indexBuffer"/>, <paramref name="meshPosition"/> and <paramref name="meshOrientation"/> have to be converted to OpenXR coordinate.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static XrResult CreateProjectedPassthrough(out XrPassthroughHTC passthrough, CompositionLayer.LayerType layerType,
[In, Out] Vector3[] vertexBuffer, [In, Out] int[] indexBuffer, //For Mesh
ProjectedPassthroughSpaceType spaceType, Vector3 meshPosition, Quaternion meshOrientation, Vector3 meshScale, //For Mesh Transform
VivePassthrough.OnPassthroughSessionDestroyDelegate onDestroyPassthroughSessionHandler = null,
float alpha = 1f, uint compositionDepth = 0, bool trackingToWorldSpace = true, bool convertFromUnityToOpenXR = true)
{
passthrough = 0;
XrResult res = XrResult.XR_ERROR_RUNTIME_FAILURE;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return res;
}
if (vertexBuffer.Length < 3 || indexBuffer.Length % 3 != 0) //Must have at least 3 vertices and complete triangles
{
ERROR("Mesh data invalid.");
return res;
}
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
#if UNITY_ANDROID
IntPtr.Zero,
#else
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
#endif
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PROJECTED_HTC
);
res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
if (res == XrResult.XR_SUCCESS)
{
var layer = new PassthroughLayer(passthrough, layerType);
var xrLayer = PassthroughLayer.MakeEmptyLayer();
xrLayer.passthrough = passthrough;
xrLayer.layerFlags = (UInt64)XrCompositionLayerFlagBits.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
xrLayer.space = 0;
xrLayer.color.alpha = alpha;
layer.SetLayer(xrLayer);
var xrMesh = PassthroughLayer.MakeMeshTransform();
xrMesh.time = XR_HTC_passthrough.Interop.GetFrameState().predictedDisplayTime;
xrMesh.baseSpace = XR_HTC_passthrough.Interop.GetTrackingSpace();
xrMesh.scale = new XrVector3f(meshScale.x, meshScale.y, meshScale.z);
layer.SetMeshTransform(xrMesh);
layersDict.Add(passthrough, layer);
}
if (res == XrResult.XR_SUCCESS)
{
SetPassthroughAlpha(passthrough, alpha);
SetProjectedPassthroughMesh(passthrough, vertexBuffer, indexBuffer, convertFromUnityToOpenXR);
SetProjectedPassthroughMeshTransform(passthrough, spaceType, meshPosition, meshOrientation, meshScale, trackingToWorldSpace, convertFromUnityToOpenXR);
}
return res;
}
/// <summary>
/// Creates a projected passthrough (i.e. Passthrough is only partially visible). Visible region of the projected passthrough is determined by the mesh and its transform.
/// Passthroughs will be destroyed automatically when the current <see cref="XrSession"/> is destroyed.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="layerType">The <see cref="LayerType"/> specifies whether the passthrough is an overlay or underlay.</param>
/// <param name="onDestroyPassthroughSessionHandler">A <see cref="VivePassthrough.OnPassthroughSessionDestroyDelegate">delegate</see> will be invoked when the current OpenXR Session is going to be destroyed.</param>
/// <param name="alpha">The alpha value of the passthrough layer within the range [0, 1] where 1 (Opaque) is default.</param>
/// <param name="compositionDepth">The composition depth relative to other composition layers if present where 0 is default.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static XrResult CreateProjectedPassthrough(out XrPassthroughHTC passthrough, CompositionLayer.LayerType layerType, VivePassthrough.OnPassthroughSessionDestroyDelegate onDestroyPassthroughSessionHandler = null, float alpha = 1f, uint compositionDepth = 0)
{
passthrough = 0;
XrResult res = XrResult.XR_ERROR_RUNTIME_FAILURE;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return res;
}
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
#if UNITY_ANDROID
IntPtr.Zero,
#else
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
#endif
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PROJECTED_HTC
);
res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
if (res == XrResult.XR_SUCCESS)
{
var layer = new PassthroughLayer(passthrough, layerType);
var xrLayer = PassthroughLayer.MakeEmptyLayer();
xrLayer.passthrough = passthrough;
xrLayer.layerFlags = (UInt64)XrCompositionLayerFlagBits.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
xrLayer.color.alpha = alpha;
layer.SetLayer(xrLayer);
var xrMesh = PassthroughLayer.MakeMeshTransform();
layer.SetMeshTransform(xrMesh, true);
layersDict.Add(passthrough, layer);
}
if (res == XrResult.XR_SUCCESS)
{
SetPassthroughAlpha(passthrough, alpha);
}
return res;
}
private static void SubmitLayer()
{
passthroughFeature.SubmitLayers(layersDict.Values.ToList());
}
/// <summary>
/// To Destroying a passthrough.
/// You should call this function when the <see cref="VivePassthrough.OnPassthroughSessionDestroyDelegate">delegate</see> is invoked.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <returns>XR_SUCCESS for success.</returns>
public static XrResult DestroyPassthrough(XrPassthroughHTC passthrough)
{
XrResult res = XrResult.XR_ERROR_RUNTIME_FAILURE;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return res;
}
if (!passthroughFeature.PassthroughList.Contains(passthrough))
{
ERROR("Passthrough to be destroyed not found");
return res;
}
if (layersDict.ContainsKey(passthrough))
{
XR_HTC_passthrough.xrDestroyPassthroughHTC(passthrough);
var layer = layersDict[passthrough];
layer.Dispose();
layersDict.Remove(passthrough);
}
SubmitLayer();
res = XrResult.XR_SUCCESS;
return res;
}
/// <summary>
/// Modifies the opacity of a specific passthrough layer.
/// Can be used for both Planar and Projected passthroughs.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="alpha">The alpha value of the passthrough layer within the range [0, 1] where 1 (Opaque) is default.</param>
/// <param name="autoClamp">
/// Specify whether out of range alpha values should be clamped automatically.
/// When set to true, the function will clamp and apply the alpha value automatically.
/// When set to false, the function will return false if the alpha is out of range.
/// Default is true.
/// </param>
/// <returns>XR_SUCCESS for success.</returns>
public static bool SetPassthroughAlpha(XrPassthroughHTC passthrough, float alpha, bool autoClamp = true)
{
bool ret = false;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return ret;
}
if (layersDict.ContainsKey(passthrough))
{
var layer = layersDict[passthrough];
var xrLayer = layer.GetLayer();
xrLayer.color.alpha = alpha;
layer.SetLayer(xrLayer);
SubmitLayer();
ret = true;
}
else
ret = false;
return ret;
}
/// <summary>
/// Modifies the mesh data of a projected passthrough layer.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="vertexBuffer">Positions of the vertices in the mesh.</param>
/// <param name="indexBuffer">List of triangles represented by indices into the <paramref name="vertexBuffer"/>.</param>
/// <param name="convertFromUnityToOpenXR">Specify whether or not the parameters <paramref name="vertexBuffer"/>, <paramref name="indexBuffer"/>, <paramref name="meshPosition"/> and <paramref name="meshOrientation"/> have to be converted to OpenXR coordinate.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static bool SetProjectedPassthroughMesh(XrPassthroughHTC passthrough, [In, Out] Vector3[] vertexBuffer, [In, Out] int[] indexBuffer, bool convertFromUnityToOpenXR = true)
{
bool ret = false;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return ret;
}
if (vertexBuffer.Length < 3 || indexBuffer.Length % 3 != 0) //Must have at least 3 vertices and complete triangles
{
ERROR("Mesh data invalid.");
return ret;
}
if (layersDict[passthrough] == null)
{
ERROR("Passthrough layer not found.");
return ret;
}
var layer = layersDict[passthrough];
var xrMesh = layer.GetMesh();
layer.SetMeshData(ref xrMesh, vertexBuffer, indexBuffer, convertFromUnityToOpenXR);
layer.SetMeshTransform(xrMesh);
SubmitLayer();
return true;
}
/// <summary>
/// Modifies the mesh transform of a projected passthrough layer.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="spaceType">The projected passthrough's <see cref="ProjectedPassthroughSpaceType"/></param>
/// <param name="meshPosition">Position of the mesh.</param>
/// <param name="meshOrientation">Orientation of the mesh.</param>
/// <param name="meshScale">Scale of the mesh.</param>
/// <param name="trackingToWorldSpace">Specify whether or not the position and rotation of the mesh transform have to be converted from tracking space to world space.</param>
/// <param name="convertFromUnityToOpenXR">Specify whether or not the parameters <paramref name="vertexBuffer"/>, <paramref name="indexBuffer"/>, <paramref name="meshPosition"/> and <paramref name="meshOrientation"/> have to be converted to OpenXR coordinate.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static bool SetProjectedPassthroughMeshTransform(XrPassthroughHTC passthrough, ProjectedPassthroughSpaceType spaceType, Vector3 meshPosition, Quaternion meshOrientation, Vector3 meshScale, bool trackingToWorldSpace = true, bool convertFromUnityToOpenXR = true)
{
bool ret = false;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return ret;
}
Vector3 trackingSpaceMeshPosition = meshPosition;
Quaternion trackingSpaceMeshRotation = meshOrientation;
TrackingSpaceOrigin currentTrackingSpaceOrigin = TrackingSpaceOrigin.Instance;
if (currentTrackingSpaceOrigin != null && trackingToWorldSpace) //Apply origin correction to the mesh pose
{
Matrix4x4 trackingSpaceOriginTRS = Matrix4x4.TRS(currentTrackingSpaceOrigin.transform.position, currentTrackingSpaceOrigin.transform.rotation, Vector3.one);
Matrix4x4 worldSpaceLayerPoseTRS = Matrix4x4.TRS(meshPosition, meshOrientation, Vector3.one);
Matrix4x4 trackingSpaceLayerPoseTRS = trackingSpaceOriginTRS.inverse * worldSpaceLayerPoseTRS;
trackingSpaceMeshPosition = trackingSpaceLayerPoseTRS.GetColumn(3); //4th Column of TRS Matrix is the position
trackingSpaceMeshRotation = Quaternion.LookRotation(trackingSpaceLayerPoseTRS.GetColumn(2), trackingSpaceLayerPoseTRS.GetColumn(1));
}
XrPosef meshXrPose;
meshXrPose.position = OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR);
meshXrPose.orientation = OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR);
XrVector3f meshXrScale = OpenXRHelper.ToOpenXRVector(meshScale, false);
if (layersDict[passthrough] == null)
{
ERROR("Passthrough layer not found.");
return ret;
}
var layer = layersDict[passthrough];
var xrMesh = layer.GetMesh();
xrMesh.pose = meshXrPose;
xrMesh.scale = meshXrScale;
xrMesh.time = XR_HTC_passthrough.Interop.GetFrameState().predictedDisplayTime;
xrMesh.baseSpace = passthroughFeature.GetXrSpaceFromSpaceType(spaceType);
layer.SetMeshTransform(xrMesh);
SubmitLayer();
return true;
}
/// <summary>
/// Modifies layer type and composition depth of a passthrough layer.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="layerType">The <see cref="LayerType"/> specifies whether the passthrough is an overlay or underlay.</param>
/// <param name="compositionDepth">The composition depth relative to other composition layers if present where 0 is default.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static bool SetPassthroughLayerType(XrPassthroughHTC passthrough, CompositionLayer.LayerType layerType, uint compositionDepth = 0)
{
bool ret = false;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return ret;
}
if (layersDict[passthrough] == null)
{
ERROR("Passthrough layer not found.");
return ret;
}
var layer = layersDict[passthrough];
layer.LayerType = layerType;
layer.Depth = (int)compositionDepth;
SubmitLayer();
return true;
}
/// <summary>
/// Modifies the space of a projected passthrough layer.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="spaceType">The projected passthrough's <see cref="ProjectedPassthroughSpaceType"/></param>
/// <returns>XR_SUCCESS for success.</returns>
public static bool SetProjectedPassthroughSpaceType(XrPassthroughHTC passthrough, ProjectedPassthroughSpaceType spaceType)
{
bool ret = false;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return ret;
}
if (layersDict[passthrough] == null)
{
ERROR("Passthrough layer not found.");
return ret;
}
var layer = layersDict[passthrough];
var xrMesh = layer.GetMesh();
xrMesh.baseSpace = passthroughFeature.GetXrSpaceFromSpaceType(spaceType);
layer.SetMeshTransform(xrMesh);
SubmitLayer();
return true;
}
/// <summary>
/// Modifies the mesh position of a projected passthrough layer.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="meshPosition">Position of the mesh.</param>
/// <param name="trackingToWorldSpace">Specify whether or not the position and rotation of the mesh transform have to be converted from tracking space to world space.</param>
/// <param name="convertFromUnityToOpenXR">Specify whether or not the parameters <paramref name="vertexBuffer"/>, <paramref name="indexBuffer"/>, <paramref name="meshPosition"/> and <paramref name="meshOrientation"/> have to be converted to OpenXR coordinate.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static bool SetProjectedPassthroughMeshPosition(XrPassthroughHTC passthrough, Vector3 meshPosition, bool trackingToWorldSpace = true, bool convertFromUnityToOpenXR = true)
{
bool ret = false;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return ret;
}
Vector3 trackingSpaceMeshPosition = meshPosition;
TrackingSpaceOrigin currentTrackingSpaceOrigin = TrackingSpaceOrigin.Instance;
if (currentTrackingSpaceOrigin != null && trackingToWorldSpace) //Apply origin correction to the mesh pose
{
Matrix4x4 trackingSpaceOriginTRS = Matrix4x4.TRS(currentTrackingSpaceOrigin.transform.position, Quaternion.identity, Vector3.one);
Matrix4x4 worldSpaceLayerPoseTRS = Matrix4x4.TRS(meshPosition, Quaternion.identity, Vector3.one);
Matrix4x4 trackingSpaceLayerPoseTRS = trackingSpaceOriginTRS.inverse * worldSpaceLayerPoseTRS;
trackingSpaceMeshPosition = trackingSpaceLayerPoseTRS.GetColumn(3); //4th Column of TRS Matrix is the position
}
if (layersDict[passthrough] == null)
{
ERROR("Passthrough layer not found.");
return ret;
}
var layer = layersDict[passthrough];
var xrMesh = layer.GetMesh();
xrMesh.pose.position = OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR);
layer.SetMeshTransform(xrMesh);
SubmitLayer();
return true;
}
/// <summary>
/// Modifies the mesh orientation of a projected passthrough layer.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="meshOrientation">Orientation of the mesh.</param>
/// <param name="trackingToWorldSpace">Specify whether or not the position and rotation of the mesh transform have to be converted from tracking space to world space.</param>
/// <param name="convertFromUnityToOpenXR">Specify whether or not the parameters <paramref name="vertexBuffer"/>, <paramref name="indexBuffer"/>, <paramref name="meshPosition"/> and <paramref name="meshOrientation"/> have to be converted to OpenXR coordinate.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static bool SetProjectedPassthroughMeshOrientation(XrPassthroughHTC passthrough, Quaternion meshOrientation, bool trackingToWorldSpace = true, bool convertFromUnityToOpenXR = true)
{
bool ret = false;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return ret;
}
Quaternion trackingSpaceMeshRotation = meshOrientation;
TrackingSpaceOrigin currentTrackingSpaceOrigin = TrackingSpaceOrigin.Instance;
if (currentTrackingSpaceOrigin != null && trackingToWorldSpace) //Apply origin correction to the mesh pose
{
Matrix4x4 trackingSpaceOriginTRS = Matrix4x4.TRS(Vector3.zero, currentTrackingSpaceOrigin.transform.rotation, Vector3.one);
Matrix4x4 worldSpaceLayerPoseTRS = Matrix4x4.TRS(Vector3.zero, meshOrientation, Vector3.one);
Matrix4x4 trackingSpaceLayerPoseTRS = trackingSpaceOriginTRS.inverse * worldSpaceLayerPoseTRS;
trackingSpaceMeshRotation = Quaternion.LookRotation(trackingSpaceLayerPoseTRS.GetColumn(2), trackingSpaceLayerPoseTRS.GetColumn(1));
}
if (layersDict[passthrough] == null)
{
ERROR("Passthrough layer not found.");
return ret;
}
var layer = layersDict[passthrough];
var xrMesh = layer.GetMesh();
xrMesh.pose.orientation = OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR);
layer.SetMeshTransform(xrMesh);
SubmitLayer();
return true;
}
/// <summary>
/// Modifies the mesh scale of a passthrough layer.
/// </summary>
/// <param name="passthrough">The created <see cref="XrPassthroughHTC"/></param>
/// <param name="meshScale">Scale of the mesh.</param>
/// <returns>XR_SUCCESS for success.</returns>
public static bool SetProjectedPassthroughScale(XrPassthroughHTC passthrough, Vector3 meshScale)
{
bool ret = false;
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return ret;
}
if (layersDict[passthrough] == null)
{
ERROR("Passthrough layer not found.");
return ret;
}
var layer = layersDict[passthrough];
var xrMesh = layer.GetMesh();
xrMesh.scale = OpenXRHelper.ToOpenXRVector(meshScale, false);
layer.SetMeshTransform(xrMesh);
SubmitLayer();
return true;
}
/// <summary>
/// To get the list of IDs of active passthrough layers.
/// </summary>
/// <returns>
/// The a copy of the list of IDs of active passthrough layers.
/// </returns>
public static List<XrPassthroughHTC> GetCurrentPassthroughLayerIDs()
{
if (!AssertFeature())
{
ERROR("HTC_Passthrough feature instance not found.");
return null;
}
return passthroughFeature.PassthroughList;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9bfb5ce0ea49a0a4cb998989754606c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b2e0dce69c4834e47b59f25575269566
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f9a059009a1d2414c94f3b9c7e46dfe9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,381 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using VIVE.OpenXR.PlaneDetection;
using static VIVE.OpenXR.PlaneDetection.VivePlaneDetection;
namespace VIVE.OpenXR.Toolkits.PlaneDetection
{
/// <summary>
/// The detected plane's data.
/// See <see cref="VivePlaneDetection.XrPlaneDetectorLocationEXT"/>
/// </summary>
public class PlaneDetectorLocation
{
public ulong planeId;
public XrSpaceLocationFlags locationFlags;
public Pose pose;
public Vector3 size; // Only width(X) and height(Y) are valid, Z is always 0.
public XrPlaneDetectorOrientationEXT orientation;
public XrPlaneDetectorSemanticTypeEXT semanticType;
public uint polygonBufferCount;
public XrPlaneDetectorLocationEXT locationRaw;
}
/// <summary>
/// The information for creating Mesh.
/// Plane's normal is facing +Z in Unity's coordination.
/// </summary>
public class Plane
{
public Vector3 scale; // Only width(X) and height(Y) are valid, Z is always 1.
public Vector3 center; // Should always be Vector3.Zero.
public Vector3[] verticesRaw; // The original vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/>
public Vector3[] verticesGenerated; // generated vertices for creating Mesh.
public Vector2[] uvsGenerated;
public int[] indicesGenerated;
/// <summary>
/// According to the input vertices, calculate the rectangle, and create the plane.
/// </summary>
/// <param name="vertices">The vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/></param>
public static Plane CreateFromVertices(Vector2[] vertices)
{
// Assume the polygon is a rectangle.
if (vertices.Length != 4)
return null;
// Check the size from vertices.
Vector2 min, max;
min = max = new Vector2(vertices[0].x, vertices[0].y);
for (int i = 1; i < vertices.Length; i++)
{
min.x = Mathf.Min(min.x, vertices[i].x);
min.y = Mathf.Min(min.y, vertices[i].y);
max.x = Mathf.Max(max.x, vertices[i].x);
max.y = Mathf.Max(max.y, vertices[i].y);
}
var verticesRaw = new Vector3[vertices.Length];
for (int i = 0; i < vertices.Length; i++)
verticesRaw[i] = new Vector3(vertices[i].x, 0, vertices[i].y);
var verticesGenerated = new Vector3[4];
verticesGenerated[0] = new Vector3(min.x, min.y, 0);
verticesGenerated[1] = new Vector3(max.x, min.y, 0);
verticesGenerated[2] = new Vector3(min.x, max.y, 0);
verticesGenerated[3] = new Vector3(max.x, max.y, 0);
var indicesGenerated = new int[] { 0, 3, 2, 0, 1, 3 };
var uvsGenerated = new Vector2[4];
uvsGenerated[0] = new Vector2(0, 0);
uvsGenerated[1] = new Vector2(1, 0);
uvsGenerated[2] = new Vector2(0, 1);
uvsGenerated[3] = new Vector2(1, 1);
return new Plane()
{
scale = max - min,
center = (max + min) / 2,
verticesRaw = verticesRaw,
verticesGenerated = verticesGenerated,
indicesGenerated = indicesGenerated,
uvsGenerated = uvsGenerated
};
}
}
/// <summary>
/// The PlaneDetector is created by <see cref="PlaneDetectionManager.CreatePlaneDetector" />.
/// </summary>
public class PlaneDetector
{
IntPtr planeDetector = IntPtr.Zero;
VivePlaneDetection feature = null;
/// <summary>
/// Should not create PlaneDetector directly. Use <see cref="PlaneDetectionManager.CreatePlaneDetector" /> instead.
/// </summary>
/// <param name="pd">The native handle of plane detector</param>
/// <param name="f">the feature</param>
internal PlaneDetector(IntPtr pd, VivePlaneDetection f)
{
feature = f;
planeDetector = pd;
}
/// <summary>
/// Get the raw plane detector handle. See <see cref="VivePlaneDetection.CreatePlaneDetector"/>
/// </summary>
/// <returns>The raw plane detector handle</returns>
public IntPtr GetDetectorRaw()
{
return planeDetector;
}
/// <summary>
/// Begin detect plane. In VIVE's implementation, planes are predefined in Room Setup.
/// However you have to call this function to get the plane information.
/// See <see cref="VivePlaneDetection.BeginPlaneDetection"/>
/// </summary>
public XrResult BeginPlaneDetection()
{
if (feature == null)
return XrResult.XR_ERROR_FEATURE_UNSUPPORTED;
Debug.Log("BeginPlaneDetection()");
var beginInfo = feature.MakeGetAllXrPlaneDetectorBeginInfoEXT();
return feature.BeginPlaneDetection(planeDetector, beginInfo);
}
/// <summary>
/// Get the state of plane detection.
/// See <see cref="VivePlaneDetection.GetPlaneDetectionState"/>
/// </summary>
/// <returns></returns>
public XrPlaneDetectionStateEXT GetPlaneDetectionState()
{
if (feature == null)
return XrPlaneDetectionStateEXT.NONE_EXT;
Debug.Log("GetPlaneDetectionState()");
XrPlaneDetectionStateEXT state = XrPlaneDetectionStateEXT.NONE_EXT;
feature.GetPlaneDetectionState(planeDetector, ref state);
return state;
}
/// <summary>
/// Get result of plane detection.
/// See <see cref="VivePlaneDetection.GetPlaneDetections"/>
/// </summary>
/// <param name="locations">The detected planes.</param>
/// <returns></returns>
public XrResult GetPlaneDetections(out List<PlaneDetectorLocation> locations)
{
locations = null;
if (feature == null)
return XrResult.XR_ERROR_FEATURE_UNSUPPORTED;
Debug.Log("GetPlaneDetections()");
XrPlaneDetectorGetInfoEXT info = new XrPlaneDetectorGetInfoEXT
{
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_GET_INFO_EXT,
baseSpace = feature.GetTrackingSpace(),
time = feature.GetPredictTime(),
};
XrPlaneDetectorLocationsEXT locationsRaw = new XrPlaneDetectorLocationsEXT
{
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_LOCATIONS_EXT,
planeLocationCapacityInput = 0,
planeLocationCountOutput = 0,
planeLocations = IntPtr.Zero,
};
var ret = feature.GetPlaneDetections(planeDetector, ref info, ref locationsRaw);
if (ret != XrResult.XR_SUCCESS || locationsRaw.planeLocationCountOutput == 0)
return ret;
Debug.Log("GetPlaneDetections() locations.planeLocationCountOutput: " + locationsRaw.planeLocationCountOutput);
var locationsArray = new XrPlaneDetectorLocationEXT[locationsRaw.planeLocationCountOutput];
var locationsPtr = MemoryTools.MakeRawMemory(locationsArray);
locationsRaw.planeLocationCapacityInput = locationsRaw.planeLocationCountOutput;
locationsRaw.planeLocationCountOutput = 0;
locationsRaw.planeLocations = locationsPtr;
ret = feature.GetPlaneDetections(planeDetector, ref info, ref locationsRaw);
if (ret != XrResult.XR_SUCCESS)
{
MemoryTools.ReleaseRawMemory(locationsPtr);
return ret;
}
MemoryTools.CopyFromRawMemory(locationsArray, locationsPtr);
locations = new List<PlaneDetectorLocation>();
// The plane's neutral pose is horizontal, and not like the plane pose in unity which is vertical.
// Therefore, we wil perform a rotation to convert from the OpenXR's forward to unity's forward.
// In Unity, the rotation applied order is in ZXY order.
Quaternion forward = Quaternion.Euler(-90, 180, 0);
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
{
XrPlaneDetectorLocationEXT location = locationsArray[i];
PlaneDetectorLocation pdl = new PlaneDetectorLocation
{
planeId = location.planeId,
locationFlags = location.locationFlags,
pose = new Pose(OpenXRHelper.ToUnityVector(location.pose.position), OpenXRHelper.ToUnityQuaternion(location.pose.orientation) * forward),
// Because the pose is converted, we will apply extent to X and Y.
size = new Vector3(location.extents.width, location.extents.height, 0),
orientation = location.orientation,
semanticType = location.semanticType,
polygonBufferCount = location.polygonBufferCount,
locationRaw = location,
};
locations.Add(pdl);
}
MemoryTools.ReleaseRawMemory(locationsPtr);
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
{
var location = locations[i];
Debug.Log("GetPlaneDetections() location.planeId: " + location.planeId);
Debug.Log("GetPlaneDetections() location.locationFlags: " + location.locationFlags);
Debug.Log("GetPlaneDetections() location.pose.position: " + location.pose.position);
Debug.Log("GetPlaneDetections() location.pose.rotation: " + location.pose.rotation);
Debug.Log("GetPlaneDetections() location.pose.rotation.eulerAngles: " + location.pose.rotation.eulerAngles);
var rot = location.locationRaw.pose.orientation;
Debug.Log($"GetPlaneDetections() locationRaw.pose.rotation: {rot.x}, {rot.y}, {rot.z}, {rot.w}");
}
return ret;
}
/// <summary>
/// Get the vertices of the plane from extension. Because there is no triangle
/// information from extension, it is hard to generate a mesh from only these vertices.
/// However VIVE only have rectangle plane. In this function, it will return the
/// <see cref="Plane"/> class which contains generated information for creating Mesh.
/// </summary>
/// <param name="planeId">The planeId from <see cref="PlaneDetectorLocation"/></param>
/// <returns>The information for creating Mesh.</returns>
public Plane GetPlane(ulong planeId)
{
if (feature == null)
return null;
XrPlaneDetectorPolygonBufferEXT polygonBuffer = new XrPlaneDetectorPolygonBufferEXT
{
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_POLYGON_BUFFER_EXT,
vertexCapacityInput = 0,
vertexCountOutput = 0,
vertices = IntPtr.Zero,
};
var ret = feature.GetPlanePolygonBuffer(planeDetector, planeId, 0, ref polygonBuffer);
Debug.Log("GetPlane() polygonBuffer.vertexCountOutput: " + polygonBuffer.vertexCountOutput);
if (ret != XrResult.XR_SUCCESS || polygonBuffer.vertexCountOutput == 0)
return null;
var verticesArray = new Vector2[polygonBuffer.vertexCountOutput];
var verticesPtr = MemoryTools.MakeRawMemory(verticesArray);
polygonBuffer.vertexCapacityInput = polygonBuffer.vertexCountOutput;
polygonBuffer.vertexCountOutput = 0;
polygonBuffer.vertices = verticesPtr;
if (feature.GetPlanePolygonBuffer(planeDetector, planeId, 0, ref polygonBuffer) != XrResult.XR_SUCCESS)
{
MemoryTools.ReleaseRawMemory(verticesPtr);
return null;
}
MemoryTools.CopyFromRawMemory(verticesArray, verticesPtr);
MemoryTools.ReleaseRawMemory(verticesPtr);
for (int j = 0; j < verticesArray.Length; j++)
{
var v = verticesArray[j];
Debug.Log($"GetPlane() verticesArray[{j}]: ({v.x}, {v.y})");
}
return Plane.CreateFromVertices(verticesArray);
}
}
public static class PlaneDetectionManager
{
static VivePlaneDetection feature = null;
static bool isSupported = false;
static void CheckFeature()
{
if (feature != null) return;
feature = OpenXRSettings.Instance.GetFeature<VivePlaneDetection>();
if (feature == null)
throw new NotSupportedException("PlaneDetection feature is not enabled");
}
/// <summary>
/// Helper to get the extention feature instance.
/// </summary>
/// <returns></returns>
public static VivePlaneDetection GetFeature()
{
try
{
CheckFeature();
}
catch (NotSupportedException)
{
Debug.LogWarning("PlaneDetection feature is not enabled");
return null;
}
return feature;
}
/// <summary>
/// Check if the extension is supported.
/// </summary>
/// <returns></returns>
public static bool IsSupported()
{
if (GetFeature() == null) return false;
if (isSupported) return true;
if (feature == null) return false;
bool ret = false;
if (feature.GetProperties(out var properties) == XrResult.XR_SUCCESS)
{
Debug.Log("PlaneDetection: IsSupported() properties.supportedFeatures: " + properties.supportedFeatures);
ret = (properties.supportedFeatures & CAPABILITY_PLANE_DETECTION_BIT_EXT) > 0;
isSupported = ret;
}
else
{
Debug.Log("PlaneDetection: IsSupported() GetSystemProperties failed.");
}
return ret;
}
/// <summary>
/// This is a helper function. Currently only one createInfo is available. Developepr should create their own
/// </summary>
/// <returns></returns>
public static XrPlaneDetectorCreateInfoEXT MakeXrPlaneDetectorCreateInfoEXT()
{
return new XrPlaneDetectorCreateInfoEXT
{
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_CREATE_INFO_EXT,
flags = XR_PLANE_DETECTOR_ENABLE_CONTOUR_BIT_EXT,
};
}
/// <summary>
/// Plane detector is a session of detect plane. You don't need to create multiple plane detector in VIVE's implemention. You need destroy it.
/// Should call <see cref="IsSupported"/> first to check if the feature is supported.
/// </summary>
/// <returns>PlaneDetector's handle</returns>
public static PlaneDetector CreatePlaneDetector()
{
CheckFeature();
if (feature == null)
return null;
if (IsSupported() == false)
return null;
var createInfo = MakeXrPlaneDetectorCreateInfoEXT();
var ret = feature.CreatePlaneDetector(createInfo, out var planeDetector);
if (ret != XrResult.XR_SUCCESS)
return null;
return new PlaneDetector(planeDetector, feature);
}
/// <summary>
/// Destroy the plane detector to release resource.
/// </summary>
public static void DestroyPlaneDetector(PlaneDetector pd)
{
if (pd == null)
return;
CheckFeature();
if (feature == null)
return;
feature.DestroyPlaneDetector(pd.GetDetectorRaw());
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f0a3b1ff9cb22f4293ff346f84a541e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ee430aebc144da341979174bcccf7e5a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 58897b8e0a7d7b24999d04f6ab923561
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f6f2f61f885577442b9a4cafff9a62e6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: GazePointerDef
m_Shader: {fileID: 4800000, guid: 105680c0299ff144b972446cb07a81c5, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 58d737ad5355ee04eb3f7f57de71df1e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 571cc732a01df91488bf9c7a5909fead
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,88 @@
Shader "VIVE/OpenXR/Raycast/GazePointerDef"
{
Properties{
_MainTex("Font Texture", 2D) = "white" {}
_Color("Text Color", Color) = (1,1,1,1)
_StencilComp("Stencil Comparison", Float) = 8
_Stencil("Stencil ID", Float) = 0
_StencilOp("Stencil Operation", Float) = 0
_StencilWriteMask("Stencil Write Mask", Float) = 255
_StencilReadMask("Stencil Read Mask", Float) = 255
_ColorMask("Color Mask", Float) = 15
}
SubShader {
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Lighting Off
Cull Off
ZTest Off
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform fixed4 _Color;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.color = v.color * _Color;
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
#ifdef UNITY_HALF_TEXEL_OFFSET
o.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = i.color;
col.a *= tex2D(_MainTex, i.texcoord).a;
clip (col.a - 0.01);
return col;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 105680c0299ff144b972446cb07a81c5
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 827931b60f4bb6c438a0a3473ead12a5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Collections.Generic;
using UnityEngine;
namespace VIVE.OpenXR.Raycast
{
public static class CanvasProvider
{
const string LOG_TAG = "VIVE.OpenXR.Raycast.CanvasProvider";
private static void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
private static List<Canvas> s_TargetCanvases = new List<Canvas>();
public static bool RegisterTargetCanvas(Canvas canvas)
{
if (canvas != null && !s_TargetCanvases.Contains(canvas))
{
DEBUG("RegisterTargetCanvas() " + canvas.gameObject.name);
s_TargetCanvases.Add(canvas);
return true;
}
return false;
}
public static bool RemoveTargetCanvas(Canvas canvas)
{
if (canvas != null && s_TargetCanvases.Contains(canvas))
{
DEBUG("RemoveTargetCanvas() " + canvas.gameObject.name);
s_TargetCanvases.Remove(canvas);
return true;
}
return false;
}
public static Canvas[] GetTargetCanvas() { return s_TargetCanvases.ToArray(); }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c68c9c4b6f2ba4843ac07957f9727968
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.InputSystem;
using System.Text;
namespace VIVE.OpenXR.Raycast
{
public class ControllerRaycastPointer : RaycastPointer
{
const string LOG_TAG = "VIVE.OpenXR.Raycast.ControllerRaycastPointer";
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#region Inspector
[SerializeField]
private InputActionReference m_IsTracked = null;
public InputActionReference IsTracked { get => m_IsTracked; set => m_IsTracked = value; }
[Tooltip("Keys for control.")]
[SerializeField]
private List<InputActionReference> m_ActionsKeys = new List<InputActionReference>();
public List<InputActionReference> ActionKeys { get { return m_ActionsKeys; } set { m_ActionsKeys = value; } }
bool getBool(InputActionReference actionReference)
{
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
if (actionReference.action.activeControl.valueType == typeof(bool))
return actionReference.action.ReadValue<bool>();
if (actionReference.action.activeControl.valueType == typeof(float))
return actionReference.action.ReadValue<float>() > 0;
}
return false;
}
[Tooltip("To show the ray anymore.")]
[SerializeField]
private bool m_AlwaysEnable = false;
public bool AlwaysEnable { get { return m_AlwaysEnable; } set { m_AlwaysEnable = value; } }
#endregion
#region MonoBehaviour overrides
protected override void Awake()
{
base.Awake();
}
protected override void Update()
{
base.Update();
if (!IsInteractable()) { return; }
UpdateButtonStates();
}
protected override void Start()
{
base.Start();
sb.Clear().Append("Start()"); DEBUG(sb);
}
private void OnApplicationPause(bool pause)
{
sb.Clear().Append("OnApplicationPause() ").Append(pause); DEBUG(sb);
}
#endregion
private bool IsInteractable()
{
bool enabled = RaycastSwitch.Controller.Enabled;
bool validPose = getBool(m_IsTracked);
#if UNITY_XR_OPENXR_1_6_0
m_Interactable = (m_AlwaysEnable || enabled); // The isTracked value of Pose will always be flase in OpenXR 1.6.0
#else
m_Interactable = (m_AlwaysEnable || enabled) && validPose;
#endif
if (printIntervalLog)
{
sb.Clear().Append("IsInteractable() enabled: ").Append(enabled)
.Append(", validPose: ").Append(validPose)
.Append(", m_AlwaysEnable: ").Append(m_AlwaysEnable)
.Append(", m_Interactable: ").Append(m_Interactable);
DEBUG(sb);
}
return m_Interactable;
}
private void UpdateButtonStates()
{
if (m_ActionsKeys == null) { return; }
down = false;
for (int i = 0; i < m_ActionsKeys.Count; i++)
{
if (!hold)
{
down |= getBool(m_ActionsKeys[i]);
}
}
hold = false;
for (int i = 0; i < m_ActionsKeys.Count; i++)
{
hold |= getBool(m_ActionsKeys[i]);
}
}
#region RaycastImpl Actions overrides
internal bool down = false, hold = false;
protected override bool OnDown()
{
return down;
}
protected override bool OnHold()
{
return hold;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e0ed4455b73284942bc407f2d8a98eee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,323 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.XR;
namespace VIVE.OpenXR.Raycast
{
public class GazeRaycastRing : RaycastRing
{
const string LOG_TAG = "VIVE.OpenXR.Raycast.GazeRaycastRing";
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#region Inspector
[SerializeField]
[Tooltip("Use Eye Tracking data for Gaze.")]
private bool m_EyeTracking = false;
public bool EyeTracking { get { return m_EyeTracking; } set { m_EyeTracking = value; } }
[SerializeField]
private InputActionReference m_EyePose = null;
public InputActionReference EyePose { get => m_EyePose; set => m_EyePose = value; }
bool getTracked(InputActionReference actionReference)
{
bool tracked = false;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.InputSystem.XR.PoseState))
#else
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.XR.OpenXR.Input.Pose))
#endif
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
tracked = actionReference.action.ReadValue<UnityEngine.InputSystem.XR.PoseState>().isTracked;
#else
tracked = actionReference.action.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>().isTracked;
#endif
if (printIntervalLog)
{
sb.Clear().Append("getTracked(").Append(tracked).Append(")");
DEBUG(sb);
}
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append("getTracked() invalid input: ").Append(value);
DEBUG(sb);
}
}
return tracked;
}
InputTrackingState getTrackingState(InputActionReference actionReference)
{
InputTrackingState state = InputTrackingState.None;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.InputSystem.XR.PoseState))
#else
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.XR.OpenXR.Input.Pose))
#endif
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
state = actionReference.action.ReadValue<UnityEngine.InputSystem.XR.PoseState>().trackingState;
#else
state = actionReference.action.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>().trackingState;
#endif
if (printIntervalLog)
{
sb.Clear().Append("getTrackingState(").Append(state).Append(")");
DEBUG(sb);
}
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append("getTrackingState() invalid input: ").Append(value);
DEBUG(sb);
}
}
return state;
}
Vector3 getDirection(InputActionReference actionReference)
{
Quaternion rotation = Quaternion.identity;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.InputSystem.XR.PoseState))
#else
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.XR.OpenXR.Input.Pose))
#endif
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
rotation = actionReference.action.ReadValue<UnityEngine.InputSystem.XR.PoseState>().rotation;
#else
rotation = actionReference.action.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>().rotation;
#endif
if (printIntervalLog)
{
sb.Clear().Append("getDirection(").Append(rotation.x).Append(", ").Append(rotation.y).Append(", ").Append(rotation.z).Append(", ").Append(rotation.w).Append(")");
DEBUG(sb);
}
return (rotation * Vector3.forward);
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append("getDirection() invalid input: ").Append(value);
DEBUG(sb);
}
}
return Vector3.forward;
}
Vector3 getOrigin(InputActionReference actionReference)
{
var origin = Vector3.zero;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.InputSystem.XR.PoseState))
#else
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.XR.OpenXR.Input.Pose))
#endif
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
origin = actionReference.action.ReadValue<UnityEngine.InputSystem.XR.PoseState>().position;
#else
origin = actionReference.action.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>().position;
#endif
if (printIntervalLog)
{
sb.Clear().Append("getOrigin(").Append(origin.x).Append(", ").Append(origin.y).Append(", ").Append(origin.z).Append(")");
DEBUG(sb);
}
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append("getOrigin() invalid input: ").Append(value);
DEBUG(sb);
}
}
return origin;
}
[Tooltip("Event triggered by gaze.")]
[SerializeField]
private GazeEvent m_InputEvent = GazeEvent.Down;
public GazeEvent InputEvent { get { return m_InputEvent; } set { m_InputEvent = value; } }
[Tooltip("Keys for control.")]
[SerializeField]
private List<InputActionReference> m_ActionsKeys = new List<InputActionReference>();
public List<InputActionReference> ActionKeys { get { return m_ActionsKeys; } set { m_ActionsKeys = value; } }
bool getButton(InputActionReference actionReference)
{
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
if (actionReference.action.activeControl.valueType == typeof(bool))
return actionReference.action.ReadValue<bool>();
if (actionReference.action.activeControl.valueType == typeof(float))
return actionReference.action.ReadValue<float>() > 0;
}
else
{
if (printIntervalLog)
{
sb.Clear().Append("getButton() invalid input: ").Append(value);
DEBUG(sb);
}
}
return false;
}
[SerializeField]
private bool m_AlwaysEnable = false;
public bool AlwaysEnable { get { return m_AlwaysEnable; } set { m_AlwaysEnable = value; } }
#endregion
#region MonoBehaviour overrides
protected override void Awake()
{
base.Awake();
}
private bool m_KeyDown = false;
protected override void Update()
{
base.Update();
if (!IsInteractable()) { return; }
m_KeyDown = ButtonPressed();
}
#endregion
private bool IsInteractable()
{
bool enabled = RaycastSwitch.Gaze.Enabled;
m_Interactable = (m_AlwaysEnable || enabled);
if (printIntervalLog)
{
sb.Clear().Append("IsInteractable() enabled: ").Append(enabled).Append(", m_AlwaysEnable: ").Append(m_AlwaysEnable);
DEBUG(sb);
}
return m_Interactable;
}
internal bool m_Down = false, m_Hold = false;
private bool ButtonPressed()
{
if (m_ActionsKeys == null) { return false; }
bool keyDown = false;
for (int i = 0; i < m_ActionsKeys.Count; i++)
{
var pressed = getButton(m_ActionsKeys[i]);
if (pressed)
{
sb.Clear().Append("ButtonPressed()").Append(m_ActionsKeys[i].name).Append(" is pressed.");
DEBUG(sb);
}
keyDown |= pressed;
}
m_Down = false;
if (!m_Hold) { m_Down |= keyDown; }
m_Hold = keyDown;
return m_Down;
}
protected override bool UseEyeData(out Vector3 direction)
{
bool isTracked = getTracked(m_EyePose);
InputTrackingState trackingState = getTrackingState(m_EyePose);
bool positionTracked = ((trackingState & InputTrackingState.Position) != 0);
bool rotationTracked = ((trackingState & InputTrackingState.Rotation) != 0);
bool useEye = m_EyeTracking
#if !UNITY_XR_OPENXR_1_6_0
&& isTracked // The isTracked value of Pose will always be flase in OpenXR 1.6.0
#endif
//&& positionTracked
&& rotationTracked;
getOrigin(m_EyePose);
direction = getDirection(m_EyePose);
if (printIntervalLog)
{
sb.Clear().Append("UseEyeData() m_EyeTracking: ").Append(m_EyeTracking)
.Append(", isTracked: ").Append(isTracked)
.Append(", trackingState: ").Append(trackingState)
.Append(", direction (").Append(direction.x).Append(", ").Append(direction.y).Append(", ").Append(direction.z).Append(")");
DEBUG(sb);
}
if (!useEye) { return base.UseEyeData(out direction); }
return useEye;
}
#region RaycastImpl Actions overrides
protected override bool OnDown()
{
if (m_InputEvent != GazeEvent.Down) { return false; }
bool down = false;
if (m_RingPercent >= 100 || m_KeyDown)
{
m_RingPercent = 0;
m_GazeOnTime = Time.unscaledTime;
down = true;
sb.Clear().Append("OnDown()"); DEBUG(sb);
}
return down;
}
protected override bool OnSubmit()
{
if (m_InputEvent != GazeEvent.Submit) { return false; }
bool submit = false;
if (m_RingPercent >= 100 || m_KeyDown)
{
m_RingPercent = 0;
m_GazeOnTime = Time.unscaledTime;
submit = true;
sb.Clear().Append("OnSubmit()"); DEBUG(sb);
}
return submit;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78ba47d171a9d604e87df3c8c0304fde
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,285 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
using UnityEngine.XR;
using System.Text;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
namespace VIVE.OpenXR.Raycast
{
public class HandRaycastPointer : RaycastPointer
{
const string LOG_TAG = "VIVE.OpenXR.Raycast.HandRaycastPointer ";
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#region Inspector
public bool IsLeft = false;
[Tooltip("To apply poses on the raycast pointer.")]
[SerializeField]
private bool m_UsePose = true;
public bool UsePose { get { return m_UsePose; } set { m_UsePose = value; } }
#if ENABLE_INPUT_SYSTEM
[SerializeField]
private InputActionReference m_AimPose = null;
public InputActionReference AimPose { get { return m_AimPose; } set { m_AimPose = value; } }
bool getAimTracked(InputActionReference actionReference)
{
bool tracked = false;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.InputSystem.XR.PoseState))
#else
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.XR.OpenXR.Input.Pose))
#endif
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
tracked = actionReference.action.ReadValue<UnityEngine.InputSystem.XR.PoseState>().isTracked;
#else
tracked = actionReference.action.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>().isTracked;
#endif
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getAimTracked(").Append(tracked).Append(")");
DEBUG(sb);
}
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getAimTracked() invalid input: ").Append(value);
DEBUG(sb);
}
}
return tracked;
}
InputTrackingState getAimTrackingState(InputActionReference actionReference)
{
InputTrackingState state = InputTrackingState.None;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.InputSystem.XR.PoseState))
#else
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.XR.OpenXR.Input.Pose))
#endif
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
state = actionReference.action.ReadValue<UnityEngine.InputSystem.XR.PoseState>().trackingState;
#else
state = actionReference.action.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>().trackingState;
#endif
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getAimTrackingState(").Append(state).Append(")");
DEBUG(sb);
}
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getAimTrackingState() invalid input: ").Append(value);
DEBUG(sb);
}
}
return state;
}
Vector3 getAimPosition(InputActionReference actionReference)
{
var position = Vector3.zero;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.InputSystem.XR.PoseState))
#else
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.XR.OpenXR.Input.Pose))
#endif
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
position = actionReference.action.ReadValue<UnityEngine.InputSystem.XR.PoseState>().position;
#else
position = actionReference.action.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>().position;
#endif
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getAimPosition(").Append(position.x).Append(", ").Append(position.y).Append(", ").Append(position.z).Append(")");
DEBUG(sb);
}
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getAimPosition() invalid input: ").Append(value);
DEBUG(sb);
}
}
return position;
}
Quaternion getAimRotation(InputActionReference actionReference)
{
var rotation = Quaternion.identity;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.InputSystem.XR.PoseState))
#else
if (actionReference.action.activeControl.valueType == typeof(UnityEngine.XR.OpenXR.Input.Pose))
#endif
{
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
rotation = actionReference.action.ReadValue<UnityEngine.InputSystem.XR.PoseState>().rotation;
#else
rotation = actionReference.action.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>().rotation;
#endif
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getAimRotation(").Append(rotation.x).Append(", ").Append(rotation.y).Append(", ").Append(rotation.z).Append(", ").Append(rotation.w).Append(")");
DEBUG(sb);
}
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getAimRotation() invalid input: ").Append(value);
DEBUG(sb);
}
}
return rotation;
}
[SerializeField]
private InputActionReference m_PinchStrength = null;
public InputActionReference PinchStrength { get => m_PinchStrength; set => m_PinchStrength = value; }
float getStrength(InputActionReference actionReference)
{
float strength = 0;
if (OpenXRHelper.VALIDATE(actionReference, out string value))
{
if (actionReference.action.activeControl.valueType == typeof(float))
{
strength = actionReference.action.ReadValue<float>();
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getStrength(").Append(strength).Append(")");
DEBUG(sb);
}
}
}
else
{
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("getStrength() invalid input: ").Append(value);
DEBUG(sb);
}
}
return strength;
}
#endif
[Tooltip("Pinch threshold to trigger events.")]
[SerializeField]
private float m_PinchThreshold = .5f;
public float PinchThreshold { get { return m_PinchThreshold; } set { m_PinchThreshold = value; } }
[SerializeField]
private bool m_AlwaysEnable = false;
public bool AlwaysEnable { get { return m_AlwaysEnable; } set { m_AlwaysEnable = value; } }
#endregion
#if ENABLE_INPUT_SYSTEM
protected override void Update()
{
base.Update();
if (!IsInteractable()) { return; }
pinchStrength = getStrength(m_PinchStrength);
if (m_UsePose)
{
transform.localPosition = getAimPosition(m_AimPose);
transform.localRotation = getAimRotation(m_AimPose);
}
}
private bool IsInteractable()
{
bool enabled = RaycastSwitch.Hand.Enabled;
bool isTracked = getAimTracked(m_AimPose);
InputTrackingState trackingState = getAimTrackingState(m_AimPose);
bool positionTracked = ((trackingState & InputTrackingState.Position) != 0);
bool rotationTracked = ((trackingState & InputTrackingState.Rotation) != 0);
m_Interactable = (m_AlwaysEnable || enabled)
#if !UNITY_XR_OPENXR_1_6_0
&& isTracked // The isTracked value of Pose will always be flase in OpenXR 1.6.0
#endif
&& positionTracked
&& rotationTracked;
if (printIntervalLog)
{
sb.Clear().Append(LOG_TAG).Append("IsInteractable() m_Interactable: ").Append(m_Interactable)
.Append(", enabled: ").Append(enabled)
.Append(", isTracked: ").Append(isTracked)
.Append(", positionTracked: ").Append(positionTracked)
.Append(", rotationTracked: ").Append(rotationTracked)
.Append(", m_AlwaysEnable: ").Append(m_AlwaysEnable);
DEBUG(sb);
}
return m_Interactable;
}
#endif
#region RaycastImpl Actions overrides
bool eligibleForClick = false;
float pinchStrength = 0;
protected override bool OnDown()
{
if (!eligibleForClick)
{
bool down = pinchStrength > m_PinchThreshold;
if (down)
{
eligibleForClick = true;
return true;
}
}
return false;
}
protected override bool OnHold()
{
bool hold = pinchStrength > m_PinchThreshold;
if (!hold)
eligibleForClick = false;
return hold;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d1efc78bbff2cc4eb930103b4111b67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
using UnityEngine.EventSystems;
namespace VIVE.OpenXR.Raycast
{
public class RaycastEventData : PointerEventData
{
/// <summary> The actor sends an event. </summary>
public GameObject Actor { get { return m_Actor; } }
private GameObject m_Actor = null;
public RaycastEventData(EventSystem eventSystem, GameObject actor)
: base(eventSystem)
{
m_Actor = actor;
}
}
/// <summary>
/// The object which receives events should implement this interface.
/// </summary>
public interface IHoverHandler : IEventSystemHandler
{
void OnHover(RaycastEventData eventData);
}
/// <summary>
/// Objects will use
/// ExecuteEvents.Execute (GameObject, BaseEventData, RayastEvents.pointerXXXXHandler)
/// to send XXXX events.
/// </summary>
public static class RaycastEvents
{
#region Event Executor of Hover
/// Use ExecuteEvents.Execute (GameObject, BaseEventData, RaycastEvents.pointerHoverHandler)
private static void HoverExecutor(IHoverHandler handler, BaseEventData eventData)
{
handler.OnHover(ExecuteEvents.ValidateEventData<RaycastEventData>(eventData));
}
public static ExecuteEvents.EventFunction<IHoverHandler> pointerHoverHandler
{
get { return HoverExecutor; }
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 34e90154ac6fdd64f85236ba9b967440
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,595 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace VIVE.OpenXR.Raycast
{
[DisallowMultipleComponent]
[RequireComponent(typeof(Camera))]
public class RaycastImpl : BaseRaycaster
{
#region Log
StringBuilder m_sb = null;
protected StringBuilder sb {
get {
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
const string LOG_TAG = "VIVE.OpenXR.Raycast.RaycastImpl";
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#endregion
#region Inspector
[SerializeField]
private bool m_IgnoreReversedGraphics = false;
public bool IgnoreReversedGraphics { get { return m_IgnoreReversedGraphics; } set { m_IgnoreReversedGraphics = value; } }
[SerializeField]
private float m_PhysicsCastDistance = 100;
public float PhysicsCastDistance { get { return m_PhysicsCastDistance; } set { m_PhysicsCastDistance = value; } }
[SerializeField]
private LayerMask m_PhysicsEventMask = ~0;
public LayerMask PhysicsEventMask { get { return m_PhysicsEventMask; } set { m_PhysicsEventMask = value; } }
#endregion
private Camera m_Camera = null;
public override Camera eventCamera { get { return m_Camera; } }
#region MonoBehaviour overrides
protected override void OnEnable()
{
sb.Clear().Append("OnEnable()"); DEBUG(sb);
base.OnEnable();
/// 1. Set up the event camera.
m_Camera = GetComponent<Camera>();
m_Camera.stereoTargetEye = StereoTargetEyeMask.None;
m_Camera.enabled = false;
/// 2. Set up the EventSystem.
if (EventSystem.current == null)
{
var eventSystemObject = new GameObject("EventSystem");
eventSystemObject.AddComponent<EventSystem>();
}
}
protected override void OnDisable()
{
sb.Clear().Append("OnDisable()"); DEBUG(sb);
base.OnDisable();
}
int printFrame = 0;
protected bool printIntervalLog = false;
protected bool m_Interactable = true;
protected virtual void Update()
{
printFrame++;
printFrame %= 300;
printIntervalLog = (printFrame == 0);
if (!m_Interactable) return;
/// Use the event camera and EventSystem to reset PointerEventData.
ResetEventData();
/// Update the raycast results
resultAppendList.Clear();
Raycast(pointerData, resultAppendList);
pointerData.pointerCurrentRaycast = currentRaycastResult;
/// Send events
HandleRaycastEvent();
}
#endregion
#region Raycast Result Handling
static readonly Comparison<RaycastResult> rrComparator = RaycastResultComparator;
private RaycastResult GetFirstRaycastResult(List<RaycastResult> results)
{
RaycastResult rr = default;
results.Sort(rrComparator);
for (int i = 0; i < results.Count; i++)
{
if (results[i].isValid)
{
rr = results[i];
break;
}
}
return rr;
}
private static int RaycastResultComparator(RaycastResult lhs, RaycastResult rhs)
{
if (lhs.module != rhs.module)
{
if (lhs.module.eventCamera != null && rhs.module.eventCamera != null && lhs.module.eventCamera.depth != rhs.module.eventCamera.depth)
{
// need to reverse the standard compareTo
if (lhs.module.eventCamera.depth < rhs.module.eventCamera.depth) { return 1; }
if (lhs.module.eventCamera.depth == rhs.module.eventCamera.depth) { return 0; }
return -1;
}
if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
{
return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);
}
if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
{
return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
}
}
if (lhs.sortingLayer != rhs.sortingLayer)
{
// Uses the layer value to properly compare the relative order of the layers.
var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
return rid.CompareTo(lid);
}
if (lhs.sortingOrder != rhs.sortingOrder)
{
return rhs.sortingOrder.CompareTo(lhs.sortingOrder);
}
if (!Mathf.Approximately(lhs.distance, rhs.distance))
{
return lhs.distance.CompareTo(rhs.distance);
}
if (lhs.depth != rhs.depth)
{
return rhs.depth.CompareTo(lhs.depth);
}
return lhs.index.CompareTo(rhs.index);
}
#endregion
#if UNITY_EDITOR
bool drawDebugLine = false;
#endif
#region Raycast
protected virtual bool UseEyeData(out Vector3 direction)
{
direction = Vector3.forward;
return false;
}
protected PointerEventData pointerData = null;
protected Vector3 pointerLocalOffset = Vector3.forward;
private Vector3 physicsWorldPosition = Vector3.zero;
private Vector2 graphicScreenPosition = Vector2.zero;
private void UpdatePointerDataPosition()
{
/// 1. Calculate the pointer offset in "local" space.
pointerLocalOffset = Vector3.forward;
if (UseEyeData(out Vector3 direction))
{
pointerLocalOffset = direction;
// Revise the offset from World space to Local space.
// OpenXR always uses World space.
pointerLocalOffset = Quaternion.Inverse(transform.rotation) * pointerLocalOffset;
}
/// 2. Calculate the pointer position in "world" space.
Vector3 rotated_offset = transform.rotation * pointerLocalOffset;
physicsWorldPosition = transform.position + rotated_offset;
graphicScreenPosition = m_Camera.WorldToScreenPoint(physicsWorldPosition);
// The graphicScreenPosition.x should be equivalent to (0.5f * Screen.width);
// The graphicScreenPosition.y should be equivalent to (0.5f * Screen.height);
}
private void ResetEventData()
{
if (pointerData == null) { pointerData = new RaycastEventData(EventSystem.current, gameObject); }
UpdatePointerDataPosition();
pointerData.position = graphicScreenPosition;
}
List<RaycastResult> resultAppendList = new List<RaycastResult>();
private RaycastResult currentRaycastResult = default;
protected GameObject raycastObject = null;
protected List<GameObject> s_raycastObjects = new List<GameObject>();
protected GameObject raycastObjectEx = null;
protected List<GameObject> s_raycastObjectsEx = new List<GameObject>();
/**
* Call to
* GraphicRaycast(Canvas canvas, Camera eventCamera, Vector2 screenPosition, List<RaycastResult> resultAppendList)
* PhysicsRaycast(Ray ray, Camera eventCamera, List<RaycastResult> resultAppendList)
**/
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
// --------------- Previous Results ---------------
raycastObjectEx = raycastObject;
s_raycastObjects.Clear();
// --------------- Graphic Raycast ---------------
Canvas[] canvases = CanvasProvider.GetTargetCanvas();
if (canvases.Length <= 0) { canvases = FindObjectsOfType<Canvas>(); } // note: GC.Alloc
for (int i = 0; i < canvases.Length; i++)
{
GraphicRaycast(canvases[i], m_Camera, eventData.position, resultAppendList);
}
// --------------- Physics Raycast ---------------
Ray ray = new Ray(transform.position, (physicsWorldPosition - transform.position));
PhysicsRaycast(ray, m_Camera, resultAppendList);
currentRaycastResult = GetFirstRaycastResult(resultAppendList);
// --------------- Current Results ---------------
raycastObject = currentRaycastResult.gameObject;
GameObject raycastTarget = currentRaycastResult.gameObject;
while (raycastTarget != null)
{
s_raycastObjects.Add(raycastTarget);
raycastTarget = (raycastTarget.transform.parent != null ? raycastTarget.transform.parent.gameObject : null);
}
#if UNITY_EDITOR
if (drawDebugLine)
{
Vector3 end = transform.position + (transform.forward * 100);
Debug.DrawLine(transform.position, end, Color.red, 1);
}
#endif
}
Ray ray = new Ray();
protected virtual void GraphicRaycast(Canvas canvas, Camera eventCamera, Vector2 screenPosition, List<RaycastResult> resultAppendList)
{
if (canvas == null)
return;
IList<Graphic> foundGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
if (foundGraphics == null || foundGraphics.Count == 0)
return;
int displayIndex = 0;
var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
displayIndex = canvas.targetDisplay;
else
displayIndex = currentEventCamera.targetDisplay;
if (currentEventCamera != null)
ray = currentEventCamera.ScreenPointToRay(screenPosition);
// Necessary for the event system
for (int i = 0; i < foundGraphics.Count; ++i)
{
Graphic graphic = foundGraphics[i];
// -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1) { continue; }
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, screenPosition, currentEventCamera)) { continue; }
if (currentEventCamera != null && currentEventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > currentEventCamera.farClipPlane) { continue; }
if (graphic.Raycast(screenPosition, currentEventCamera))
{
var go = graphic.gameObject;
bool appendGraphic = true;
if (m_IgnoreReversedGraphics)
{
if (currentEventCamera == null)
{
// If we dont have a camera we know that we should always be facing forward
var dir = go.transform.rotation * Vector3.forward;
appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
}
else
{
// If we have a camera compare the direction against the cameras forward.
var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane;
appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0;
}
}
if (appendGraphic)
{
float distance = 0;
Transform trans = go.transform;
Vector3 transForward = trans.forward;
if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
distance = 0;
else
{
// http://geomalgorithms.com/a06-_intersect-2.html
distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));
// Check to see if the go is behind the camera.
if (distance < 0)
continue;
}
resultAppendList.Add(new RaycastResult
{
gameObject = go,
module = this,
distance = distance,
screenPosition = screenPosition,
displayIndex = displayIndex,
index = resultAppendList.Count,
depth = graphic.depth,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
worldPosition = ray.origin + ray.direction * distance,
worldNormal = -transForward
});
}
}
}
}
Vector3 hitScreenPos = Vector3.zero;
Vector2 hitScreenPos2D = Vector2.zero;
static readonly RaycastHit[] hits = new RaycastHit[255];
protected virtual void PhysicsRaycast(Ray ray, Camera eventCamera, List<RaycastResult> resultAppendList)
{
var hitCount = Physics.RaycastNonAlloc(ray, hits, m_PhysicsCastDistance, m_PhysicsEventMask);
for (int i = 0; i < hitCount; ++i)
{
hitScreenPos = eventCamera.WorldToScreenPoint(hits[i].point);
hitScreenPos2D.x = hitScreenPos.x;
hitScreenPos2D.y = hitScreenPos.y;
resultAppendList.Add(new RaycastResult
{
gameObject = hits[i].collider.gameObject,
module = this,
distance = hits[i].distance,
worldPosition = hits[i].point,
worldNormal = hits[i].normal,
screenPosition = hitScreenPos2D,
index = resultAppendList.Count,
sortingLayer = 0,
sortingOrder = 0
});
}
}
#endregion
#region Event
private void CopyList(List<GameObject> src, List<GameObject> dst)
{
dst.Clear();
for (int i = 0; i < src.Count; i++)
dst.Add(src[i]);
}
private void ExitEnterHandler(ref List<GameObject> enterObjects, ref List<GameObject> exitObjects)
{
if (exitObjects.Count > 0)
{
for (int i = 0; i < exitObjects.Count; i++)
{
if (exitObjects[i] != null && !enterObjects.Contains(exitObjects[i]))
{
ExecuteEvents.Execute(exitObjects[i], pointerData, ExecuteEvents.pointerExitHandler);
sb.Clear().Append("ExitEnterHandler() Exit: ").Append(exitObjects[i].name); DEBUG(sb);
}
}
}
if (enterObjects.Count > 0)
{
for (int i = 0; i < enterObjects.Count; i++)
{
if (enterObjects[i] != null && !exitObjects.Contains(enterObjects[i]))
{
ExecuteEvents.Execute(enterObjects[i], pointerData, ExecuteEvents.pointerEnterHandler);
sb.Clear().Append("ExitEnterHandler() Enter: ").Append(enterObjects[i].name).Append(", camera: ").Append(pointerData.enterEventCamera); DEBUG(sb);
}
}
}
CopyList(enterObjects, exitObjects);
}
private void HoverHandler()
{
if (raycastObject != null && (raycastObject == raycastObjectEx))
{
if (printIntervalLog) { sb.Clear().Append("HoverHandler() Hover: ").Append(raycastObject.name); DEBUG(sb); }
ExecuteEvents.ExecuteHierarchy(raycastObject, pointerData, RaycastEvents.pointerHoverHandler);
}
}
private void DownHandler()
{
sb.Clear().Append("DownHandler()");DEBUG(sb);
if (raycastObject == null) { return; }
pointerData.pressPosition = pointerData.position;
pointerData.pointerPressRaycast = pointerData.pointerCurrentRaycast;
pointerData.pointerPress =
ExecuteEvents.ExecuteHierarchy(raycastObject, pointerData, ExecuteEvents.pointerDownHandler)
?? ExecuteEvents.GetEventHandler<IPointerClickHandler>(raycastObject);
sb.Clear().Append("DownHandler() Down: ").Append(pointerData.pointerPress).Append(", raycastObject: ").Append(raycastObject.name); DEBUG(sb);
// If Drag Handler exists, send initializePotentialDrag event.
pointerData.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(raycastObject);
if (pointerData.pointerDrag != null)
{
sb.Clear().Append("DownHandler() Send initializePotentialDrag to ").Append(pointerData.pointerDrag.name).Append(", current GameObject is ").Append(raycastObject.name); DEBUG(sb);
ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.initializePotentialDrag);
}
// press happened (even not handled) object.
pointerData.rawPointerPress = raycastObject;
// allow to send Pointer Click event
pointerData.eligibleForClick = true;
// reset the screen position of press, can be used to estimate move distance
pointerData.delta = Vector2.zero;
// current Down, reset drag state
pointerData.dragging = false;
pointerData.useDragThreshold = true;
// record the count of Pointer Click should be processed, clean when Click event is sent.
pointerData.clickCount = 1;
// set clickTime to current time of Pointer Down instead of Pointer Click.
// since Down & Up event should not be sent too closely. (< kClickInterval)
pointerData.clickTime = Time.unscaledTime;
}
private void UpHandler()
{
if (!pointerData.eligibleForClick && !pointerData.dragging)
{
// 1. no pending click
// 2. no dragging
// Mean user has finished all actions and do NOTHING in current frame.
return;
}
// raycastObject may be different with pointerData.pointerDrag so we don't check null
if (pointerData.pointerPress != null)
{
// In the frame of button is pressed -> unpressed, send Pointer Up
sb.Clear().Append("UpHandler() Send Pointer Up to ").Append(pointerData.pointerPress.name); DEBUG(sb);
ExecuteEvents.Execute(pointerData.pointerPress, pointerData, ExecuteEvents.pointerUpHandler);
}
if (pointerData.eligibleForClick)
{
GameObject objectToClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(raycastObject);
if (objectToClick != null)
{
if (objectToClick == pointerData.pointerPress)
{
// In the frame of button from being pressed to unpressed, send Pointer Click if Click is pending.
sb.Clear().Append("UpHandler() Send Pointer Click to ").Append(pointerData.pointerPress.name); DEBUG(sb);
ExecuteEvents.Execute(pointerData.pointerPress, pointerData, ExecuteEvents.pointerClickHandler);
}
else
{
sb.Clear().Append("UpHandler() pointer down object ").Append(pointerData.pointerPress).Append(" is different with click object ").Append(objectToClick.name); DEBUG(sb);
}
}
else
{
if (pointerData.dragging)
{
GameObject _pointerDrop = ExecuteEvents.GetEventHandler<IDropHandler>(raycastObject);
if (_pointerDrop == pointerData.pointerDrag)
{
// In next frame of button from being pressed to unpressed, send Drop and EndDrag if dragging.
sb.Clear().Append("UpHandler() Send Pointer Drop to ").Append(pointerData.pointerDrag); DEBUG(sb);
ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.dropHandler);
}
sb.Clear().Append("UpHandler() Send Pointer endDrag to ").Append(pointerData.pointerDrag); DEBUG(sb);
ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.endDragHandler);
pointerData.dragging = false;
}
}
}
// initializePotentialDrag was sent when IDragHandler exists.
pointerData.pointerDrag = null;
// Down of pending Click object.
pointerData.pointerPress = null;
// press happened (even not handled) object.
pointerData.rawPointerPress = null;
// clear pending state.
pointerData.eligibleForClick = false;
// Click is processed, clearcount.
pointerData.clickCount = 0;
// Up is processed thus clear the time limitation of Down event.
pointerData.clickTime = 0;
}
// After selecting an object over this duration, the drag action will be taken.
const float kTimeToDrag = 0.2f;
private void DragHandler()
{
if (Time.unscaledTime - pointerData.clickTime < kTimeToDrag) { return; }
if (pointerData.pointerDrag == null) { return; }
if (!pointerData.dragging)
{
sb.Clear().Append("DragHandler() Send BeginDrag to ").Append(pointerData.pointerDrag.name); DEBUG(sb);
ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.beginDragHandler);
pointerData.dragging = true;
}
else
{
ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.dragHandler);
}
}
private void SubmitHandler()
{
if (raycastObject == null) { return; }
sb.Clear().Append("SubmitHandler() Submit: ").Append(raycastObject.name); DEBUG(sb);
ExecuteEvents.ExecuteHierarchy(raycastObject, pointerData, ExecuteEvents.submitHandler);
}
// Do NOT allow event DOWN being sent multiple times during kClickInterval
// since UI element of Unity needs time to perform transitions.
const float kClickInterval = 0.2f;
private void HandleRaycastEvent()
{
ExitEnterHandler(ref s_raycastObjects, ref s_raycastObjectsEx);
HoverHandler();
bool submit = OnSubmit();
if (submit)
{
SubmitHandler();
return;
}
bool down = OnDown();
bool hold = OnHold();
if (!down && hold)
{
// Hold means to Drag.
DragHandler();
}
else if (Time.unscaledTime - pointerData.clickTime < kClickInterval)
{
// Delay new events until kClickInterval has passed.
}
else if (down && !pointerData.eligibleForClick)
{
// 1. Not Down -> Down
// 2. No pending Click should be procced.
DownHandler();
}
else if (!hold)
{
// 1. If Down before, send Up event and clear Down state.
// 2. If Dragging, send Drop & EndDrag event and clear Dragging state.
// 3. If no Down or Dragging state, do NOTHING.
UpHandler();
}
}
#endregion
#region Actions
protected virtual bool OnDown() { return false; }
protected virtual bool OnHold() { return false; }
protected virtual bool OnSubmit() { return false; }
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 74b5628be71247e4f80cb6ddc4d7025b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,210 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
namespace VIVE.OpenXR.Raycast
{
[RequireComponent(typeof(LineRenderer))]
public class RaycastPointer : RaycastImpl
{
const string LOG_TAG = "VIVE.OpenXR.Raycast.RaycastPointer";
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#region Inspector
[Tooltip("To show the ray which presents the casting direction.")]
[SerializeField]
private bool m_ShowRay = true;
public bool ShowRay { get { return m_ShowRay; } set { m_ShowRay = value; } }
[SerializeField]
private float m_RayStartWidth = 0.01f;
public float RayStartWidth { get { return m_RayStartWidth; } set { m_RayStartWidth = value; } }
[SerializeField]
private float m_RayEndWidth = 0.01f;
public float RayEndWidth { get { return m_RayEndWidth; } set { m_RayEndWidth = value; } }
[SerializeField]
private Material m_RayMaterial = null;
public Material RayMaterial { get { return m_RayMaterial; } set { m_RayMaterial = value; } }
[SerializeField]
private GameObject m_Pointer = null;
public GameObject Pointer { get { return m_Pointer; } set { m_Pointer = value; } }
#endregion
LineRenderer m_Ray = null;
Vector3 m_PointerScale = new Vector3(.15f, .15f, .15f);
#region MonoBehaviour overrides
protected override void OnEnable()
{
sb.Clear().Append("OnEnable()"); DEBUG(sb);
base.OnEnable();
if (m_Ray == null) { m_Ray = GetComponent<LineRenderer>(); }
if (m_Pointer != null)
{
m_PointerScale = m_Pointer.transform.localScale;
sb.Clear().Append("OnEnable() Get default pointer scale (").Append(m_PointerScale.x).Append(", ").Append(m_PointerScale.y).Append(", ").Append(m_PointerScale.z).Append(")"); DEBUG(sb);
}
}
protected override void OnDisable()
{
sb.Clear().Append("OnDisable()"); DEBUG(sb);
base.OnDisable();
ActivatePointer(false);
ActivateRay(false);
}
protected override void Update()
{
/// Raycast
base.Update();
if (printIntervalLog)
{
if (m_Ray != null)
sb.Clear().Append("Update() ").Append(gameObject.name).Append(", m_Ray enabled: ").Append(m_Ray.enabled); DEBUG(sb);
if (m_Pointer != null)
sb.Clear().Append("Update() ").Append(gameObject.name).Append(", m_Pointer enabled: ").Append(m_Pointer.activeSelf); DEBUG(sb);
}
if (!IsInteractable()) { return; }
/// Draw the ray and pointer.
DrawRayPointer();
}
#endregion
#region Ray and Pointer
private void ActivatePointer(bool active)
{
if (m_Pointer != null)
{
if (m_Pointer.activeSelf != active) { sb.Clear().Append("ActivatePointer() ").Append(gameObject.name).Append(" ").Append(active); DEBUG(sb); }
m_Pointer.SetActive(active);
}
}
private void ActivateRay(bool active)
{
if (m_Ray != null)
{
if (m_Ray.enabled != active) { sb.Clear().Append("ActivateRay() ").Append(gameObject.name).Append(" ").Append(active); DEBUG(sb); }
m_Ray.enabled = active;
}
}
private Vector3 GetIntersectionPosition(Camera cam, RaycastResult raycastResult)
{
if (cam == null)
return Vector3.zero;
float intersectionDistance = raycastResult.distance + cam.nearClipPlane;
Vector3 intersectionPosition = cam.transform.forward * intersectionDistance + cam.transform.position;
return intersectionPosition;
}
Vector3 rayStart = Vector3.zero, rayEnd = Vector3.zero;
const float kRayLengthMin = 0.5f;
private float m_RayLength = 10;
protected Vector3 pointerPosition = Vector3.zero;
private void DrawRayPointer()
{
Vector3 hit = GetIntersectionPosition(eventCamera, pointerData.pointerCurrentRaycast);
rayStart = transform.position;
if (raycastObject != null)
{
m_RayLength = Vector3.Distance(hit, rayStart);
m_RayLength = m_RayLength > kRayLengthMin ? m_RayLength : kRayLengthMin;
}
if (LockPointer())
{
Vector3 middle = new Vector3(0, 0, (m_RayLength - 0.2f) / 4);
DrawCurveRay(rayStart, middle, rayEnd, m_RayStartWidth, m_RayEndWidth, m_RayMaterial);
}
else
{
rayEnd = rayStart + (transform.forward * (m_RayLength - 0.2f));
pointerPosition = rayStart + (transform.forward * m_RayLength);
DrawRay(rayStart, rayEnd, m_RayStartWidth, m_RayEndWidth, m_RayMaterial);
}
DrawPointer(pointerPosition);
}
const float kPointerDistance = 10;
private void DrawPointer(Vector3 position)
{
if (m_Pointer == null) { return; }
m_Pointer.transform.position = position;
m_Pointer.transform.rotation = Camera.main.transform.rotation;
float distance = Vector3.Distance(position, Camera.main.transform.position);
m_Pointer.transform.localScale = m_PointerScale * (distance / kPointerDistance);
}
private void DrawRay(Vector3 start, Vector3 end, float startWidth, float endWidth, Material material)
{
if (m_Ray == null) { return; }
Vector3[] positions = new Vector3[] { start, end };
m_Ray.positionCount = positions.Length;
m_Ray.SetPositions(positions);
m_Ray.startWidth = startWidth;
m_Ray.endWidth = endWidth;
m_Ray.material = material;
m_Ray.useWorldSpace = true;
}
private void DrawCurveRay(Vector3 start, Vector3 middle, Vector3 end, float startWidth, float endWidth, Material material)
{
if (m_Ray == null) { m_Ray = GetComponent<LineRenderer>(); }
Vector3[] positions = GenerateBezierCurve3(50, start, middle, end);
m_Ray.positionCount = positions.Length;
m_Ray.SetPositions(positions);
m_Ray.startWidth = startWidth;
m_Ray.endWidth = endWidth;
m_Ray.material = material;
m_Ray.useWorldSpace = true;
}
Vector3[] GenerateBezierCurve2(int iteration, Vector3 start, Vector3 end)
{
Vector3[] points = new Vector3[iteration + 1];
for (int i = 0; i < iteration + 1; i++)
{
points.SetValue(start + ((end - start).normalized * (end - start).magnitude * i / iteration), i);
}
return points;
}
Vector3[] GenerateBezierCurve3(int iteration, Vector3 start, Vector3 middle, Vector3 end)
{
Vector3[] points1 = GenerateBezierCurve2(iteration, start, middle);
Vector3[] points2 = GenerateBezierCurve2(iteration, start, end);
Vector3[] points = new Vector3[iteration + 1];
for (int i = 0; i < iteration + 1; i++)
{
points.SetValue(points1[i] + ((points2[i] - points1[i]).normalized * (points2[i] - points1[i]).magnitude * i / iteration), i);
}
return points;
}
#endregion
private bool IsInteractable()
{
ActivatePointer(m_Interactable);
ActivateRay(m_Interactable && m_ShowRay);
return m_Interactable;
}
/// <summary> For DrawRayPointer(), controls whether locking the pointer or not. </summary>
protected virtual bool LockPointer()
{
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e8ff0b4ae49c1a4ea049f9e8afe0230
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,337 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Raycast
{
/// <summary>
/// To draw a ring pointer to indicate the gazed space.
/// </summary>
[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
public class RaycastRing : RaycastImpl
{
const string LOG_TAG = "VIVE.OpenXR.Raycast.RaycastRing";
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
public enum GazeEvent
{
Down = 0,
Submit = 1
}
#region Inspector
// ----------- Width of ring -----------
const float kRingWidthDefault = 0.005f;
const float kRingWidthMinimal = 0.001f;
[Tooltip("Set the width of the pointer's ring.")]
[SerializeField]
private float m_PointerRingWidth = kRingWidthDefault;
public float PointerRingWidth { get { return m_PointerRingWidth; } set { m_PointerRingWidth = value; } }
// ----------- Radius of inner circle -----------
const float kInnerCircleRadiusDefault = 0.005f;
const float kInnerCircleRadiusMinimal = 0.001f;
[Tooltip("Set the radius of the pointer's inner circle.")]
[SerializeField]
private float m_PointerCircleRadius = kInnerCircleRadiusDefault;
public float PointerCircleRadius { get { return m_PointerCircleRadius; } set { m_PointerCircleRadius = value; } }
// ----------- Z distance of ring -----------
const float kPointerDistanceDefault = 1;
const float kPointerDistanceMinimal = 0.1f;
[Tooltip("Set the z-coordinate of the pointer.")]
[SerializeField]
private float m_PointerDistance = kPointerDistanceDefault;
public float PointerDistance { get { return m_PointerDistance; } set { m_PointerDistance = value; } }
/// The offset from the pointer to the pointer-mounted object.
private Vector3 ringOffset = Vector3.zero;
/// The offset from the pointer to the pointer-mounted object in every frame.
private Vector3 ringFrameOffset = Vector3.zero;
/// The pointer world position.
private Vector3 ringWorldPosition = Vector3.zero;
// ----------- Color of ring -----------
/// Color of ring background.
[Tooltip("Set the ring background color.")]
[SerializeField]
private Color m_PointerColor = Color.white;
public Color PointerColor { get { return m_PointerColor; } set { m_PointerColor = value; } }
/// Color of ring foreground
[Tooltip("Set the ring foreground progess color.")]
[SerializeField]
private Color m_ProgressColor = new Color(0, 245, 255);
public Color ProgressColor { get { return m_ProgressColor; } set { m_ProgressColor = value; } }
// ----------- Material and Mesh -----------
private Mesh m_Mesh = null;
const string kPointerMaterial = "Materials/GazePointerDef";
[Tooltip("Empty for using the default material or set a customized material.")]
[SerializeField]
private Material m_PointerMaterial = null;
public Material PointerMaterial { get { return m_PointerMaterial; } set { m_PointerMaterial = value; } }
private Material pointerMaterialInstance = null;
private MeshFilter m_MeshFilter = null;
private MeshRenderer m_MeshRenderer = null;
const int kMaterialRenderQueueMin = 1000;
const int kMaterialRenderQueueMax = 5000;
/// The material's renderQueue.
[Tooltip("Set the Material's renderQueue.")]
[SerializeField]
private int m_PointerRenderQueue = kMaterialRenderQueueMax;
public int PointerRenderQueue { get { return m_PointerRenderQueue; } set { m_PointerRenderQueue = value; } }
/// The MeshRenderer's sortingOrder.
[Tooltip("Set the MeshRenderer's sortingOrder.")]
[SerializeField]
private int m_PointerSortingOrder = 32767;
public int PointerSortingOrder { get { return m_PointerSortingOrder; } set { m_PointerSortingOrder = value; } }
protected int m_RingPercent = 0;
public int RingPercent { get { return m_RingPercent; } set { m_RingPercent = value; } }
[Tooltip("Gaze timer to trigger gaze events.")]
[SerializeField]
private float m_TimeToGaze = 1.5f;
public float TimeToGaze { get { return m_TimeToGaze; } set { m_TimeToGaze = value; } }
private void ValidateParameters()
{
if (m_PointerRingWidth < kRingWidthMinimal)
m_PointerRingWidth = kRingWidthDefault;
if (m_PointerCircleRadius < kInnerCircleRadiusMinimal)
m_PointerCircleRadius = kInnerCircleRadiusDefault;
if (m_PointerDistance < kPointerDistanceMinimal)
m_PointerDistance = kPointerDistanceDefault;
if (m_PointerRenderQueue < kMaterialRenderQueueMin || m_PointerRenderQueue > kMaterialRenderQueueMax)
m_PointerRenderQueue = kMaterialRenderQueueMax;
}
#endregion
#region MonoBehaviour overrides
private bool mEnabled = false;
protected override void OnEnable()
{
base.OnEnable();
if (!mEnabled)
{
sb.Clear().Append("OnEnable()"); DEBUG(sb);
// 1. Texture or Mesh < Material < < MeshFilter < MeshRenderer, we don't use the texture.
if (m_Mesh == null)
m_Mesh = new Mesh();
if (m_Mesh != null)
m_Mesh.name = gameObject.name + " Mesh";
// 2. Load the Material RingUnlitTransparentMat.
if (m_PointerMaterial == null)
m_PointerMaterial = Resources.Load(kPointerMaterial) as Material;
if (m_PointerMaterial != null)
{
pointerMaterialInstance = Instantiate(m_PointerMaterial);
sb.Clear().Append("OnEnable() Loaded resource ").Append(pointerMaterialInstance.name); DEBUG(sb);
}
// 3. Get the MeshFilter.
m_MeshFilter = GetComponent<MeshFilter>();
// 4. Get the MeshRenderer.
m_MeshRenderer = GetComponent<MeshRenderer>();
m_MeshRenderer.sortingOrder = m_PointerSortingOrder;
m_MeshRenderer.material = pointerMaterialInstance;
m_MeshRenderer.material.renderQueue = PointerRenderQueue;
mEnabled = true;
}
}
protected override void OnDisable()
{
base.OnDisable();
if (mEnabled)
{
sb.Clear().Append("OnDisable()"); DEBUG(sb);
if (m_MeshFilter != null)
{
Mesh mesh = m_MeshFilter.mesh;
mesh.Clear();
}
Destroy(pointerMaterialInstance);
mEnabled = false;
}
}
protected override void Update()
{
base.Update();
if (!IsInteractable()) { return; }
ValidateParameters();
UpdatePointerOffset();
ringFrameOffset = ringOffset;
ringFrameOffset.z = ringFrameOffset.z < kPointerDistanceMinimal ? kPointerDistanceDefault : ringFrameOffset.z;
// Calculate the pointer world position
Vector3 rotated_direction = transform.rotation * ringFrameOffset;
ringWorldPosition = transform.position + rotated_direction;
//DEBUG("ringWorldPosition: " + ringWorldPosition.x + ", " + ringWorldPosition.y + ", " + ringWorldPosition.z);
float calcRingWidth = m_PointerRingWidth * (ringFrameOffset.z / kPointerDistanceDefault);
float calcInnerCircleRadius = m_PointerCircleRadius * (ringFrameOffset.z / kPointerDistanceDefault);
UpdateRingPercent();
DrawRingRoll(calcRingWidth + calcInnerCircleRadius, calcInnerCircleRadius, ringFrameOffset, m_RingPercent);
if (printIntervalLog)
{
sb.Clear().Append("Update() ")
.Append(gameObject.name).Append(" is ").Append(m_MeshRenderer.enabled ? "shown" : "hidden")
.Append(", ringFrameOffset (").Append(ringFrameOffset.x).Append(", ").Append(ringFrameOffset.y).Append(", ").Append(ringFrameOffset.z).Append(")");
DEBUG(sb);
}
}
#endregion
private bool IsInteractable()
{
ActivatePointer(m_Interactable);
return m_Interactable;
}
private void ActivatePointer(bool active)
{
if (m_MeshRenderer == null)
return;
if (m_MeshRenderer.enabled != active)
{
m_MeshRenderer.enabled = active;
sb.Clear().Append("ActivatePointer() ").Append(m_MeshRenderer.enabled); DEBUG(sb);
if (m_MeshRenderer.enabled)
{
m_MeshRenderer.sortingOrder = m_PointerSortingOrder;
if (pointerMaterialInstance != null)
{
m_MeshRenderer.material = pointerMaterialInstance;
m_MeshRenderer.material.renderQueue = PointerRenderQueue;
}
// The MeshFilter's mesh is updated in DrawRingRoll(), not here.
}
}
}
private void UpdatePointerOffset()
{
ringOffset = pointerLocalOffset;
// Moves the pointer onto the gazed object.
if (raycastObject != null)
{
Vector3 rotated_direction = pointerData.pointerCurrentRaycast.worldPosition - gameObject.transform.position;
ringOffset = Quaternion.Inverse(transform.rotation) * rotated_direction;
}
}
protected float m_GazeOnTime = 0;
private void UpdateRingPercent()
{
if (raycastObject != raycastObjectEx)
{
m_RingPercent = 0;
if (raycastObject != null)
m_GazeOnTime = Time.unscaledTime;
}
else
{
// Hovering
if (raycastObject != null)
m_RingPercent = (int)(((Time.unscaledTime - m_GazeOnTime) / m_TimeToGaze) * 100);
}
}
const int kRingVertexCount = 400; // 100 percents * 2 + 2, ex: 80% ring -> 80 * 2 + 2
private Vector3[] ringVert = new Vector3[kRingVertexCount];
private Color[] ringColor = new Color[kRingVertexCount];
const int kRingTriangleCount = 100 * 6; // 100 percents * 6, ex: 80% ring -> 80 * 6
private int[] ringTriangle = new int[kRingTriangleCount];
private Vector2[] ringUv = new Vector2[kRingVertexCount];
const float kPercentAngle = 3.6f; // 100% = 100 * 3.6f = 360 degrees.
private void DrawRingRoll(float radius, float innerRadius, Vector3 offset, int percent)
{
if (m_MeshFilter == null)
return;
percent = percent >= 100 ? 100 : percent;
// vertices and colors
float start_angle = 90; // Start angle of drawing ring.
for (int i = 0; i < kRingVertexCount; i += 2)
{
float radian_cur = start_angle * Mathf.Deg2Rad;
float cosA = Mathf.Cos(radian_cur);
float sinA = Mathf.Sin(radian_cur);
ringVert[i].x = offset.x + radius * cosA;
ringVert[i].y = offset.y + radius * sinA;
ringVert[i].z = offset.z;
ringColor[i] = (i <= (percent * 2) && i > 0) ? m_ProgressColor : m_PointerColor;
ringVert[i + 1].x = offset.x + innerRadius * cosA;
ringVert[i + 1].y = offset.y + innerRadius * sinA;
ringVert[i + 1].z = offset.z;
ringColor[i + 1] = (i <= (percent * 2) && i > 0) ? m_ProgressColor : m_PointerColor;
start_angle -= kPercentAngle;
}
// triangles
for (int i = 0, vi = 0; i < kRingTriangleCount; i += 6, vi += 2)
{
ringTriangle[i] = vi;
ringTriangle[i + 1] = vi + 3;
ringTriangle[i + 2] = vi + 1;
ringTriangle[i + 3] = vi + 2;
ringTriangle[i + 4] = vi + 3;
ringTriangle[i + 5] = vi;
}
// uv
for (int i = 0; i < kRingVertexCount; i++)
{
ringUv[i].x = ringVert[i].x / radius / 2 + 0.5f;
ringUv[i].y = ringVert[i].z / radius / 2 + 0.5f;
}
m_Mesh.Clear();
m_Mesh.vertices = ringVert;
m_Mesh.colors = ringColor;
m_Mesh.triangles = ringTriangle;
m_Mesh.uv = ringUv;
m_MeshFilter.mesh = m_Mesh;
}
#region External Functions
public Vector3 GetPointerPosition()
{
return ringWorldPosition;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 928994fe36775ae4782045d0503ba99f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,104 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using UnityEngine;
namespace VIVE.OpenXR.Raycast
{
[DisallowMultipleComponent]
public sealed class RaycastSwitch : MonoBehaviour
{
const string LOG_TAG = "VIVE.OpenXR.Raycast.RaycastSwitch";
void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
[Serializable]
public class GazeSettings
{
public bool Enabled = false;
}
[SerializeField]
private GazeSettings m_GazeRaycast = new GazeSettings();
public GazeSettings GazeRaycast { get { return m_GazeRaycast; } set { m_GazeRaycast = value; } }
public static GazeSettings Gaze { get { return Instance.GazeRaycast; } }
[Serializable]
public class ControllerSettings
{
public bool Enabled = true;
}
[SerializeField]
private ControllerSettings m_ControllerRaycast = new ControllerSettings();
public ControllerSettings ControllerRaycast { get { return m_ControllerRaycast; } set { m_ControllerRaycast = value; } }
public static ControllerSettings Controller { get { return Instance.ControllerRaycast; } }
[Serializable]
public class HandSettings
{
public bool Enabled = true;
}
[SerializeField]
private HandSettings m_HandRaycast = new HandSettings();
public HandSettings HandRaycast { get { return m_HandRaycast; } set { m_HandRaycast = value; } }
public static HandSettings Hand { get { return Instance.HandRaycast; } }
private static RaycastSwitch m_Instance = null;
public static RaycastSwitch Instance
{
get
{
if (m_Instance == null)
{
var rs = new GameObject("RaycastSwitch");
m_Instance = rs.AddComponent<RaycastSwitch>();
// This object should survive all scene transitions.
DontDestroyOnLoad(rs);
}
return m_Instance;
}
}
private void Awake()
{
m_Instance = this;
}
private bool m_Enabled = false;
private void OnEnable()
{
if (!m_Enabled)
{
DEBUG("OnEnable()");
m_Enabled = true;
}
}
private void OnDisable()
{
if (m_Enabled)
{
DEBUG("OnDisable()");
m_Enabled = false;
}
}
int printFrame = 0;
bool printLog = false;
private void Update()
{
printFrame++;
printFrame %= 300;
printLog = (printFrame == 0);
CheckSettings();
if (printLog)
{
DEBUG("Update() Gaze.Enabled: " + GazeRaycast.Enabled
+ ", Controller.Enabled: " + ControllerRaycast.Enabled
+ ", Hand.Enabled: " + HandRaycast.Enabled);
}
}
/// <summary> Updates Gaze, Controller and Hand settings in runtime. </summary>
private void CheckSettings()
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1caf898641806b3448ff86ad2e1b0727
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
namespace VIVE.OpenXR.Raycast
{
public class TargetCanvas : MonoBehaviour
{
const string LOG_TAG = "VIVE.OpenXR.Raycast.TargetCanvas";
private void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + gameObject.name + ", " + msg); }
Canvas m_Canvas = null;
private void Awake()
{
m_Canvas = GetComponent<Canvas>();
}
private void OnEnable()
{
DEBUG("OnEnable()");
if (m_Canvas != null)
{
DEBUG("OnEnable() RegisterTargetCanvas.");
CanvasProvider.RegisterTargetCanvas(m_Canvas);
}
}
private void OnDisable()
{
DEBUG("OnDisable()");
if (m_Canvas != null)
{
DEBUG("OnDisable() RemoveTargetCanvas.");
CanvasProvider.RemoveTargetCanvas(m_Canvas);
}
}
Canvas[] s_ChildrenCanvas = null;
private void Update()
{
Canvas[] canvases = GetComponentsInChildren<Canvas>();
if (canvases != null && canvases.Length > 0) // find children canvas
{
s_ChildrenCanvas = canvases;
for (int i = 0; i < s_ChildrenCanvas.Length; i++)
CanvasProvider.RegisterTargetCanvas(s_ChildrenCanvas[i]);
return;
}
if (s_ChildrenCanvas != null && s_ChildrenCanvas.Length > 0) // remove old children canvas
{
for (int i = 0; i < s_ChildrenCanvas.Length; i++)
CanvasProvider.RemoveTargetCanvas(s_ChildrenCanvas[i]);
s_ChildrenCanvas = null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6fe9581cc98d0ff4fb8250646cc0359f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fa3c636a6aff8234b9c9e019b5396109
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ff1e9b9f321387c4e86e6a5fa6cd65c9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the Wave SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(CustomGrabPose))]
public class CustomGrabPoseEditor : UnityEditor.Editor
{
private CustomGrabPose m_GrabPoseDesigner;
private void Awake()
{
m_GrabPoseDesigner = target as CustomGrabPose;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
GUILayout.Space(10f);
ShowGrabPosesMenu();
}
private void ShowGrabPosesMenu()
{
if (GUILayout.Button("Save HandGrab Pose"))
{
m_GrabPoseDesigner.FindNearInteractable();
m_GrabPoseDesigner.SavePoseWithCandidate();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c01f88bc88bb27849a723888721c96f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,382 @@
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the Wave SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using UnityEngine;
using System;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditorInternal;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(HandGrabInteractable))]
public class HandGrabInteractableEditor : UnityEditor.Editor
{
private static HandGrabInteractable activeGrabbable = null;
private HandGrabInteractable handGrabbable = null;
private SerializedProperty m_IsGrabbable, m_FingerRequirement, m_Rigidbody, m_GrabPoses, m_ShowAllIndicator, m_OnBeginGrabbed, m_OnEndGrabbed,
m_OneHandContraintMovement, m_PreviewIndex, grabPoseName, gestureThumbPose, gestureIndexPose, gestureMiddlePose, gestureRingPose, gesturePinkyPose,
recordedGrabRotations, isLeft, enableIndicator, autoIndicator, indicatorObject, grabOffset;
private ReorderableList grabPoseList;
private bool showGrabPoses = false;
private bool showConstraint = false;
private bool showEvent = false;
private void OnEnable()
{
handGrabbable = target as HandGrabInteractable;
m_IsGrabbable = serializedObject.FindProperty("m_IsGrabbable");
m_FingerRequirement = serializedObject.FindProperty("m_FingerRequirement");
m_Rigidbody = serializedObject.FindProperty("m_Rigidbody");
m_GrabPoses = serializedObject.FindProperty("m_GrabPoses");
m_ShowAllIndicator = serializedObject.FindProperty("m_ShowAllIndicator");
m_OnBeginGrabbed = serializedObject.FindProperty("m_OnBeginGrabbed");
m_OnEndGrabbed = serializedObject.FindProperty("m_OnEndGrabbed");
m_OneHandContraintMovement = serializedObject.FindProperty("m_OneHandContraintMovement");
m_PreviewIndex = serializedObject.FindProperty("m_PreviewIndex");
#region ReorderableList
grabPoseList = new ReorderableList(serializedObject, m_GrabPoses, true, true, true, true);
grabPoseList.drawHeaderCallback = (Rect rect) =>
{
EditorGUI.LabelField(rect, "Grab Pose List");
};
grabPoseList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return; }
if (string.IsNullOrEmpty(grabPoseName.stringValue))
{
grabPoseName.stringValue = $"Grab Pose {index + 1}";
}
Rect elementRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
grabPoseName.stringValue = EditorGUI.TextField(elementRect, grabPoseName.stringValue);
DrawGrabGesture(ref elementRect);
DrawHandedness(ref elementRect);
DrawIndicator(ref elementRect);
DrawMirrorButton(ref elementRect);
DrawPoseOffset(ref elementRect);
DrawFineTune(ref elementRect, index);
};
grabPoseList.elementHeightCallback = (int index) =>
{
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return EditorGUIUtility.singleLineHeight; }
// Including Title, Handness, Show Indicator, Mirror Pose, Position, Rotation, Fine Tune
int minHeight = 7;
// To Show GrabGesture
if (recordedGrabRotations.arraySize == 0)
{
minHeight += 5;
}
if (enableIndicator.boolValue)
{
// To Show Auto Indicator
minHeight += 1;
// To Show Indicator Gameobject
if (!autoIndicator.boolValue)
{
minHeight += 1;
}
}
return EditorGUIUtility.singleLineHeight * minHeight + EditorGUIUtility.standardVerticalSpacing * minHeight;
};
grabPoseList.onAddCallback = (ReorderableList list) =>
{
m_GrabPoses.arraySize++;
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(list.count - 1)))
{
grabPoseName.stringValue = $"Grab Pose {list.count}";
}
};
#endregion
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_Rigidbody);
EditorGUILayout.PropertyField(m_IsGrabbable);
EditorGUILayout.PropertyField(m_FingerRequirement);
showGrabPoses = EditorGUILayout.Foldout(showGrabPoses, "Grab Pose Settings");
if (showGrabPoses)
{
if (m_GrabPoses.arraySize == 0)
{
grabPoseList.elementHeight = EditorGUIUtility.singleLineHeight;
}
grabPoseList.DoLayoutList();
bool isToggle = EditorGUILayout.Toggle("Show All Indicator", m_ShowAllIndicator.boolValue);
if (isToggle != m_ShowAllIndicator.boolValue)
{
m_ShowAllIndicator.boolValue = isToggle;
for (int i = 0; i < m_GrabPoses.arraySize; i++)
{
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(i)))
{
enableIndicator.boolValue = m_ShowAllIndicator.boolValue;
}
}
}
}
showEvent = EditorGUILayout.Foldout(showEvent, "Grabbed Event");
if (showEvent)
{
EditorGUILayout.PropertyField(m_OnBeginGrabbed);
EditorGUILayout.PropertyField(m_OnEndGrabbed);
}
showConstraint = EditorGUILayout.Foldout(showConstraint, "Constraint Movement (Optional)");
if (showConstraint)
{
EditorGUILayout.PropertyField(m_OneHandContraintMovement);
}
serializedObject.ApplyModifiedProperties();
}
private bool UpdateGrabPose(SerializedProperty grabPose)
{
SerializedProperty indicator = grabPose.FindPropertyRelative("indicator");
if (grabPose == null || indicator == null) { return false; }
grabPoseName = grabPose.FindPropertyRelative("grabPoseName");
gestureThumbPose = grabPose.FindPropertyRelative("handGrabGesture.thumbPose");
gestureIndexPose = grabPose.FindPropertyRelative("handGrabGesture.indexPose");
gestureMiddlePose = grabPose.FindPropertyRelative("handGrabGesture.middlePose");
gestureRingPose = grabPose.FindPropertyRelative("handGrabGesture.ringPose");
gesturePinkyPose = grabPose.FindPropertyRelative("handGrabGesture.pinkyPose");
recordedGrabRotations = grabPose.FindPropertyRelative("recordedGrabRotations");
isLeft = grabPose.FindPropertyRelative("isLeft");
enableIndicator = indicator.FindPropertyRelative("enableIndicator");
autoIndicator = indicator.FindPropertyRelative("autoIndicator");
indicatorObject = indicator.FindPropertyRelative("target");
grabOffset = grabPose.FindPropertyRelative("grabOffset");
return true;
}
private void AddElementHeight(ref Rect rect)
{
rect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
}
private void DrawGrabGesture(ref Rect rect)
{
if (recordedGrabRotations.arraySize == 0)
{
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gestureThumbPose);
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gestureIndexPose);
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gestureMiddlePose);
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gestureRingPose);
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gesturePinkyPose);
}
}
private void DrawHandedness(ref Rect rect)
{
AddElementHeight(ref rect);
bool isToggle = EditorGUI.Toggle(rect, "Is Left", isLeft.boolValue);
if (isToggle != isLeft.boolValue)
{
isLeft.boolValue = isToggle;
SwitchRotations(ref recordedGrabRotations);
}
}
private void DrawIndicator(ref Rect rect)
{
AddElementHeight(ref rect);
enableIndicator.boolValue = EditorGUI.Toggle(rect, "Show Indicator", enableIndicator.boolValue);
if (enableIndicator.boolValue)
{
AddElementHeight(ref rect);
autoIndicator.boolValue = EditorGUI.Toggle(rect, "Auto Generator Indicator", autoIndicator.boolValue);
if (!autoIndicator.boolValue)
{
AddElementHeight(ref rect);
indicatorObject.objectReferenceValue = (GameObject)EditorGUI.ObjectField(rect, "Indicator", (GameObject)indicatorObject.objectReferenceValue, typeof(GameObject), true);
}
}
else
{
m_ShowAllIndicator.boolValue = false;
}
}
private void DrawMirrorButton(ref Rect rect)
{
AddElementHeight(ref rect);
Rect labelRect = new Rect(rect.x, rect.y, EditorGUIUtility.labelWidth, rect.height);
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Mirror Pose"));
Rect mirrorXRect = new Rect(rect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, rect.y,
(rect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 3, rect.height);
Rect mirrorYRect = new Rect(mirrorXRect.x + mirrorXRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, mirrorXRect.width, rect.height);
Rect mirrorZRect = new Rect(mirrorYRect.x + mirrorYRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, mirrorYRect.width, rect.height);
if (GUI.Button(mirrorXRect, "Align X axis"))
{
MirrorPose(ref grabOffset, Vector3.right);
}
if (GUI.Button(mirrorYRect, "Align Y axis"))
{
MirrorPose(ref grabOffset, Vector3.up);
}
if (GUI.Button(mirrorZRect, "Align Z axis"))
{
MirrorPose(ref grabOffset, Vector3.forward);
}
}
private void DrawPoseOffset(ref Rect rect)
{
SerializedProperty srcPos = grabOffset.FindPropertyRelative("sourcePosition");
SerializedProperty srcRot = grabOffset.FindPropertyRelative("sourceRotation");
SerializedProperty tgtPos = grabOffset.FindPropertyRelative("targetPosition");
SerializedProperty tgtRot = grabOffset.FindPropertyRelative("targetRotation");
AddElementHeight(ref rect);
EditorGUI.Vector3Field(rect, "Position Offset", tgtPos.vector3Value - srcPos.vector3Value);
AddElementHeight(ref rect);
Vector3 rotEulerAngles = (Quaternion.Inverse(srcRot.quaternionValue) * tgtRot.quaternionValue).eulerAngles;
for (int i = 0; i < 3; i++)
{
if (rotEulerAngles[i] > 180)
{
rotEulerAngles[i] = 360.0f - rotEulerAngles[i];
}
}
EditorGUI.Vector3Field(rect, "Rotation Offset", rotEulerAngles);
}
private void DrawFineTune(ref Rect rect, int index)
{
AddElementHeight(ref rect);
Rect labelRect = new Rect(rect.x, rect.y, EditorGUIUtility.labelWidth, rect.height);
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Fine Tune"));
Rect previewRect = new Rect(rect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, rect.y,
(rect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 2, rect.height);
Rect updateRect = new Rect(previewRect.x + previewRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, previewRect.width, rect.height);
if (GUI.Button(previewRect, "Preview Grab Pose") && Application.isPlaying)
{
activeGrabbable = handGrabbable;
m_PreviewIndex.intValue = index;
ShowMeshHandPose();
}
GUI.enabled = activeGrabbable == handGrabbable && m_PreviewIndex.intValue == index;
if (GUI.Button(updateRect, "Update Grab Pose"))
{
UpdateGrabPose();
}
GUI.enabled = true;
}
/// <summary>
/// Convert the rotation of joints of the current hand into those of another hand.
/// </summary>
/// <param name="rotations">Rotation of joints of the current hand.</param>
private void SwitchRotations(ref SerializedProperty rotations)
{
for (int i = 0; i < rotations.arraySize; i++)
{
Quaternion rotation = rotations.GetArrayElementAtIndex(i).quaternionValue;
Quaternion newRotation = Quaternion.Euler(rotation.eulerAngles.x, -rotation.eulerAngles.y, -rotation.eulerAngles.z);
rotations.GetArrayElementAtIndex(i).quaternionValue = newRotation;
}
}
/// <summary>
/// Mirrors the pose properties (position and rotation) of a serialized object along a specified mirror axis.
/// </summary>
/// <param name="pose">The serialized property representing the pose to be mirrored.</param>
/// <param name="mirrorAxis">The axis along which the mirroring should occur.</param>
private void MirrorPose(ref SerializedProperty pose, Vector3 mirrorAxis)
{
Vector3 sourcePosition = grabOffset.FindPropertyRelative("sourcePosition").vector3Value;
Quaternion sourceRotation = grabOffset.FindPropertyRelative("sourceRotation").quaternionValue;
Vector3 targetPosition = grabOffset.FindPropertyRelative("targetPosition").vector3Value;
Quaternion targetRotation = grabOffset.FindPropertyRelative("targetRotation").quaternionValue;
Vector3 reflectNormal = targetRotation * mirrorAxis;
Vector3 diffPos = sourcePosition - targetPosition;
Vector3 mirrorPosition = targetPosition + Vector3.Reflect(diffPos, reflectNormal);
pose.FindPropertyRelative("sourcePosition").vector3Value = mirrorPosition;
Vector3 sourceForward = sourceRotation * Vector3.forward;
Vector3 sourceUp = sourceRotation * Vector3.up;
Quaternion mirroredRotation = Quaternion.LookRotation(Vector3.Reflect(sourceForward, reflectNormal), Vector3.Reflect(sourceUp, reflectNormal));
pose.FindPropertyRelative("sourceRotation").quaternionValue = mirroredRotation;
}
/// <summary>
/// Obtain the MeshHand and set its position and rotation based on the grabOffset of grabpose.
/// </summary>
private void ShowMeshHandPose()
{
HandPose handPose = HandPoseProvider.GetHandPose(isLeft.boolValue ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
if (handPose != null && handPose is MeshHandPose meshHandPose)
{
GrabOffset grabOffsetObj = handGrabbable.grabPoses[m_PreviewIndex.intValue].grabOffset;
Quaternion handRot = handGrabbable.transform.rotation * Quaternion.Inverse(grabOffsetObj.rotOffset);
Quaternion handRotDiff = handRot * Quaternion.Inverse(grabOffsetObj.sourceRotation);
Vector3 handPos = handGrabbable.transform.position - handRotDiff * grabOffsetObj.posOffset;
meshHandPose.SetJointPose(JointType.Wrist, new Pose(handPos, handRot));
foreach (JointType joint in Enum.GetValues(typeof(JointType)))
{
if (joint == JointType.Wrist || joint == JointType.Count) { continue; }
meshHandPose.GetPosition(joint, out Vector3 pos, local: true);
Quaternion rot = recordedGrabRotations.GetArrayElementAtIndex((int)joint).quaternionValue;
meshHandPose.SetJointPose(joint, new Pose(pos, rot), local: true);
}
}
}
/// <summary>
/// Update the grabpose based on position and rotation of the MeshHand and Object.
/// </summary>
private void UpdateGrabPose()
{
HandPose handPose = HandPoseProvider.GetHandPose(isLeft.boolValue ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
if (handPose != null && handPose is MeshHandPose meshHandPose)
{
meshHandPose.GetPosition(JointType.Wrist, out Vector3 wristPosition);
meshHandPose.GetRotation(JointType.Wrist, out Quaternion wristRotation);
Quaternion[] fingerJointRotation = new Quaternion[(int)JointType.Count];
for (int i = 0; i < fingerJointRotation.Length; i++)
{
meshHandPose.GetRotation((JointType)i, out Quaternion jointRotation, local: true);
fingerJointRotation[i] = jointRotation;
}
GrabPose grabPose = handGrabbable.grabPoses[m_PreviewIndex.intValue];
grabPose.Update(grabPoseName.stringValue, fingerJointRotation, isLeft.boolValue);
grabPose.grabOffset.Update(wristPosition, wristRotation, handGrabbable.transform.position, handGrabbable.transform.rotation);
handGrabbable.grabPoses[m_PreviewIndex.intValue] = grabPose;
GrabbablePoseRecorder.SaveChanges();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7ba007edb5befc349a95aa1cfa7cb6c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(HandGrabInteractor))]
public class HandGrabInteractorEditor : UnityEditor.Editor
{
private SerializedProperty m_Handedness, m_GrabDistance, m_OnBeginGrab, m_OnEndGrab;
private bool showEvent = false;
private void OnEnable()
{
m_Handedness = serializedObject.FindProperty("m_Handedness");
m_GrabDistance = serializedObject.FindProperty("m_GrabDistance");
m_OnBeginGrab = serializedObject.FindProperty("m_OnBeginGrab");
m_OnEndGrab = serializedObject.FindProperty("m_OnEndGrab");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_Handedness);
EditorGUILayout.PropertyField(m_GrabDistance);
showEvent = EditorGUILayout.Foldout(showEvent, "Grab Event");
if (showEvent)
{
EditorGUILayout.PropertyField(m_OnBeginGrab);
EditorGUILayout.PropertyField(m_OnEndGrab);
}
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8e7c213fed939e4b859638cde97a31c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(HandMeshManager))]
public class HandMeshManagerEditor : UnityEditor.Editor
{
private HandMeshManager m_HandMesh;
private SerializedProperty m_Handedness, m_EnableCollider, m_HandJoints;
private bool showJoints = false;
public static readonly GUIContent findJoints = EditorGUIUtility.TrTextContent("Find Joints");
public static readonly GUIContent clearJoints = EditorGUIUtility.TrTextContent("All Clear");
private void OnEnable()
{
m_HandMesh = target as HandMeshManager;
m_Handedness = serializedObject.FindProperty("m_Handedness");
m_EnableCollider = serializedObject.FindProperty("m_EnableCollider");
m_HandJoints = serializedObject.FindProperty("m_HandJoints");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Please check if your model is used to bind left hand poses", MessageType.None);
EditorGUILayout.PropertyField(m_Handedness, new GUIContent("Handedness"));
EditorGUILayout.HelpBox("Please check if you want the hand model with collision enabled.", MessageType.None);
EditorGUILayout.PropertyField(m_EnableCollider, new GUIContent("Enable Collider"));
showJoints = EditorGUILayout.Foldout(showJoints, "Hand Bones Reference");
if (showJoints)
{
EditorGUILayout.HelpBox("Please change rotation to make sure your model should palm faces forward and fingers points up in global axis.", MessageType.Info);
using (new EditorGUILayout.HorizontalScope())
{
using (new EditorGUI.DisabledScope())
{
if (GUILayout.Button(findJoints))
{
m_HandMesh.FindJoints();
}
}
using (new EditorGUI.DisabledScope())
{
if (GUILayout.Button(clearJoints))
{
m_HandMesh.ClearJoints();
}
}
}
bool isDetected = false;
for (int i = 0; i < m_HandJoints.arraySize; i++)
{
SerializedProperty bone = m_HandJoints.GetArrayElementAtIndex(i);
Transform boneTransform = (Transform)bone.objectReferenceValue;
if (boneTransform != null)
{
isDetected = true;
break;
}
}
if (isDetected)
{
for (int i = 0; i < m_HandJoints.arraySize; i++)
{
SerializedProperty bone = m_HandJoints.GetArrayElementAtIndex(i);
EditorGUILayout.PropertyField(bone, new GUIContent(((JointType)i).ToString()));
}
}
}
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d606d68d81c647241b90f1060786476c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e312e5246287a2546944bb77519270c4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3744e08fd384e154c82d53686f0c82a6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 44fadaef2720de846af62a7bbb2ec370
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: LeftHandGrab
serializedVersion: 5
m_AnimatorParameters: []
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: 9113736214857964471}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1102 &83218441301297147
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: LeftHandGrab
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 44fadaef2720de846af62a7bbb2ec370, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1107 &9113736214857964471
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: 83218441301297147}
m_Position: {x: 200, y: 0, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: 83218441301297147}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1504c5149524a8a44a3854dfd4f94d1e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a10ddbfcbce64f84787e026f4fc003a4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1107 &-3394168293143139300
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: 3187830036533893518}
m_Position: {x: 200, y: 0, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: 3187830036533893518}
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: RightHandGrab
serializedVersion: 5
m_AnimatorParameters: []
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: -3394168293143139300}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1102 &3187830036533893518
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: RightHandGrab
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: a10ddbfcbce64f84787e026f4fc003a4, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 950573398b7eb5149bea536bfa6107ca
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 32007394341a17c44859030ef5b809ee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 010c0098d232cb0428f42a48488a6255
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9731229184277b54ba66bffd1633169b
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9b7f1f69b8b23e9459efda715941fbee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea1b2b1a3faba024aa3df5391529aec0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the Wave SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to automatically generate indicators.
/// </summary>
public class AutoGenIndicator : MonoBehaviour
{
private MeshRenderer meshRenderer;
private MeshFilter meshFilter;
private readonly Color indicatorColor = new Color(1f, 0.7960785f, 0.09411766f, 1f);
private const float k_Length = 0.05f;
private const float k_Width = 0.05f;
private void Start()
{
meshRenderer = transform.gameObject.AddComponent<MeshRenderer>();
meshFilter = transform.gameObject.AddComponent<MeshFilter>();
MeshInitialize();
}
/// <summary>
/// Initialize the mesh for the indicator.
/// </summary>
private void MeshInitialize()
{
Shader shader = Shader.Find("Sprites/Default");
meshRenderer.material = new Material(shader);
meshRenderer.material.SetColor("_Color", indicatorColor);
meshRenderer.sortingOrder = 1;
Mesh arrowMesh = new Mesh();
Vector3[] vertices = new Vector3[4];
int[] triangles = new int[3 * 2];
vertices[0] = new Vector3(0, 0f, 0f);
vertices[1] = new Vector3(0f, k_Length * 0.8f, 0f);
vertices[2] = new Vector3(-k_Width * 0.5f, k_Length, 0f);
vertices[3] = new Vector3(k_Width * 0.5f, k_Length, 0f);
triangles[0] = 0;
triangles[1] = 2;
triangles[2] = 1;
triangles[3] = 0;
triangles[4] = 1;
triangles[5] = 3;
arrowMesh.vertices = vertices;
arrowMesh.triangles = triangles;
arrowMesh.RecalculateNormals();
meshFilter.mesh = arrowMesh;
}
/// <summary>
/// Set the pose of the indicator.
/// </summary>
/// <param name="position">The position vector to set.</param>
/// <param name="direction">The direction vector to set.</param>
public void SetPose(Vector3 position, Vector3 direction)
{
transform.position = position;
transform.up = direction.normalized;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4694bdada589dce4c9b7096c7169833f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,394 @@
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the Wave SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to edit grab gestures.
/// </summary>
[RequireComponent(typeof(HandMeshManager))]
public class CustomGrabPose : MonoBehaviour
{
#if UNITY_EDITOR
#region Log
const string LOG_TAG = "Wave.Essence.Hand.Interaction.CustomGrabPose";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(StringBuilder msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(StringBuilder msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(StringBuilder msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
/// <summary>
/// This structure is designed to record the rotation values of each joint at different bend angles.
/// </summary>
private struct JointBendingRotation
{
public FingerId fingerId;
public string jointPath;
public int bending;
public Quaternion rotation;
public JointBendingRotation(FingerId in_FingerId, string in_JointPath, int in_Bending, Quaternion in_Rotation)
{
fingerId = in_FingerId;
jointPath = in_JointPath;
bending = in_Bending;
rotation = in_Rotation;
}
public JointBendingRotation Identity => new JointBendingRotation(FingerId.Invalid, "", -1, Quaternion.identity);
}
[SerializeField]
private AnimationClip animationClip;
[SerializeField]
[Range(1, 30)]
private int thumbBending = 1;
[SerializeField]
[Range(1, 30)]
private int indexBending = 1;
[SerializeField]
[Range(1, 30)]
private int middleBending = 1;
[SerializeField]
[Range(1, 30)]
private int ringBending = 1;
[SerializeField]
[Range(1, 30)]
private int pinkyBending = 1;
private HandMeshManager m_HandMesh;
private Dictionary<FingerId, int> fingersBendingMapping = new Dictionary<FingerId, int>()
{
{FingerId.Thumb, 1 },
{FingerId.Index, 1 },
{FingerId.Middle, 1 },
{FingerId.Ring, 1 },
{FingerId.Pinky, 1 },
};
private static readonly Dictionary<string, FingerId> jointsPathMapping = new Dictionary<string, FingerId>()
{
{"WaveBone_0", FingerId.Invalid },
{"WaveBone_1", FingerId.Invalid },
{"WaveBone_2", FingerId.Thumb },
{"WaveBone_2/WaveBone_3", FingerId.Thumb },
{"WaveBone_2/WaveBone_3/WaveBone_4", FingerId.Thumb },
{"WaveBone_2/WaveBone_3/WaveBone_4/WaveBone_5", FingerId.Thumb },
{"WaveBone_6", FingerId.Index },
{"WaveBone_6/WaveBone_7", FingerId.Index },
{"WaveBone_6/WaveBone_7/WaveBone_8", FingerId.Index },
{"WaveBone_6/WaveBone_7/WaveBone_8/WaveBone_9", FingerId.Index },
{"WaveBone_6/WaveBone_7/WaveBone_8/WaveBone_9/WaveBone_10", FingerId.Index },
{"WaveBone_11", FingerId.Middle },
{"WaveBone_11/WaveBone_12", FingerId.Middle },
{"WaveBone_11/WaveBone_12/WaveBone_13", FingerId.Middle },
{"WaveBone_11/WaveBone_12/WaveBone_13/WaveBone_14", FingerId.Middle },
{"WaveBone_11/WaveBone_12/WaveBone_13/WaveBone_14/WaveBone_15", FingerId.Middle },
{"WaveBone_16", FingerId.Ring },
{"WaveBone_16/WaveBone_17", FingerId.Ring },
{"WaveBone_16/WaveBone_17/WaveBone_18", FingerId.Ring },
{"WaveBone_16/WaveBone_17/WaveBone_18/WaveBone_19", FingerId.Ring },
{"WaveBone_16/WaveBone_17/WaveBone_18/WaveBone_19/WaveBone_20", FingerId.Ring },
{"WaveBone_21", FingerId.Pinky },
{"WaveBone_21/WaveBone_22", FingerId.Pinky },
{"WaveBone_21/WaveBone_22/WaveBone_23", FingerId.Pinky },
{"WaveBone_21/WaveBone_22/WaveBone_23/WaveBone_24", FingerId.Pinky },
{"WaveBone_21/WaveBone_22/WaveBone_23/WaveBone_24/WaveBone_25", FingerId.Pinky },
};
private List<JointBendingRotation> jointsBending = new List<JointBendingRotation>();
private readonly float k_GrabDistance = 0.1f;
private HandGrabInteractable candidate = null;
private Pose wristPose = Pose.identity;
private Quaternion[] fingerJointRotation = new Quaternion[jointsPathMapping.Count];
private bool isNewInputSystem = false;
#region MonoBehaviours
private void OnEnable()
{
m_HandMesh = transform.GetComponent<HandMeshManager>();
if (m_HandMesh == null)
{
sb.Clear().Append("Failed to find HandMeshRenderer.");
ERROR(sb);
}
if (animationClip != null)
{
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(animationClip);
foreach (string propertyName in jointsPathMapping.Keys)
{
SetEachFrameRotation(curveBindings, propertyName);
}
}
else
{
sb.Clear().Append("Failed to find hand grab animation. The hand model will not change when you change bend angles of finger.");
sb.Append("However, you still can record grab pose if you use direct preview mode.");
WARNING(sb);
}
isNewInputSystem = Keyboard.current != null;
}
private void OnDisable()
{
jointsBending.Clear();
}
private void Update()
{
if (IsFingerBendingUpdated())
{
for (int i = 0; i < fingerJointRotation.Length; i++)
{
var jointInfo = jointsPathMapping.ElementAt(i);
string jointPath = jointInfo.Key;
FingerId fingerId = jointInfo.Value;
int bending = -1;
if (fingersBendingMapping.ContainsKey(fingerId))
{
bending = fingersBendingMapping[fingerId] - 1;
}
if (jointsBending.Count(x => x.fingerId == fingerId && x.jointPath == jointPath && x.bending == bending) > 0)
{
JointBendingRotation jointRotation = jointsBending.FirstOrDefault(x => x.fingerId == fingerId && x.jointPath == jointPath && x.bending == bending);
fingerJointRotation[i] = jointRotation.rotation;
}
else
{
fingerJointRotation[i] = Quaternion.identity;
}
}
if (m_HandMesh != null)
{
for (int i = 0; i < fingerJointRotation.Length; i++)
{
JointType joint = (JointType)i;
m_HandMesh.GetJointPositionAndRotation(joint, out Vector3 jointPosition, out _, local: true);
m_HandMesh.SetJointPositionAndRotation(joint, jointPosition, fingerJointRotation[i], local: true);
}
}
}
if (IsEnterPressed())
{
FindNearInteractable();
SavePoseWithCandidate();
}
}
#endregion
/// <summary>
/// Reads the rotation of each joint frame by frame and records it.
/// </summary>
/// <param name="curveBindings">All the float curve bindings currently stored in the clip.</param>
/// <param name="jointPath">The path of the joint.</param>
private void SetEachFrameRotation(EditorCurveBinding[] curveBindings, string jointPath)
{
const int propertyCount = 4;
const int animeCount = 30;
const float animeFPS = 60.0f;
List<EditorCurveBinding> matchCurve = new List<EditorCurveBinding>();
foreach (EditorCurveBinding binding in curveBindings)
{
if (binding.path.Equals(jointPath))
{
matchCurve.Add(binding);
}
if (matchCurve.Count == propertyCount)
{
break;
}
}
if (matchCurve.Count == propertyCount)
{
for (int i = 0; i < animeCount; i++)
{
Quaternion rotation = Quaternion.identity;
foreach (var curveBinding in matchCurve)
{
AnimationCurve curve = AnimationUtility.GetEditorCurve(animationClip, curveBinding);
switch (curveBinding.propertyName)
{
case "m_LocalRotation.x":
rotation.x = curve.Evaluate(i / animeFPS);
break;
case "m_LocalRotation.y":
rotation.y = curve.Evaluate(i / animeFPS);
break;
case "m_LocalRotation.z":
rotation.z = curve.Evaluate(i / animeFPS);
break;
case "m_LocalRotation.w":
rotation.w = curve.Evaluate(i / animeFPS);
break;
}
}
jointsBending.Add(new JointBendingRotation(jointsPathMapping[jointPath], jointPath, i, rotation));
}
}
}
/// <summary>
/// Checks if the current finger bend angle has changed.
/// </summary>
/// <returns>True if the bend angle has changed; otherwise, false.</returns>
private bool IsFingerBendingUpdated()
{
bool updated = false;
if (fingersBendingMapping[FingerId.Thumb] != thumbBending)
{
fingersBendingMapping[FingerId.Thumb] = thumbBending;
updated = true;
}
if (fingersBendingMapping[FingerId.Index] != indexBending)
{
fingersBendingMapping[FingerId.Index] = indexBending;
updated = true;
}
if (fingersBendingMapping[FingerId.Middle] != middleBending)
{
fingersBendingMapping[FingerId.Middle] = middleBending;
updated = true;
}
if (fingersBendingMapping[FingerId.Ring] != ringBending)
{
fingersBendingMapping[FingerId.Ring] = ringBending;
updated = true;
}
if (fingersBendingMapping[FingerId.Pinky] != pinkyBending)
{
fingersBendingMapping[FingerId.Pinky] = pinkyBending;
updated = true;
}
return updated;
}
/// <summary>
/// Update hand pose from HandMeshRenderer.
/// </summary>
/// <returns>Return true if updating hand pose from HandMeshRenderer; otherwise.</returns>
private bool UpdateHandPose()
{
bool updated = false;
if (m_HandMesh != null)
{
for (int i = 0; i < fingerJointRotation.Length; i++)
{
if (i == (int)JointType.Wrist)
{
m_HandMesh.GetJointPositionAndRotation(JointType.Wrist, out wristPose.position, out wristPose.rotation);
}
m_HandMesh.GetJointPositionAndRotation((JointType)i, out _, out fingerJointRotation[i], local: true);
}
updated = true;
}
if (!updated)
{
sb.Clear().Append("Failed to update hand pose.");
DEBUG(sb);
}
return updated;
}
private bool IsEnterPressed()
{
if (isNewInputSystem)
{
return (Keyboard.current.enterKey?.wasPressedThisFrame ?? false) ||
(Keyboard.current.numpadEnterKey?.wasPressedThisFrame ?? false);
}
else
{
return Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter);
}
}
/// <summary>
/// Finds the nearest interactable object to the hand.
/// </summary>
public void FindNearInteractable()
{
if (!UpdateHandPose()) { return; }
candidate = null;
float maxScore = 0;
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
{
float distanceScore = interactable.CalculateDistanceScore(wristPose.position, k_GrabDistance);
if (distanceScore > maxScore)
{
maxScore = distanceScore;
candidate = interactable;
}
}
if (candidate == null)
{
sb.Clear().Append("Unable to find a suitable candidate.");
WARNING(sb);
}
}
/// <summary>
/// Save the position and rotation offset with the candidate.
/// </summary>
public void SavePoseWithCandidate()
{
if (!UpdateHandPose() || candidate == null) { return; }
Quaternion[] clone = new Quaternion[fingerJointRotation.Length];
Array.Copy(fingerJointRotation, clone, fingerJointRotation.Length);
GrabPose grabPose = GrabPose.Identity;
grabPose.Update($"Grab Pose {candidate.grabPoses.Count + 1}", clone, m_HandMesh.isLeft);
grabPose.grabOffset = new GrabOffset(wristPose.position, wristPose.rotation, candidate.transform.position, candidate.transform.rotation);
if (!candidate.grabPoses.Contains(grabPose))
{
candidate.grabPoses.Add(grabPose);
}
GrabbablePoseRecorder.SaveChanges();
sb.Clear().Append("Save grab pose successfully.");
DEBUG(sb);
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd5957dc7b39bd249885b5bb53749b7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,173 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to manage all Grabbers and Grabbables.
/// </summary>
public static class GrabManager
{
private static List<IGrabber> m_GrabberRegistry = new List<IGrabber>();
public static IReadOnlyList<HandGrabInteractor> handGrabbers => m_GrabberRegistry.OfType<HandGrabInteractor>().ToList().AsReadOnly();
private static List<IGrabbable> m_GrabbableRegistry = new List<IGrabbable>();
public static IReadOnlyList<HandGrabInteractable> handGrabbables => m_GrabbableRegistry.OfType<HandGrabInteractable>().ToList().AsReadOnly();
#region IGrabber
/// <summary>
/// Register the grabber in the grabber registry.
/// </summary>
/// <param name="grabber">The grabber to register.</param>
/// <returns>True if the grabber is successfully registered; otherwise, false.</returns>
public static bool RegisterGrabber(IGrabber grabber)
{
if (!m_GrabberRegistry.Contains(grabber))
{
m_GrabberRegistry.Add(grabber);
}
return m_GrabberRegistry.Contains(grabber);
}
/// <summary>
/// Remove the grabber from the grabber registry.
/// </summary>
/// <param name="grabber">The grabber to remove.</param>
/// <returns>True if the grabber is successfully removed; otherwise, false.</returns>
public static bool UnregisterGrabber(IGrabber grabber)
{
if (m_GrabberRegistry.Contains(grabber))
{
m_GrabberRegistry.Remove(grabber);
}
return !m_GrabberRegistry.Contains(grabber);
}
/// <summary>
/// Get the first hand grabber component found in the child hierarchy of the GameObject.
/// </summary>
/// <param name="target">The target whose child hierarchy to search.</param>
/// <param name="grabber">The output parameter to store the first hand grabber component found.</param>
/// <returns>True if a hand grabber component is found; otherwise, false.</returns>
public static bool GetFirstHandGrabberFromChild(GameObject target, out HandGrabInteractor grabber)
{
grabber = TopDownFind<HandGrabInteractor>(target.transform);
return grabber != null;
}
/// <summary>
/// Get the first hand grabber component found in the parent hierarchy of the GameObject.
/// </summary>
/// <param name="target">The target whose parent hierarchy to search.</param>
/// <param name="grabber">The output parameter to store the first hand grabber component found.</param>
/// <returns>True if a hand grabber component is found; otherwise, false.</returns>
public static bool GetFirstHandGrabberFromParent(GameObject target, out HandGrabInteractor grabber)
{
grabber = BottomUpFind<HandGrabInteractor>(target.transform);
return grabber != null;
}
#endregion
#region GrabInteractable
/// <summary>
/// Register the grabbable in the grabbable registry.
/// </summary>
/// <param name="grabbable">The grabbable to register.</param>
/// <returns>True if the grabbable is successfully registered; otherwise, false.</returns>
public static bool RegisterGrabbable(IGrabbable grabbable)
{
if (!m_GrabbableRegistry.Contains(grabbable))
{
m_GrabbableRegistry.Add(grabbable);
}
return m_GrabbableRegistry.Contains(grabbable);
}
/// <summary>
/// Remove the grabbable from the grabbable registry.
/// </summary>
/// <param name="grabbable">The grabbable to remove.</param>
/// <returns>True if the grabbable is successfully removed; otherwise, false.</returns>
public static bool UnregisterGrabbable(IGrabbable grabbable)
{
if (m_GrabbableRegistry.Contains(grabbable))
{
m_GrabbableRegistry.Remove(grabbable);
}
return !m_GrabbableRegistry.Contains(grabbable);
}
/// <summary>
/// Get the first hand grabbable component found in the child hierarchy of the GameObject.
/// </summary>
/// <param name="target">The target whose child hierarchy to search.</param>
/// <param name="grabbable">The output parameter to store the first hand grabbable component found.</param>
/// <returns>True if a hand grabbable component is found; otherwise, false.</returns>
public static bool GetFirstHandGrabbableFromChild(GameObject target, out HandGrabInteractable grabbable)
{
grabbable = TopDownFind<HandGrabInteractable>(target.transform);
return grabbable != null;
}
/// <summary>
/// Get the first hand grabbable component found in the parent hierarchy of the GameObject.
/// </summary>
/// <param name="target">The target whose parent hierarchy to search.</param>
/// <param name="grabbable">The output parameter to store the first hand grabbable component found.</param>
/// <returns>True if a hand grabbable component is found; otherwise, false.</returns>
public static bool GetFirstHandGrabbableFromParent(GameObject target, out HandGrabInteractable grabbable)
{
grabbable = BottomUpFind<HandGrabInteractable>(target.transform);
return grabbable != null;
}
#endregion
/// <summary>
/// Find available components from self to children nodes.
/// </summary>
/// <param name="transform">The transform of the gameobject.</param>
/// <returns>Value for available component.</returns>
private static T TopDownFind<T>(Transform transform) where T : Component
{
T component = transform.GetComponent<T>();
if (component != null)
{
return component;
}
if (transform.childCount > 0)
{
for (int i = 0; i < transform.childCount; i++)
{
T childComponent = TopDownFind<T>(transform.GetChild(i));
if (childComponent != null)
{
return childComponent;
}
}
}
return null;
}
/// <summary>
/// Find available components from self to parent node.
/// </summary>
/// <param name="transform">The transform of the gameobject.</param>
/// <returns>Value for available component.</returns>
private static T BottomUpFind<T>(Transform transform) where T : Component
{
T component = transform.GetComponent<T>();
if (component != null)
{
return component;
}
if (transform.parent != null)
{
return BottomUpFind<T>(transform.parent);
}
return null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 252950eac28fb1f4cb1eae8e653f92ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,120 @@
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the WaveVR SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
#if UNITY_EDITOR
/// <summary>
/// The class is designed to record all grab poses for all hand Grabbables.
/// </summary>
public class GrabPoseBinder : ScriptableObject
{
/// <summary>
/// This struct records the grab pose for grabbable object.
/// </summary>
[Serializable]
private struct GrabPoseBindFormat
{
[SerializeField]
public string grabbableName;
[SerializeField]
public List<GrabPose> grabPoses;
public GrabPoseBindFormat(string in_GrabbableName, List<GrabPose> in_GrabPoses)
{
grabbableName = in_GrabbableName;
grabPoses = in_GrabPoses;
}
public GrabPoseBindFormat Identity => new GrabPoseBindFormat(string.Empty, new List<GrabPose>());
public void Update(List<GrabPose> grabPoses)
{
this.grabPoses.Clear();
this.grabPoses.AddRange(grabPoses);
}
public void Reset()
{
grabbableName = string.Empty;
grabPoses.Clear();
}
public override bool Equals(object obj)
{
return obj is GrabPoseBindFormat grabPoseBindFormat &&
grabbableName == grabPoseBindFormat.grabbableName &&
grabPoses == grabPoseBindFormat.grabPoses;
}
public override int GetHashCode()
{
return grabbableName.GetHashCode() ^ grabPoses.GetHashCode();
}
public static bool operator ==(GrabPoseBindFormat source, GrabPoseBindFormat target) => source.Equals(target);
public static bool operator !=(GrabPoseBindFormat source, GrabPoseBindFormat target) => !(source == target);
}
[SerializeField]
private List<GrabPoseBindFormat> m_BindingInfos = new List<GrabPoseBindFormat>();
/// <summary>
/// Update the binding information for each hand grabbable object.
/// </summary>
public void UpdateBindingInfos()
{
m_BindingInfos.Clear();
foreach (HandGrabInteractable grabbable in GrabManager.handGrabbables)
{
m_BindingInfos.Add(new GrabPoseBindFormat(grabbable.name, grabbable.grabPoses));
}
}
/// <summary>
/// Stores the binding information.
/// </summary>
/// <returns>True if storage is successful; otherwise, false.</returns>
public bool StorageData()
{
if (m_BindingInfos.Count == 0) { return false; }
EditorApplication.delayCall += () =>
{
AssetDatabase.Refresh();
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
};
return true;
}
/// <summary>
/// Finds grab poses associated with the specified hand grabbable object.
/// </summary>
/// <param name="grabbable">The hand grabbable object to search for.</param>
/// <param name="grabPoses">The output parameter to store the found grab poses.</param>
/// <returns>True if grab poses are found for the grabbable object; otherwise, false.</returns>
public bool FindGrabPosesWithGrabbable(HandGrabInteractable grabbable, out List<GrabPose> grabPoses)
{
grabPoses = new List<GrabPose>();
GrabPoseBindFormat bindingInfo = m_BindingInfos.Find(x => x.grabbableName == grabbable.name);
if (bindingInfo != null)
{
grabPoses = bindingInfo.grabPoses;
return true;
}
return false;
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53503199deedf444e84f1714b700737d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,104 @@
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the WaveVR SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
#if UNITY_EDITOR
/// <summary>
/// The class is designed to update the grab poses of all hand Grabbables.
/// </summary>
[InitializeOnLoad]
public class GrabbablePoseRecorder
{
private static readonly string filepath = "Assets/GrabablePoseRecording.asset";
private static readonly string metaFilepath = filepath + ".meta";
private static bool IsFileExist => File.Exists(filepath);
static GrabbablePoseRecorder()
{
EditorApplication.playModeStateChanged += ApplyChanges;
}
/// <summary>
/// Apply changes to grab poses when entering edit mode.
/// </summary>
/// <param name="state">The state of the play mode.</param>
private static void ApplyChanges(PlayModeStateChange state)
{
if (IsFileExist && state == PlayModeStateChange.EnteredEditMode)
{
GrabPoseBinder binder = AssetDatabase.LoadAssetAtPath<GrabPoseBinder>(filepath);
if (binder != null)
{
HandGrabInteractable[] grabbables = Object.FindObjectsOfType<HandGrabInteractable>();
foreach (var grabbable in grabbables)
{
if (binder.FindGrabPosesWithGrabbable(grabbable, out List<GrabPose> updatedGrabPose))
{
for (int i = 0; i < updatedGrabPose.Count; i++)
{
GrabPose grabPose = updatedGrabPose[i];
GrabPose oldGrabPose = grabbable.grabPoses.Find(x => x.grabPoseName == grabPose.grabPoseName);
if (oldGrabPose != null)
{
grabPose.indicator.target = oldGrabPose.indicator.target;
}
updatedGrabPose[i] = grabPose;
}
grabbable.grabPoses.Clear();
grabbable.grabPoses.AddRange(updatedGrabPose);
}
}
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
}
File.Delete(filepath);
File.Delete(metaFilepath);
}
}
/// <summary>
/// Saves changes to grab pose bindings.
/// </summary>
public static void SaveChanges()
{
if (!IsFileExist)
{
GenerateAsset();
}
else
{
GrabPoseBinder binder = AssetDatabase.LoadAssetAtPath<GrabPoseBinder>(filepath);
if (binder != null)
{
binder.UpdateBindingInfos();
binder.StorageData();
}
}
}
/// <summary>
/// Generates a new asset for storing grab pose bindings.
/// </summary>
private static void GenerateAsset()
{
GrabPoseBinder binder = ScriptableObject.CreateInstance<GrabPoseBinder>();
binder.UpdateBindingInfos();
AssetDatabase.CreateAsset(binder, filepath);
AssetDatabase.SaveAssets();
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9119682127e09314ca250470f13db0f2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,426 @@
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the Wave SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System.Collections.Generic;
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to implement IHandGrabbable, allowing objects to be grabbed.
/// </summary>
public class HandGrabInteractable : MonoBehaviour, IHandGrabbable
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandGrabInteractable";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
#region Interface Implement
private HandGrabInteractor m_Grabber = null;
public IGrabber grabber => m_Grabber;
public bool isGrabbed => m_Grabber != null;
[SerializeField]
private bool m_IsGrabbable = true;
public bool isGrabbable { get { return m_IsGrabbable; } set { m_IsGrabbable = value; } }
[SerializeField]
private FingerRequirement m_FingerRequirement;
public FingerRequirement fingerRequirement => m_FingerRequirement;
[SerializeField]
HandGrabbableEvent m_OnBeginGrabbed = new HandGrabbableEvent();
public HandGrabbableEvent onBeginGrabbed => m_OnBeginGrabbed;
[SerializeField]
HandGrabbableEvent m_OnEndGrabbed = new HandGrabbableEvent();
public HandGrabbableEvent onEndGrabbed => m_OnEndGrabbed;
#endregion
#region Public State
[SerializeField]
private Rigidbody m_Rigidbody = null;
#pragma warning disable
public Rigidbody rigidbody => m_Rigidbody;
#pragma warning enable
[SerializeField]
private List<GrabPose> m_GrabPoses = new List<GrabPose>();
public List<GrabPose> grabPoses => m_GrabPoses;
private GrabPose m_BestGrabPose = GrabPose.Identity;
public GrabPose bestGrabPose => m_BestGrabPose;
#endregion
[SerializeField]
private bool m_ShowAllIndicator = false;
private List<Collider> allColliders = new List<Collider>();
private HandGrabInteractor closestGrabber = null;
[SerializeField]
private IOneHandContraintMovement m_OneHandContraintMovement;
public IOneHandContraintMovement oneHandContraintMovement { get { return m_OneHandContraintMovement; } set { m_OneHandContraintMovement = value; } }
public bool isContraint => m_OneHandContraintMovement != null;
#pragma warning disable
[SerializeField]
private int m_PreviewIndex = -1;
#pragma warning enable
private RaycastHit[] hitResults = new RaycastHit[10];
#region MonoBehaviour
private void Awake()
{
allColliders.AddRange(transform.GetComponentsInChildren<Collider>(true));
}
private void OnEnable()
{
GrabManager.RegisterGrabbable(this);
Initialize();
}
private void OnDisable()
{
GrabManager.UnregisterGrabbable(this);
}
#endregion
#region Public Interface
/// <summary>
/// Set the grabber for the hand grabbable object.
/// </summary>
/// <param name="grabber">The grabber to set.</param>
public void SetGrabber(IGrabber grabber)
{
if (grabber is HandGrabInteractor handGrabber)
{
m_Grabber = handGrabber;
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
UpdateBestGrabPose(handGrabber.isLeft, new Pose(wristPos, wristRot));
m_OnBeginGrabbed?.Invoke(this);
DEBUG($"{transform.name} is grabbed by {handGrabber.name}");
}
else
{
m_Grabber = null;
m_BestGrabPose = GrabPose.Identity;
m_OnEndGrabbed?.Invoke(this);
DEBUG($"{transform.name} is released.");
}
}
/// <summary>
/// Enable/Disable indicators. If enabled, display the closest indicator based on grabber position.
/// </summary>
/// <param name="enable">True to show the indicator, false to hide it.</param>
/// <param name="grabber">The grabber for which to show or hide this indicator.</param>
public void ShowIndicator(bool enable, HandGrabInteractor grabber)
{
if (enable)
{
closestGrabber = grabber;
if (m_ShowAllIndicator)
{
ShowAllIndicator(grabber.isLeft);
}
else
{
HandPose handPose = HandPoseProvider.GetHandPose(grabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
int index = FindBestGrabPose(grabber.isLeft, new Pose(wristPos, wristRot));
ShowIndicatorByIndex(index);
}
}
else
{
if (closestGrabber == grabber)
{
closestGrabber = null;
ShowIndicatorByIndex(-1);
}
}
}
/// <summary>
/// Calculate the shortest distance between the grabber and the grabbable and convert it into a score based on grabDistance.
/// </summary>
/// <param name="grabberPos">The current pose of grabber.</param>
/// <param name="grabDistance">The maximum grab distance between the grabber and the grabbable object.</param>
/// <returns>The score represents the distance between the grabber and the grabbable.</returns>
public float CalculateDistanceScore(Vector3 grabberPos, float grabDistance = 0.03f)
{
if (!isGrabbable || isGrabbed) { return 0; }
Vector3 closestPoint = GetClosestPoint(grabberPos);
float distanceSqr = (grabberPos - closestPoint).sqrMagnitude;
float grabDistSqr = grabDistance * grabDistance;
return distanceSqr > grabDistSqr ? 0 : 1 - (distanceSqr / grabDistSqr);
}
/// <summary>
/// Update the position and rotation of the self with the pose of the hand that is grabbing it.
/// </summary>
/// <param name="grabberPose">The pose of the hand.</param>
public void UpdatePositionAndRotation(Pose grabberPose)
{
if (m_OneHandContraintMovement == null)
{
Quaternion handRotDiff = grabberPose.rotation * Quaternion.Inverse(m_BestGrabPose.grabOffset.sourceRotation);
transform.position = grabberPose.position + handRotDiff * m_BestGrabPose.grabOffset.posOffset;
transform.rotation = grabberPose.rotation * m_BestGrabPose.grabOffset.rotOffset;
}
if (m_OneHandContraintMovement != null)
{
m_OneHandContraintMovement.UpdatePose(grabberPose);
UpdateMeshPoseByGrabbable();
}
}
#endregion
/// <summary>
/// Generate all indicators and calculate grab offsets.
/// </summary>
private void Initialize()
{
if (m_OneHandContraintMovement != null)
{
m_OneHandContraintMovement.Initialize(this);
onBeginGrabbed.AddListener(m_OneHandContraintMovement.OnBeginGrabbed);
onEndGrabbed.AddListener(m_OneHandContraintMovement.OnEndGrabbed);
}
for (int i = 0; i < m_GrabPoses.Count; i++)
{
if (m_GrabPoses[i].indicator.enableIndicator || m_ShowAllIndicator)
{
if (m_GrabPoses[i].indicator.NeedGenerateIndicator())
{
AutoGenerateIndicator(i);
}
else
{
GrabPose grabPose = m_GrabPoses[i];
grabPose.indicator.CalculateGrabOffset(transform.position, transform.rotation);
m_GrabPoses[i] = grabPose;
}
}
}
ShowIndicatorByIndex(-1);
}
/// <summary>
/// Automatically generate an indicator by the index of the grab pose.
/// </summary>
/// <param name="index">The index of the grab pose.</param>
private void AutoGenerateIndicator(int index)
{
AutoGenIndicator autoGenIndicator = new GameObject($"Indicator {index}", typeof(AutoGenIndicator)).GetComponent<AutoGenIndicator>();
GrabPose grabPose = m_GrabPoses[index];
Pose handPose = CalculateActualHandPose(grabPose.grabOffset);
Vector3 closestPoint = GetClosestPoint(handPose.position);
autoGenIndicator.SetPose(closestPoint, closestPoint - transform.position);
grabPose.indicator.Update(true, true, autoGenIndicator.gameObject);
grabPose.indicator.CalculateGrabOffset(transform.position, transform.rotation);
m_GrabPoses[index] = grabPose;
}
/// <summary>
/// Calculate the point closest to the source position.
/// </summary>
/// <param name="sourcePos">The position of source.</param>
/// <returns>The position which closest to the source position.</returns>
private Vector3 GetClosestPoint(Vector3 sourcePos)
{
Vector3 closestPoint = Vector3.zero;
float shortDistanceSqr = float.MaxValue;
for (int i = 0; i < allColliders.Count; i++)
{
Collider collider = allColliders[i];
Vector3 closePoint = collider.ClosestPointOnBounds(sourcePos);
float distanceSqr = (sourcePos - closePoint).sqrMagnitude;
if (distanceSqr < 0.001f)
{
return closePoint;
}
if (collider.bounds.Contains(closePoint))
{
Vector3 direction = closePoint - sourcePos;
direction.Normalize();
int hitCount = Physics.RaycastNonAlloc(sourcePos, direction, hitResults, Mathf.Sqrt(distanceSqr));
for (int j = 0; j < hitCount; j++)
{
RaycastHit hit = hitResults[j];
if (hit.collider == collider)
{
float hitDistanceSqr = (sourcePos - hit.point).sqrMagnitude;
if (distanceSqr > hitDistanceSqr)
{
distanceSqr = hitDistanceSqr;
closePoint = hit.point;
}
}
}
}
if (shortDistanceSqr > distanceSqr)
{
shortDistanceSqr = distanceSqr;
closestPoint = closePoint;
}
}
return closestPoint;
}
/// <summary>
/// Find the best grab pose for the grabber and updates the bestGrabPoseId.
/// </summary>
/// <param name="isLeft">Whether the grabber is the left hand.</param>
/// <param name="grabberPose">The pose of the grabber.</param>
/// <returns>True if a best grab pose is found; otherwise, false.</returns>
private void UpdateBestGrabPose(bool isLeft, Pose grabberPose)
{
int index = FindBestGrabPose(isLeft, grabberPose);
if (index != -1 && index < m_GrabPoses.Count)
{
m_BestGrabPose = m_GrabPoses[index];
}
else
{
m_BestGrabPose.grabOffset = new GrabOffset(grabberPose.position, grabberPose.rotation, transform.position, transform.rotation);
HandPose handPose = HandPoseProvider.GetHandPose(isLeft ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
if (handPose is MeshHandPose meshHandPose)
{
Quaternion[] grabRotations = new Quaternion[(int)JointType.Count];
for (int i = 0; i < (int)JointType.Count; i++)
{
meshHandPose.GetRotation((JointType)i, out grabRotations[i], local: true);
}
m_BestGrabPose.recordedGrabRotations = grabRotations;
}
}
}
/// <summary>
/// Find the best grab pose for the grabber.
/// </summary>
/// <param name="isLeft">Whether the grabber is the left hand.</param>
/// <param name="grabberPose">The pose of the grabber.</param>
/// <returns>The index of the best grab pose among the grab poses.</returns>
private int FindBestGrabPose(bool isLeft, Pose grabberPose)
{
int index = -1;
float maxDot = float.MinValue;
Vector3 currentDirection = grabberPose.position - transform.position;
for (int i = 0; i < m_GrabPoses.Count; i++)
{
if (m_GrabPoses[i].isLeft == isLeft)
{
Pose handPose = CalculateActualHandPose(m_GrabPoses[i].grabOffset);
Vector3 grabDirection = handPose.position - transform.position;
float dot = Vector3.Dot(currentDirection.normalized, grabDirection.normalized);
if (dot > maxDot)
{
maxDot = dot;
index = i;
}
}
}
return index;
}
/// <summary>
/// Show the indicator corresponding to the specified index and hides others.
/// </summary>
/// <param name="index">The index of the indicator to show.</param>
private void ShowIndicatorByIndex(int index)
{
for (int i = 0; i < m_GrabPoses.Count; i++)
{
if (index != i)
{
m_GrabPoses[i].indicator.SetActive(false);
}
}
if (index >= 0 && index < m_GrabPoses.Count &&
m_GrabPoses[index].indicator.enableIndicator)
{
m_GrabPoses[index].indicator.UpdatePositionAndRotation(transform.position, transform.rotation);
m_GrabPoses[index].indicator.SetActive(true);
}
}
/// <summary>
/// Show all indicators corresponding to the specified hand side and hides others.
/// </summary>
/// <param name="isLeft">Whether the hand side is left.</param>
private void ShowAllIndicator(bool isLeft)
{
for (int i = 0; i < m_GrabPoses.Count; i++)
{
m_GrabPoses[i].indicator.SetActive(false);
}
foreach (var grabPose in m_GrabPoses)
{
if (grabPose.isLeft == isLeft)
{
grabPose.indicator.UpdatePositionAndRotation(transform.position, transform.rotation);
grabPose.indicator.SetActive(true);
}
}
}
private void UpdateMeshPoseByGrabbable()
{
if (grabber is HandGrabInteractor handGrabber)
{
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
if (handPose != null && handPose is MeshHandPose meshHandPose)
{
Pose realHandPose = CalculateActualHandPose(m_BestGrabPose.grabOffset);
meshHandPose.SetJointPose(JointType.Wrist, realHandPose);
}
}
}
private Pose CalculateActualHandPose(GrabOffset grabOffset)
{
Quaternion handRot = transform.rotation * Quaternion.Inverse(grabOffset.rotOffset);
Quaternion handRotDiff = handRot * Quaternion.Inverse(grabOffset.sourceRotation);
Vector3 handPos = transform.position - handRotDiff * grabOffset.posOffset;
return new Pose(handPos, handRot);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b1c0e40da1ab9014c89d359be00fffb1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,320 @@
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the Wave SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System.Collections.Generic;
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to implement IHandGrabber, allowing objects to grab grabbable objects.
/// </summary>
public class HandGrabInteractor : MonoBehaviour, IHandGrabber
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandGrabInteractor";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
#endregion
private enum GrabState
{
None,
Hover,
Grabbing,
};
#region Public States
private HandGrabInteractable m_Grabbable = null;
public IGrabbable grabbable => m_Grabbable;
public bool isGrabbing => m_Grabbable != null;
[SerializeField]
private Handedness m_Handedness = Handedness.Left;
public Handedness handedness => m_Handedness;
private HandGrabState m_HandGrabState = null;
public HandGrabState handGrabState => m_HandGrabState;
[SerializeField]
private float m_GrabDistance = 0.03f;
public float grabDistance { get { return m_GrabDistance; } set { m_GrabDistance = value; } }
public bool isLeft => handedness == Handedness.Left;
[SerializeField]
private HandGrabberEvent m_OnBeginGrab = new HandGrabberEvent();
public HandGrabberEvent onBeginGrab => m_OnBeginGrab;
[SerializeField]
private HandGrabberEvent m_OnEndGrab = new HandGrabberEvent();
public HandGrabberEvent onEndGrab => m_OnEndGrab;
#endregion
private readonly float MinGrabScore = 0.25f;
private readonly float MinDistanceScore = 0.25f;
private HandGrabInteractable currentCandidate = null;
private GrabState m_State = GrabState.None;
private Pose wristPose = Pose.identity;
private Vector3[] fingerTipPosition = new Vector3[(int)FingerId.Count];
private const int kMaxCacheSize = 100;
private int lastBufferCount = 0;
private Collider[] colliderBuffer = new Collider[50];
private HandGrabInteractable[] grabbableBuffer = new HandGrabInteractable[50];
private LinkedList<Collider> lruList = new LinkedList<Collider>();
private Dictionary<Collider, LinkedListNode<Collider>> unusedColliders = new Dictionary<Collider, LinkedListNode<Collider>>();
#region MonoBehaviour
private void Awake()
{
m_HandGrabState = new HandGrabState(isLeft);
}
private void OnEnable()
{
GrabManager.RegisterGrabber(this);
}
private void OnDisable()
{
GrabManager.UnregisterGrabber(this);
}
private void Update()
{
m_HandGrabState.UpdateState();
HandPose handPose = HandPoseProvider.GetHandPose(isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
if (handPose != null)
{
handPose.GetPosition(JointType.Wrist, out wristPose.position);
handPose.GetRotation(JointType.Wrist, out wristPose.rotation);
handPose.GetPosition(JointType.Thumb_Tip, out fingerTipPosition[(int)FingerId.Thumb]);
handPose.GetPosition(JointType.Index_Tip, out fingerTipPosition[(int)FingerId.Index]);
handPose.GetPosition(JointType.Middle_Tip, out fingerTipPosition[(int)FingerId.Middle]);
handPose.GetPosition(JointType.Ring_Tip, out fingerTipPosition[(int)FingerId.Ring]);
handPose.GetPosition(JointType.Pinky_Tip, out fingerTipPosition[(int)FingerId.Pinky]);
}
if (m_State != GrabState.Grabbing)
{
FindCandidate();
}
switch (m_State)
{
case GrabState.None:
NoneUpdate();
break;
case GrabState.Hover:
HoverUpdate();
break;
case GrabState.Grabbing:
GrabbingUpdate();
break;
}
}
#endregion
#region Public Interface
/// <summary>
/// Checks if the specified joint type is required.
/// </summary>
/// <param name="joint">The joint that need to check.</param>
/// <returns>True if the joint is required; otherwise, false.</returns>
public bool IsRequiredJoint(JointType joint)
{
if (m_Grabbable != null)
{
HandData.GetJointIndex(joint, out int group, out _);
switch (group)
{
case 2: return m_Grabbable.fingerRequirement.thumb == GrabRequirement.Required;
case 3: return m_Grabbable.fingerRequirement.index == GrabRequirement.Required;
case 4: return m_Grabbable.fingerRequirement.middle == GrabRequirement.Required;
case 5: return m_Grabbable.fingerRequirement.ring == GrabRequirement.Required;
case 6: return m_Grabbable.fingerRequirement.pinky == GrabRequirement.Required;
}
}
return false;
}
#endregion
/// <summary>
/// Find the candidate grabbable object for grabber.
/// </summary>
private void FindCandidate()
{
float distanceScore = float.MinValue;
if (GetClosestGrabbable(m_GrabDistance, out HandGrabInteractable grabbable, out float score) && score > distanceScore)
{
distanceScore = score;
currentCandidate = grabbable;
}
if (currentCandidate != null)
{
float grabScore = Grab.CalculateHandGrabScore(this, currentCandidate);
if (distanceScore < MinDistanceScore || grabScore < MinGrabScore)
{
currentCandidate = null;
}
}
}
/// <summary>
/// Get the closest grabbable object for grabber.
/// </summary>
/// <param name="grabDistance">The maximum grab distance between the grabber and the grabbable object.</param>
/// <param name="grabbable">The closest grabbable object.</param>
/// <param name="maxScore">The maximum score indicating the closeness of the grabbable object.</param>
/// <returns>True if a grabbable object is found within the grab distance; otherwise, false.</returns>
private bool GetClosestGrabbable(float grabDistance, out HandGrabInteractable grabbable, out float maxScore)
{
grabbable = null;
maxScore = 0f;
for (int i = 0; i < lastBufferCount; i++)
{
HandGrabInteractable interactable = grabbableBuffer[i];
interactable.ShowIndicator(false, this);
}
int colliderCount = Physics.OverlapSphereNonAlloc(wristPose.position, grabDistance * 5, colliderBuffer);
int interactableCount = 0;
for (int i = 0; i < colliderCount; i++)
{
Collider collider = colliderBuffer[i];
if (unusedColliders.TryGetValue(collider, out _)) { continue; }
HandGrabInteractable interactable = collider.GetComponentInParent<HandGrabInteractable>()
?? collider.GetComponentInChildren<HandGrabInteractable>();
if (interactable != null)
{
bool isUnique = true;
for (int j = 0; j < interactableCount; j++)
{
if (grabbableBuffer[j] == interactable)
{
isUnique = false;
break;
}
}
if (isUnique)
{
grabbableBuffer[interactableCount++] = interactable;
}
}
else
{
AddUnusedColliders(collider);
}
}
lastBufferCount = interactableCount;
for (int i = 0; i < interactableCount; i++)
{
HandGrabInteractable interactable = grabbableBuffer[i];
for (int j = 0; j < fingerTipPosition.Length; j++)
{
float distanceScore = interactable.CalculateDistanceScore(fingerTipPosition[j], grabDistance);
if (distanceScore > maxScore)
{
maxScore = distanceScore;
grabbable = interactable;
}
}
}
if (grabbable != null)
{
grabbable.ShowIndicator(true, this);
}
return grabbable != null;
}
/// <summary>
/// Set the state to GrabState.Hover if a candidate is found.
/// </summary>
private void NoneUpdate()
{
if (currentCandidate != null)
{
m_State = GrabState.Hover;
}
}
/// <summary>
/// Update the state and related information when the grabber begins grabbing the grabbable.
/// </summary>
private void HoverUpdate()
{
if (currentCandidate == null)
{
m_State = GrabState.None;
return;
}
if (Grab.HandBeginGrab(this, currentCandidate))
{
m_State = GrabState.Grabbing;
m_Grabbable = currentCandidate;
m_Grabbable.SetGrabber(this);
m_Grabbable.ShowIndicator(false, this);
onBeginGrab?.Invoke(this);
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand begins to grab the {m_Grabbable.name}");
}
}
/// <summary>
/// Update the position of grabbable object according to the movement of the grabber.
/// </summary>
private void GrabbingUpdate()
{
if (Grab.HandDoneGrab(this, m_Grabbable) || !Grab.HandIsGrabbing(this, m_Grabbable))
{
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand ends to grab the {m_Grabbable.name}");
onEndGrab?.Invoke(this);
m_Grabbable.SetGrabber(null);
m_Grabbable = null;
m_State = GrabState.Hover;
return;
}
m_Grabbable.UpdatePositionAndRotation(wristPose);
}
private void AddUnusedColliders(Collider collider)
{
if (lruList.Count >= kMaxCacheSize)
{
var oldest = lruList.First;
unusedColliders.Remove(oldest.Value);
lruList.RemoveFirst();
}
var node = lruList.AddLast(collider);
unusedColliders[collider] = node;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3155365f073fdb45ba7a61887f8cf06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,155 @@
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class OneGrabMoveConstraint : IOneHandContraintMovement
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.OneGrabMoveConstraint";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
[SerializeField]
private Transform m_Constraint;
[SerializeField]
private ConstraintInfo m_NegativeXMove = ConstraintInfo.Identity;
private float defaultNegativeXPos = 0.0f;
public float xNegativeBoundary => defaultNegativeXPos;
[SerializeField]
private ConstraintInfo m_PositiveXMove = ConstraintInfo.Identity;
private float defaultPositiveXPos = 0.0f;
public float xPositiveBoundary => defaultPositiveXPos;
[SerializeField]
private ConstraintInfo m_NegativeYMove = ConstraintInfo.Identity;
private float defaultNegativeYPos = 0.0f;
public float yNegativeBoundary => defaultNegativeYPos;
[SerializeField]
private ConstraintInfo m_PositiveYMove = ConstraintInfo.Identity;
private float defaultPositiveYPos = 0.0f;
public float yPositiveBoundary => defaultPositiveYPos;
[SerializeField]
private ConstraintInfo m_NegativeZMove = ConstraintInfo.Identity;
private float defaultNegativeZPos = 0.0f;
public float zNegativeBoundary => defaultNegativeZPos;
[SerializeField]
private ConstraintInfo m_PositiveZMove = ConstraintInfo.Identity;
private float defaultPositiveZPos = 0.0f;
public float zPositiveBoundary => defaultPositiveZPos;
private Pose previousHandPose = Pose.identity;
private GrabPose currentGrabPose = GrabPose.Identity;
public override void Initialize(IGrabbable grabbable)
{
if (grabbable is HandGrabInteractable handGrabbable)
{
if (m_Constraint == null)
{
m_Constraint = handGrabbable.transform;
WARNING("Since no constraint object is set, self will be used as the constraint object.");
}
}
if (m_NegativeXMove.enableConstraint) { defaultNegativeXPos = m_Constraint.position.x - m_NegativeXMove.value; }
if (m_PositiveXMove.enableConstraint) { defaultPositiveXPos = m_Constraint.position.x + m_PositiveXMove.value; }
if (m_NegativeYMove.enableConstraint) { defaultNegativeYPos = m_Constraint.position.y - m_NegativeYMove.value; }
if (m_PositiveYMove.enableConstraint) { defaultPositiveYPos = m_Constraint.position.y + m_PositiveYMove.value; }
if (m_NegativeZMove.enableConstraint) { defaultNegativeZPos = m_Constraint.position.z - m_NegativeZMove.value; }
if (m_PositiveZMove.enableConstraint) { defaultPositiveZPos = m_Constraint.position.z + m_PositiveZMove.value; }
}
public override void OnBeginGrabbed(IGrabbable grabbable)
{
if (grabbable is HandGrabInteractable handGrabbable)
{
currentGrabPose = handGrabbable.bestGrabPose;
}
if (grabbable.grabber is HandGrabInteractor handGrabber)
{
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
previousHandPose = new Pose(wristPos, wristRot);
}
}
public override void UpdatePose(Pose handPose)
{
if (previousHandPose == Pose.identity)
{
previousHandPose = handPose;
return;
}
Quaternion previousRotOffset = previousHandPose.rotation * Quaternion.Inverse(currentGrabPose.grabOffset.sourceRotation);
Vector3 previousPos = previousHandPose.position + previousRotOffset * currentGrabPose.grabOffset.posOffset;
Quaternion currentRotOffset = handPose.rotation * Quaternion.Inverse(currentGrabPose.grabOffset.sourceRotation);
Vector3 currentPos = handPose.position + currentRotOffset * currentGrabPose.grabOffset.posOffset;
Vector3 handOffset = currentPos - previousPos;
if (m_NegativeXMove.enableConstraint)
{
float x = (m_Constraint.position + handOffset).x;
x = Mathf.Max(defaultNegativeXPos, x);
m_Constraint.position = new Vector3(x, m_Constraint.position.y, m_Constraint.position.z);
}
if (m_PositiveXMove.enableConstraint)
{
float x = (m_Constraint.position + handOffset).x;
x = Mathf.Min(defaultPositiveXPos, x);
m_Constraint.position = new Vector3(x, m_Constraint.position.y, m_Constraint.position.z);
}
if (m_NegativeYMove.enableConstraint)
{
float y = (m_Constraint.position + handOffset).y;
y = Mathf.Max(defaultNegativeYPos, y);
m_Constraint.position = new Vector3(m_Constraint.position.x, y, m_Constraint.position.z);
}
if (m_PositiveYMove.enableConstraint)
{
float y = (m_Constraint.position + handOffset).y;
y = Mathf.Min(defaultPositiveYPos, y);
m_Constraint.position = new Vector3(m_Constraint.position.x, y, m_Constraint.position.z);
}
if (m_NegativeZMove.enableConstraint)
{
float z = (m_Constraint.position + handOffset).z;
z = Mathf.Max(defaultNegativeZPos, z);
m_Constraint.position = new Vector3(m_Constraint.position.x, m_Constraint.position.y, z);
}
if (m_PositiveZMove.enableConstraint)
{
float z = (m_Constraint.position + handOffset).z;
z = Mathf.Min(defaultPositiveZPos, z);
m_Constraint.position = new Vector3(m_Constraint.position.x, m_Constraint.position.y, z);
}
previousHandPose = handPose;
}
public override void OnEndGrabbed(IGrabbable grabbable)
{
currentGrabPose = GrabPose.Identity;
previousHandPose = Pose.identity;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d521c71dbb8287409daf6ef81005d79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,126 @@
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class OneGrabRotateConstraint : IOneHandContraintMovement
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.OneGrabRotateConstraint";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
private enum RotationAxis
{
XAxis = 0,
YAxis = 1,
ZAxis = 2,
}
[SerializeField]
private Transform m_Constraint;
[SerializeField]
private Transform m_Pivot;
[SerializeField]
private RotationAxis m_RotationAxis = RotationAxis.XAxis;
[SerializeField]
private ConstraintInfo m_ClockwiseAngle = ConstraintInfo.Identity;
public float clockwiseAngle => m_ClockwiseAngle.value;
[SerializeField]
private ConstraintInfo m_CounterclockwiseAngle = ConstraintInfo.Identity;
public float counterclockwiseAngle => m_CounterclockwiseAngle.value;
private float m_TotalDegrees = 0.0f;
public float totalDegrees => m_TotalDegrees;
private Pose previousHandPose = Pose.identity;
public override void Initialize(IGrabbable grabbable)
{
if (grabbable is HandGrabInteractable handGrabbable)
{
if (m_Constraint == null)
{
m_Constraint = handGrabbable.transform;
WARNING("Since no constraint object is set, self will be used as the constraint object.");
}
if (m_Pivot == null)
{
m_Pivot = handGrabbable.transform;
WARNING("Since no pivot is set, self will be used as the pivot.");
}
}
}
public override void OnBeginGrabbed(IGrabbable grabbable)
{
if (grabbable.grabber is HandGrabInteractor handGrabber)
{
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
previousHandPose = new Pose(wristPos, wristRot);
}
}
public override void UpdatePose(Pose handPose)
{
if (previousHandPose == Pose.identity)
{
previousHandPose = handPose;
return;
}
Vector3 axis = Vector3.zero;
switch (m_RotationAxis)
{
case RotationAxis.XAxis: axis = Vector3.right; break;
case RotationAxis.YAxis: axis = Vector3.up; break;
case RotationAxis.ZAxis: axis = Vector3.forward; break;
}
Vector3 worldAxis = m_Pivot.TransformDirection(axis);
Vector3 previousOffset = previousHandPose.position - m_Pivot.position;
Vector3 previousVector = Vector3.ProjectOnPlane(previousOffset, worldAxis);
Vector3 targetOffset = handPose.position - m_Pivot.position;
Vector3 targetVector = Vector3.ProjectOnPlane(targetOffset, worldAxis);
float angleDelta = Vector3.Angle(previousVector, targetVector);
angleDelta *= Vector3.Dot(Vector3.Cross(previousVector, targetVector), worldAxis) > 0.0f ? 1.0f : -1.0f;
float previousAngle = m_TotalDegrees;
m_TotalDegrees += angleDelta;
if (m_CounterclockwiseAngle.enableConstraint)
{
m_TotalDegrees = Mathf.Max(m_TotalDegrees, -m_CounterclockwiseAngle.value);
}
if (m_ClockwiseAngle.enableConstraint)
{
m_TotalDegrees = Mathf.Min(m_TotalDegrees, m_ClockwiseAngle.value);
}
angleDelta = m_TotalDegrees - previousAngle;
m_Constraint.RotateAround(m_Pivot.position, worldAxis, angleDelta);
previousHandPose = handPose;
}
public override void OnEndGrabbed(IGrabbable grabbable)
{
previousHandPose = Pose.identity;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f25051021522804d90dc5448b7657a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More