上传YomovSDK
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ade49fa87f3cc404ea1b73cf2066c9a5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5dd060e5d33dc942adbd03d6b9dc9bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5544ca439cd389c4ba5255504ce221a4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 744a8b8476e9f394b87927173e7994b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c76f10664e4be14c9f57109107c63ed
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f35db36b366ffe458967ad9820f0b34
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a35a47ea932ca8845b42e834c36d2205
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d622bde162fe6524aa1cf99ee5bbda11
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ff3816ca3a86354f81e7ad4d29b2937
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fc3b0937a9ed7d4a82e0a0c262beaf4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3a84b60570d8b243984fbe7169ca44a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f234725d9eefc7540843fd691e94553a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10e073adb659279408a85f7b31ed9d91
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9bfb5ce0ea49a0a4cb998989754606c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2e0dce69c4834e47b59f25575269566
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9a059009a1d2414c94f3b9c7e46dfe9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f0a3b1ff9cb22f4293ff346f84a541e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee430aebc144da341979174bcccf7e5a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58897b8e0a7d7b24999d04f6ab923561
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6f2f61f885577442b9a4cafff9a62e6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58d737ad5355ee04eb3f7f57de71df1e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 571cc732a01df91488bf9c7a5909fead
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 105680c0299ff144b972446cb07a81c5
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
preprocessorOverride: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 827931b60f4bb6c438a0a3473ead12a5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c68c9c4b6f2ba4843ac07957f9727968
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0ed4455b73284942bc407f2d8a98eee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78ba47d171a9d604e87df3c8c0304fde
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d1efc78bbff2cc4eb930103b4111b67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34e90154ac6fdd64f85236ba9b967440
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74b5628be71247e4f80cb6ddc4d7025b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e8ff0b4ae49c1a4ea049f9e8afe0230
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 928994fe36775ae4782045d0503ba99f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1caf898641806b3448ff86ad2e1b0727
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fe9581cc98d0ff4fb8250646cc0359f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa3c636a6aff8234b9c9e019b5396109
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff1e9b9f321387c4e86e6a5fa6cd65c9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s 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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c01f88bc88bb27849a723888721c96f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s 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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ba007edb5befc349a95aa1cfa7cb6c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8e7c213fed939e4b859638cde97a31c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d606d68d81c647241b90f1060786476c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e312e5246287a2546944bb77519270c4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3744e08fd384e154c82d53686f0c82a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44fadaef2720de846af62a7bbb2ec370
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1504c5149524a8a44a3854dfd4f94d1e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a10ddbfcbce64f84787e026f4fc003a4
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 950573398b7eb5149bea536bfa6107ca
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32007394341a17c44859030ef5b809ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 010c0098d232cb0428f42a48488a6255
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9731229184277b54ba66bffd1633169b
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b7f1f69b8b23e9459efda715941fbee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea1b2b1a3faba024aa3df5391529aec0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4694bdada589dce4c9b7096c7169833f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd5957dc7b39bd249885b5bb53749b7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 252950eac28fb1f4cb1eae8e653f92ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s 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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53503199deedf444e84f1714b700737d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s 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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9119682127e09314ca250470f13db0f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1c0e40da1ab9014c89d359be00fffb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3155365f073fdb45ba7a61887f8cf06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d521c71dbb8287409daf6ef81005d79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user