version 2.5.0
This commit is contained in:
@@ -3,8 +3,11 @@ using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using VIVE.OpenXR.Feature;
|
||||
using VIVE.OpenXR.Anchor;
|
||||
using static VIVE.OpenXR.Anchor.ViveAnchor;
|
||||
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
|
||||
{
|
||||
@@ -12,8 +15,9 @@ namespace VIVE.OpenXR.Toolkits.Anchor
|
||||
{
|
||||
static ViveAnchor feature = null;
|
||||
static bool isSupported = false;
|
||||
static bool isPersistedAnchorSupported = false;
|
||||
|
||||
static void CheckFeature()
|
||||
static void EnsureFeature()
|
||||
{
|
||||
if (feature != null) return;
|
||||
|
||||
@@ -22,28 +26,32 @@ namespace VIVE.OpenXR.Toolkits.Anchor
|
||||
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 extention feature instance.
|
||||
/// Helper to get the extension feature instance.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>Instance of ViveAnchor feature.</returns>
|
||||
public static ViveAnchor GetFeature()
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckFeature();
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
Debug.LogWarning("ViveAnchor feature is not enabled");
|
||||
return null;
|
||||
}
|
||||
if (feature != null) return feature;
|
||||
feature = OpenXRSettings.Instance.GetFeature<ViveAnchor>();
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the extension is supported.
|
||||
/// Check if the extensions are supported. Should always check this before using the other functions.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>True if the extension is supported, false otherwise.</returns>
|
||||
public static bool IsSupported()
|
||||
{
|
||||
if (GetFeature() == null) return false;
|
||||
@@ -52,18 +60,32 @@ namespace VIVE.OpenXR.Toolkits.Anchor
|
||||
var ret = false;
|
||||
if (feature.GetProperties(out XrSystemAnchorPropertiesHTC properties) == XrResult.XR_SUCCESS)
|
||||
{
|
||||
Debug.Log("Anchor: IsSupported() properties.supportedFeatures: " + properties.supportsAnchor);
|
||||
ret = properties.supportsAnchor;
|
||||
Debug.Log("ViveAnchor: IsSupported() properties.supportedFeatures: " + properties.supportsAnchor);
|
||||
ret = properties.supportsAnchor > 0;
|
||||
isSupported = ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Anchor: IsSupported() GetSystemProperties failed.");
|
||||
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>
|
||||
@@ -71,38 +93,51 @@ namespace VIVE.OpenXR.Toolkits.Anchor
|
||||
/// <returns>Anchor container</returns>
|
||||
public static Anchor CreateAnchor(Pose pose, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckFeature();
|
||||
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.name = name;
|
||||
createInfo.space = baseSpace;
|
||||
EnsureFeature();
|
||||
|
||||
if (feature.CreateSpatialAnchor(createInfo, out XrSpace anchor) == XrResult.XR_SUCCESS)
|
||||
{
|
||||
return new Anchor(anchor, name);
|
||||
}
|
||||
} catch (Exception) { }
|
||||
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 = "";
|
||||
CheckFeature();
|
||||
EnsureFeature();
|
||||
XrResult ret = feature.GetSpatialAnchorName(anchor, out XrSpatialAnchorNameHTC xrName);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
name = xrName.name;
|
||||
name = xrName.ToString();
|
||||
return ret == XrResult.XR_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -112,7 +147,7 @@ namespace VIVE.OpenXR.Toolkits.Anchor
|
||||
/// <returns></returns>
|
||||
public static XrSpace GetTrackingSpace()
|
||||
{
|
||||
CheckFeature();
|
||||
EnsureFeature();
|
||||
return feature.GetTrackingSpace();
|
||||
}
|
||||
|
||||
@@ -128,9 +163,435 @@ namespace VIVE.OpenXR.Toolkits.Anchor
|
||||
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>
|
||||
/// Anchor is a named Space. It can be used to create a spatial anchor, or get the anchor's name.
|
||||
/// After use it, you should call Dispose() to release the anchor.
|
||||
/// 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
|
||||
{
|
||||
@@ -152,29 +613,60 @@ namespace VIVE.OpenXR.Toolkits.Anchor
|
||||
}
|
||||
}
|
||||
|
||||
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></returns>
|
||||
/// <returns>Anchor's name. Always return non null string.</returns>
|
||||
public string GetSpatialAnchorName()
|
||||
{
|
||||
AnchorManager.CheckFeature();
|
||||
if (space == 0)
|
||||
{
|
||||
Debug.LogError("Anchor: GetSpatialAnchorName: The anchor is invalid.");
|
||||
return "";
|
||||
}
|
||||
AnchorManager.EnsureFeature();
|
||||
if (AnchorManager.GetSpatialAnchorName(this, out string name))
|
||||
return name;
|
||||
return null;
|
||||
|
||||
Debug.LogError("Anchor: GetSpatialAnchorName: Failed to get Anchor name.");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Common.meta
Normal file
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Common.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5544ca439cd389c4ba5255504ce221a4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
815
com.htc.upm.vive.openxr/Runtime/Toolkits/Common/VIVEInput.cs
Normal file
815
com.htc.upm.vive.openxr/Runtime/Toolkits/Common/VIVEInput.cs
Normal file
@@ -0,0 +1,815 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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 struct InputActionMapping
|
||||
{
|
||||
public DeviceCategory device;
|
||||
public PoseState poseState;
|
||||
public ButtonEvent buttonEvent;
|
||||
public HandEvent handEvent;
|
||||
public InputAction inputAction { get; private set; }
|
||||
|
||||
public InputActionMapping(string bindingPath, DeviceCategory device,
|
||||
PoseState poseState = PoseState.None, ButtonEvent buttonEvent = ButtonEvent.None, HandEvent handEvent = HandEvent.None)
|
||||
{
|
||||
inputAction = new InputAction(binding: bindingPath);
|
||||
inputAction.Enable();
|
||||
this.device = device;
|
||||
this.poseState = poseState;
|
||||
this.buttonEvent = buttonEvent;
|
||||
this.handEvent = 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 isValid, Vector3 position, Quaternion rotation)
|
||||
{
|
||||
this.isValid = isValid;
|
||||
this.position = position;
|
||||
this.rotation = 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; }
|
||||
|
||||
public HandData(JointData[] joints)
|
||||
{
|
||||
this.joints = joints;
|
||||
isTracked = !this.joints.Any(x => x.isValid == false);
|
||||
updateTime = Time.frameCount;
|
||||
}
|
||||
|
||||
public void Update(JointData[] joints)
|
||||
{
|
||||
this.joints = joints;
|
||||
isTracked = !this.joints.Any(x => x.isValid == false);
|
||||
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 isInitInputActions = false;
|
||||
private static List<InputActionMapping> inputActions = new List<InputActionMapping>();
|
||||
private static HandData leftHand = HandData.Identify;
|
||||
private static HandData rightHand = HandData.Identify;
|
||||
#if UNITY_XR_HANDS
|
||||
private static XRHandSubsystem handSubsystem = null;
|
||||
#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
|
||||
{
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == device && x.poseState == poseState);
|
||||
if (inputActionMapping == null) { return false; }
|
||||
|
||||
try
|
||||
{
|
||||
eventResult = inputActionMapping.inputAction.ReadValue<float>() > 0;
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
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
|
||||
{
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == device && x.poseState == poseState);
|
||||
if (inputActionMapping == null) { return false; }
|
||||
|
||||
try
|
||||
{
|
||||
eventResult = inputActionMapping.inputAction.ReadValue<Vector3>();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
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
|
||||
{
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == device && x.poseState == poseState);
|
||||
if (inputActionMapping == null) { return false; }
|
||||
|
||||
try
|
||||
{
|
||||
eventResult = inputActionMapping.inputAction.ReadValue<Quaternion>();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
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;
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == GetController(handedness) && x.buttonEvent == buttonEvent);
|
||||
if (inputActionMapping != null)
|
||||
{
|
||||
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;
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == GetController(handedness) && x.buttonEvent == buttonEvent);
|
||||
if (inputActionMapping != null)
|
||||
{
|
||||
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;
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == GetController(handedness) && x.buttonEvent == buttonEvent);
|
||||
if (inputActionMapping != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
eventResult = inputActionMapping.inputAction.ReadValue<float>() == 1;
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == GetController(handedness) && x.buttonEvent == buttonEvent);
|
||||
if (inputActionMapping != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
eventResult = inputActionMapping.inputAction.ReadValue<float>();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == GetController(handedness) && x.buttonEvent == buttonEvent);
|
||||
if (inputActionMapping != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
eventResult = inputActionMapping.inputAction.ReadValue<Vector2>();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == GetHand(handedness) && x.handEvent == handEvent);
|
||||
if (inputActionMapping != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
eventResult = inputActionMapping.inputAction.ReadValue<float>();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
InputActionMapping inputActionMapping = inputActions.FirstOrDefault(x => x.device == GetHand(handedness) && x.handEvent == handEvent);
|
||||
if (inputActionMapping != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
UnityEngine.XR.OpenXR.Input.Pose pose = inputActionMapping.inputAction.ReadValue<UnityEngine.XR.OpenXR.Input.Pose>();
|
||||
eventResult = new Pose(pose.position, pose.rotation);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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(leftHand.joints[(int)joint].position, leftHand.joints[(int)joint].rotation);
|
||||
return leftHand.joints[(int)joint].isValid;
|
||||
}
|
||||
else
|
||||
{
|
||||
jointPose = new Pose(rightHand.joints[(int)joint].position, rightHand.joints[(int)joint].rotation);
|
||||
return 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 ? leftHand.isTracked : rightHand.isTracked;
|
||||
}
|
||||
|
||||
public static bool IsHandValidate()
|
||||
{
|
||||
ViveHandTracking viveHand = OpenXRSettings.Instance.GetFeature<ViveHandTracking>();
|
||||
if (viveHand)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#if UNITY_XR_HANDS
|
||||
HandTracking xrHand = OpenXRSettings.Instance.GetFeature<HandTracking>();
|
||||
if (xrHand)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[RuntimeInitializeOnLoadMethod]
|
||||
private static bool CheckInitialize()
|
||||
{
|
||||
if (!isInitInputActions)
|
||||
{
|
||||
Initialized();
|
||||
isInitInputActions = true;
|
||||
}
|
||||
return isInitInputActions;
|
||||
}
|
||||
|
||||
private static void Initialized()
|
||||
{
|
||||
#region Head
|
||||
inputActions.Add(new InputActionMapping("<XRHMD>/isTracked", DeviceCategory.HMD, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<XRHMD>/centerEyePosition", DeviceCategory.HMD, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<XRHMD>/centerEyeRotation", DeviceCategory.HMD, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<XRHMD>/centerEyeVelocity", DeviceCategory.HMD, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<XRHMD>/centerEyeAngularVelocity", DeviceCategory.HMD, poseState: PoseState.AngularVelocity));
|
||||
inputActions.Add(new InputActionMapping("<XRHMD>/centerEyeAcceleration", DeviceCategory.HMD, poseState: PoseState.Acceleration));
|
||||
inputActions.Add(new InputActionMapping("<XRHMD>/centerEyeAngularAcceleration", DeviceCategory.HMD, poseState: PoseState.AngularAcceleration));
|
||||
#endregion
|
||||
#region Eye
|
||||
inputActions.Add(new InputActionMapping("<EyeGaze>/pose/isTracked", DeviceCategory.CenterEye, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<EyeGaze>/pose/position", DeviceCategory.CenterEye, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<EyeGaze>/pose/rotation", DeviceCategory.CenterEye, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<EyeGaze>/pose/velocity", DeviceCategory.CenterEye, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<EyeGaze>/pose/angularVelocity", DeviceCategory.CenterEye, poseState: PoseState.AngularVelocity));
|
||||
#endregion
|
||||
#region Controller
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/isTracked", DeviceCategory.LeftController, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/pointerPosition", DeviceCategory.LeftController, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/pointerRotation", DeviceCategory.LeftController, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/deviceVelocity", DeviceCategory.LeftController, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/deviceAngularVelocity", DeviceCategory.LeftController, poseState: PoseState.AngularVelocity));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/deviceAcceleration", DeviceCategory.LeftController, poseState: PoseState.Acceleration));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/deviceAngularAcceleration", DeviceCategory.LeftController, poseState: PoseState.AngularAcceleration));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{grip}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.GripValue));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{gripButton}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.GripPress));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{trigger}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.TriggerValue));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/triggerTouched", DeviceCategory.LeftController, buttonEvent: ButtonEvent.TriggerTouch));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{triggerButton}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.TriggerPress));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{primary2DAxis}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.Primary2DAxisValue));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{primary2DAxisTouch}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.Primary2DAxisTouch));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{primary2DAxisClick}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.Primary2DAxisPress));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{secondary2DAxis}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.Secondary2DAxisValue));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{secondary2DAxisTouch}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.Secondary2DAxisTouch));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{secondary2DAxisClick}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.Secondary2DAxisPress));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{primaryButton}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.PrimaryButton));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/{secondaryButton}", DeviceCategory.LeftController, buttonEvent: ButtonEvent.SecondaryButton));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/parkingTouched", DeviceCategory.LeftController, buttonEvent: ButtonEvent.ParkingTouch));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{LeftHand}/menu", DeviceCategory.LeftController, buttonEvent: ButtonEvent.Menu));
|
||||
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/isTracked", DeviceCategory.RightController, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/pointerPosition", DeviceCategory.RightController, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/pointerRotation", DeviceCategory.RightController, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/deviceVelocity", DeviceCategory.RightController, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/deviceAngularVelocity", DeviceCategory.RightController, poseState: PoseState.AngularVelocity));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/deviceAcceleration", DeviceCategory.RightController, poseState: PoseState.Acceleration));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/deviceAngularAcceleration", DeviceCategory.RightController, poseState: PoseState.AngularAcceleration));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{grip}", DeviceCategory.RightController, buttonEvent: ButtonEvent.GripValue));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{gripButton}", DeviceCategory.RightController, buttonEvent: ButtonEvent.GripPress));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{trigger}", DeviceCategory.RightController, buttonEvent: ButtonEvent.TriggerValue));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/triggerTouched", DeviceCategory.RightController, buttonEvent: ButtonEvent.TriggerTouch));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{triggerButton}", DeviceCategory.RightController, buttonEvent: ButtonEvent.TriggerPress));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{primary2DAxis}", DeviceCategory.RightController, buttonEvent: ButtonEvent.Primary2DAxisValue));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{primary2DAxisTouch}", DeviceCategory.RightController, buttonEvent: ButtonEvent.Primary2DAxisTouch));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{primary2DAxisClick}", DeviceCategory.RightController, buttonEvent: ButtonEvent.Primary2DAxisPress));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{secondary2DAxis}", DeviceCategory.RightController, buttonEvent: ButtonEvent.Secondary2DAxisValue));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{secondary2DAxisTouch}", DeviceCategory.RightController, buttonEvent: ButtonEvent.Secondary2DAxisTouch));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{secondary2DAxisClick}", DeviceCategory.RightController, buttonEvent: ButtonEvent.Secondary2DAxisPress));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{primaryButton}", DeviceCategory.RightController, buttonEvent: ButtonEvent.PrimaryButton));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/{secondaryButton}", DeviceCategory.RightController, buttonEvent: ButtonEvent.SecondaryButton));
|
||||
inputActions.Add(new InputActionMapping("<XRController>{RightHand}/parkingTouched", DeviceCategory.RightController, buttonEvent: ButtonEvent.ParkingTouch));
|
||||
#endregion
|
||||
#region Hand
|
||||
inputActions.Add(new InputActionMapping("<ViveHandInteraction>{LeftHand}/selectValue", DeviceCategory.LeftHand, handEvent: HandEvent.PinchValue));
|
||||
inputActions.Add(new InputActionMapping("<ViveHandInteraction>{LeftHand}/pointerPose", DeviceCategory.LeftHand, handEvent: HandEvent.PinchPose));
|
||||
inputActions.Add(new InputActionMapping("<ViveHandInteraction>{LeftHand}/gripValue", DeviceCategory.LeftHand, handEvent: HandEvent.GraspValue));
|
||||
inputActions.Add(new InputActionMapping("<ViveHandInteraction>{LeftHand}/devicePose", DeviceCategory.LeftHand, handEvent: HandEvent.GraspPose));
|
||||
|
||||
inputActions.Add(new InputActionMapping("<ViveHandInteraction>{RightHand}/selectValue", DeviceCategory.RightHand, handEvent: HandEvent.PinchValue));
|
||||
inputActions.Add(new InputActionMapping("<ViveHandInteraction>{RightHand}/pointerPose", DeviceCategory.RightHand, handEvent: HandEvent.PinchPose));
|
||||
inputActions.Add(new InputActionMapping("<ViveHandInteraction>{RightHand}/gripValue", DeviceCategory.RightHand, handEvent: HandEvent.GraspValue));
|
||||
inputActions.Add(new InputActionMapping("<ViveHandInteraction>{RightHand}/devicePose", DeviceCategory.RightHand, handEvent: HandEvent.GraspPose));
|
||||
#endregion
|
||||
#region Tracker
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/devicePose/isTracked", DeviceCategory.Tracker0, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/devicePosition", DeviceCategory.Tracker0, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/deviceRotation", DeviceCategory.Tracker0, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/devicePose/velocity", DeviceCategory.Tracker0, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 0}/devicePose/angularVelocity", DeviceCategory.Tracker0, poseState: PoseState.AngularVelocity));
|
||||
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/devicePose/isTracked", DeviceCategory.Tracker1, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/devicePosition", DeviceCategory.Tracker1, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/deviceRotation", DeviceCategory.Tracker1, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/devicePose/velocity", DeviceCategory.Tracker1, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 1}/devicePose/angularVelocity", DeviceCategory.Tracker1, poseState: PoseState.AngularVelocity));
|
||||
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/devicePose/isTracked", DeviceCategory.Tracker2, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/devicePosition", DeviceCategory.Tracker2, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/deviceRotation", DeviceCategory.Tracker2, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/devicePose/velocity", DeviceCategory.Tracker2, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 2}/devicePose/angularVelocity", DeviceCategory.Tracker2, poseState: PoseState.AngularVelocity));
|
||||
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/devicePose/isTracked", DeviceCategory.Tracker3, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/devicePosition", DeviceCategory.Tracker3, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/deviceRotation", DeviceCategory.Tracker3, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/devicePose/velocity", DeviceCategory.Tracker3, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 3}/devicePose/angularVelocity", DeviceCategory.Tracker3, poseState: PoseState.AngularVelocity));
|
||||
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/devicePose/isTracked", DeviceCategory.Tracker4, poseState: PoseState.IsTracked));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/devicePosition", DeviceCategory.Tracker4, poseState: PoseState.Position));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/deviceRotation", DeviceCategory.Tracker4, poseState: PoseState.Rotation));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/devicePose/velocity", DeviceCategory.Tracker4, poseState: PoseState.Velocity));
|
||||
inputActions.Add(new InputActionMapping("<ViveXRTracker>{Ultimate Tracker 4}/devicePose/angularVelocity", DeviceCategory.Tracker4, poseState: PoseState.AngularVelocity));
|
||||
#endregion
|
||||
}
|
||||
|
||||
private static void CheckHandUpdated()
|
||||
{
|
||||
if (Time.frameCount > leftHand.updateTime ||
|
||||
Time.frameCount > rightHand.updateTime)
|
||||
{
|
||||
ViveHandTracking viveHand = OpenXRSettings.Instance.GetFeature<ViveHandTracking>();
|
||||
if (viveHand)
|
||||
{
|
||||
UpdateViveHand(true, viveHand);
|
||||
UpdateViveHand(false, viveHand);
|
||||
}
|
||||
|
||||
#if UNITY_XR_HANDS
|
||||
HandTracking xrHand = OpenXRSettings.Instance.GetFeature<HandTracking>();
|
||||
if (xrHand)
|
||||
{
|
||||
if (handSubsystem == null || !handSubsystem.running)
|
||||
{
|
||||
if (handSubsystem != null && !handSubsystem.running)
|
||||
{
|
||||
handSubsystem.updatedHands -= OnUpdatedHands;
|
||||
handSubsystem = null;
|
||||
}
|
||||
|
||||
var handSubsystems = new List<XRHandSubsystem>();
|
||||
SubsystemManager.GetSubsystems(handSubsystems);
|
||||
for (var i = 0; i < handSubsystems.Count; ++i)
|
||||
{
|
||||
var xrHnad = handSubsystems[i];
|
||||
if (xrHnad.running)
|
||||
{
|
||||
handSubsystem = xrHnad;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (handSubsystem != null && handSubsystem.running)
|
||||
{
|
||||
handSubsystem.updatedHands += OnUpdatedHands;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateViveHand(bool isLeft, ViveHandTracking viveHand)
|
||||
{
|
||||
bool isUpdated = viveHand.GetJointLocations(isLeft, out XrHandJointLocationEXT[] viveJoints);
|
||||
JointData[] joints = new JointData[viveJoints.Length];
|
||||
for (int i = 0; i < joints.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();
|
||||
joints[i] = new JointData(isValid, position, rotation);
|
||||
}
|
||||
if (isLeft)
|
||||
{
|
||||
leftHand.Update(joints);
|
||||
}
|
||||
else
|
||||
{
|
||||
rightHand.Update(joints);
|
||||
}
|
||||
}
|
||||
|
||||
#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 xrHnad, bool isUpdated)
|
||||
{
|
||||
JointData[] joints = new JointData[(int)HandJointType.Count];
|
||||
for (int i = 0; i < joints.Length; i++)
|
||||
{
|
||||
XRHandJointID jointId = JointTypeToXRId(i);
|
||||
XRHandJoint joint = (isLeft ? xrHnad.leftHand : xrHnad.rightHand).GetJoint(jointId);
|
||||
bool isValid = isUpdated && joint.trackingState.HasFlag(XRHandJointTrackingState.Pose);
|
||||
joint.TryGetPose(out Pose pose);
|
||||
joints[i] = new JointData(isValid, pose.position, pose.rotation);
|
||||
}
|
||||
if (isLeft)
|
||||
{
|
||||
leftHand.Update(joints);
|
||||
}
|
||||
else
|
||||
{
|
||||
rightHand.Update(joints);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a887cb158a37cf45b17458a4f27d7ee
|
||||
guid: 744a8b8476e9f394b87927173e7994b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Future.meta
Normal file
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Future.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3a84b60570d8b243984fbe7169ca44a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
390
com.htc.upm.vive.openxr/Runtime/Toolkits/Future/FutureTask.cs
Normal file
390
com.htc.upm.vive.openxr/Runtime/Toolkits/Future/FutureTask.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VIVE.OpenXR.Feature;
|
||||
using static VIVE.OpenXR.Feature.FutureWrapper;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits
|
||||
{
|
||||
/// <summary>
|
||||
/// FutureTask is not a C# Task. It is a wrapper for OpenXR Future.
|
||||
/// Each OpenXR Future may have its own FutureTask type because the result and the
|
||||
/// complete function are different.
|
||||
/// However the poll and complete are common. This class use c# Task to poll the future.
|
||||
/// You can <see cref="Cancel the future"/> if you do not want to wait for the result.
|
||||
/// However Cancel should be called before the Complete.
|
||||
/// When <see cref="IsPollCompleted"/> is true, call <see cref="Complete"/> to complete the future and get the result.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">You can customize the type depending on the complete's result.</typeparam>
|
||||
public class FutureTask<TResult> : IDisposable
|
||||
{
|
||||
private Task<(XrResult, XrFutureStateEXT)> pollTask;
|
||||
private Task autoCompleteTask;
|
||||
|
||||
Func<IntPtr, TResult> completeFunc;
|
||||
private CancellationTokenSource cts;
|
||||
private IntPtr future;
|
||||
bool autoComplete = false;
|
||||
private int pollIntervalMS = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Set poll inverval in milliseconds. The value will be clamped between 1 and 2000.
|
||||
/// </summary>
|
||||
public int PollIntervalMS {
|
||||
get => pollIntervalMS;
|
||||
set => pollIntervalMS = Math.Clamp(value, 1, 2000);
|
||||
}
|
||||
|
||||
public bool IsAutoComplete => autoComplete;
|
||||
|
||||
bool isCompleted = false;
|
||||
public bool IsCompleted => isCompleted;
|
||||
|
||||
public bool Debug { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The FutureTask is used to poll and complete the future.
|
||||
/// Once the FutureTask is create, poll will start running in the period of pollIntervalMS.
|
||||
/// if auto complete is set, the future will be do completed once user check IsPollCompleted and IsCompleted.
|
||||
/// I prefered to use non-autoComplete.
|
||||
/// If no auto complete, you can cancel the task and no need to free resouce.
|
||||
/// Once it completed, you need handle the result to avoid leakage.
|
||||
/// </summary>
|
||||
/// <param name="future"></param>
|
||||
/// <param name="completeFunc"></param>
|
||||
/// <param name="pollIntervalMS">Set poll inverval in milliseconds. The value will be clamped between 1 and 2000.</param>
|
||||
/// <param name="autoComplete">If true, do Complete when check IsPollCompleted and IsCompleted</param>
|
||||
public FutureTask(IntPtr future, Func<IntPtr, TResult> completeFunc, int pollIntervalMS = 10, bool autoComplete = false)
|
||||
{
|
||||
cts = new CancellationTokenSource();
|
||||
this.completeFunc = completeFunc;
|
||||
this.future = future;
|
||||
this.pollIntervalMS = Math.Clamp(pollIntervalMS, 1, 2000);
|
||||
|
||||
// User may get PollTask and run. So, we need to make sure the pollTask is created.
|
||||
pollTask = MakePollTask(this, cts.Token);
|
||||
|
||||
// will set autoComplete true in AutoComplete.
|
||||
this.autoComplete = false;
|
||||
if (autoComplete)
|
||||
AutoComplete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AutoComplete will complete the future once the poll task is ready and success.
|
||||
/// If you want to handle error, you should not use AutoComplete.
|
||||
/// </summary>
|
||||
public void AutoComplete()
|
||||
{
|
||||
if (autoComplete)
|
||||
return;
|
||||
autoComplete = true;
|
||||
autoCompleteTask = pollTask.ContinueWith(task =>
|
||||
{
|
||||
// If the task is cancelled or faulted, we do not need to complete the future.
|
||||
if (task.IsCanceled || task.IsFaulted)
|
||||
{
|
||||
isCompleted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var result = task.Result;
|
||||
|
||||
// Make sure call Complete only if poll task is ready and success.
|
||||
if (result.Item1 == XrResult.XR_SUCCESS)
|
||||
{
|
||||
if (result.Item2 == XrFutureStateEXT.Ready)
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
isCompleted = true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for create FromResult if you need return the result immediately.
|
||||
/// </summary>
|
||||
/// <param name="pollTask"></param>
|
||||
/// <param name="completeFunc"></param>
|
||||
FutureTask(Task<(XrResult, XrFutureStateEXT)> pollTask, Func<IntPtr, TResult> completeFunc)
|
||||
{
|
||||
this.pollTask = pollTask;
|
||||
this.completeFunc = completeFunc;
|
||||
this.future = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public Task<(XrResult, XrFutureStateEXT)> PollTask => pollTask;
|
||||
|
||||
/// <summary>
|
||||
/// If AutoComplete is set, the task will be created. Otherwise, it will be null.
|
||||
/// </summary>
|
||||
public Task AutoCompleteTask => autoCompleteTask;
|
||||
|
||||
public bool IsPollCompleted => pollTask.IsCompleted;
|
||||
|
||||
public XrResult PollResult => pollTask.Result.Item1;
|
||||
|
||||
public IntPtr Future => future;
|
||||
|
||||
/// <summary>
|
||||
/// Cancel the future. If the future is not completed yet, it will be cancelled. Otherwise, nothing will happen.
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
if (!isCompleted)
|
||||
{
|
||||
cts?.Cancel();
|
||||
FutureWrapper.Instance?.CancelFuture(future);
|
||||
}
|
||||
future = IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure do Complete after IsPollCompleted. If the future is not poll completed yet, throw exception.
|
||||
/// </summary>
|
||||
/// <returns>The result of the completeFunc.</returns>
|
||||
/// <exception cref="Exception">Thrown when the pollTask is not completed yet.</exception>
|
||||
public TResult Complete()
|
||||
{
|
||||
if (isCompleted)
|
||||
return result;
|
||||
if (pollTask.IsCompleted)
|
||||
{
|
||||
if (this.Debug)
|
||||
UnityEngine.Debug.Log("FutureTask is completed.");
|
||||
isCompleted = true;
|
||||
if (pollTask.Result.Item1 == XrResult.XR_SUCCESS)
|
||||
{
|
||||
result = completeFunc(future);
|
||||
isCompleted = true;
|
||||
return result;
|
||||
}
|
||||
if (this.Debug)
|
||||
UnityEngine.Debug.Log("FutureTask is completed with error. Check if pollTask result error.");
|
||||
return default;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("FutureTask is not completed yet.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait until poll task is completed. If the task is not completed, it will block the thread.
|
||||
/// If AutoComplete is set, wait until the complete task is completed.
|
||||
/// </summary>
|
||||
public void Wait()
|
||||
{
|
||||
pollTask.Wait();
|
||||
if (autoComplete)
|
||||
autoCompleteTask.Wait();
|
||||
}
|
||||
|
||||
TResult result;
|
||||
private bool disposedValue;
|
||||
|
||||
/// <summary>
|
||||
/// This Result did not block the thread. If not completed, it will return undefined value. Make sure you call it when Complete is done.
|
||||
/// </summary>
|
||||
public TResult Result => result;
|
||||
|
||||
public static FutureTask<TResult> FromResult(TResult result)
|
||||
{
|
||||
return new FutureTask<TResult>(Task.FromResult((XrResult.XR_SUCCESS, XrFutureStateEXT.Ready)), (future) => result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poll until the future is ready. Caceled if the cts is cancelled. But the future will not be cancelled.
|
||||
/// </summary>
|
||||
/// <param name="futureTask"></param>
|
||||
/// <param name="pollIntervalMS"></param>
|
||||
/// <param name="cts"></param>
|
||||
/// <returns></returns>
|
||||
static async Task<(XrResult, XrFutureStateEXT)> MakePollTask(FutureTask<TResult> futureTask, CancellationToken ct)
|
||||
{
|
||||
XrFuturePollInfoEXT pollInfo = new XrFuturePollInfoEXT()
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_FUTURE_POLL_INFO_EXT,
|
||||
next = IntPtr.Zero,
|
||||
future = futureTask.Future
|
||||
};
|
||||
do
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
XrResult ret = FutureWrapper.Instance.PollFuture(ref pollInfo, out FutureWrapper.XrFuturePollResultEXT pollResult);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
{
|
||||
if (pollResult.state == XrFutureStateEXT.Ready)
|
||||
{
|
||||
if (futureTask.Debug)
|
||||
UnityEngine.Debug.Log("Future is ready.");
|
||||
return (XrResult.XR_SUCCESS, pollResult.state);
|
||||
}
|
||||
else if (pollResult.state == XrFutureStateEXT.Pending)
|
||||
{
|
||||
if (futureTask.Debug)
|
||||
UnityEngine.Debug.Log("Wait for future.");
|
||||
await Task.Delay(futureTask.pollIntervalMS);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return (ret, XrFutureStateEXT.None);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
pollTask?.Dispose();
|
||||
pollTask = null;
|
||||
autoCompleteTask?.Dispose();
|
||||
autoCompleteTask = null;
|
||||
cts?.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
|
||||
if (future != IntPtr.Zero && !isCompleted)
|
||||
FutureWrapper.Instance?.CancelFuture(future);
|
||||
future = IntPtr.Zero;
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Help to manage the future task. Tasks are name less. In order to manager tasks,
|
||||
/// additonal information are required. And this class is used to store those information.
|
||||
/// Helps to retrive the task by the identity, or retrive the identity by the task.
|
||||
/// </summary>
|
||||
/// <typeparam name="Identity">What the task work for. How to identify this task.</typeparam>
|
||||
/// <typeparam name="Output">The task's output type, for example, XrResult or Tuple.</typeparam>
|
||||
public class FutureTaskManager<Identity, TResult> : IDisposable
|
||||
{
|
||||
readonly List<(Identity, FutureTask<TResult>)> tasks = new List<(Identity, FutureTask<TResult>)>();
|
||||
private bool disposedValue;
|
||||
|
||||
public FutureTaskManager() { }
|
||||
|
||||
public FutureTask<TResult> GetTask(Identity identity)
|
||||
{
|
||||
return tasks.FirstOrDefault(x => x.Item1.Equals(identity)).Item2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a task to the manager.
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <param name="task"></param>
|
||||
public void AddTask(Identity identity, FutureTask<TResult> task)
|
||||
{
|
||||
tasks.Add((identity, task));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove keeped task and cancel it If task is not completed.
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
public void RemoveTask(Identity identity)
|
||||
{
|
||||
var task = tasks.FirstOrDefault(x => x.Item1.Equals(identity));
|
||||
if (task.Item2 != null)
|
||||
{
|
||||
task.Item2.Cancel();
|
||||
task.Item2.Dispose();
|
||||
}
|
||||
tasks.Remove(task);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove keeped task and cancel it If task is not completed.
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
public void RemoveTask(FutureTask<TResult> task)
|
||||
{
|
||||
var t = tasks.FirstOrDefault(x => x.Item2 == task);
|
||||
if (t.Item2 != null)
|
||||
{
|
||||
t.Item2.Cancel();
|
||||
t.Item2.Dispose();
|
||||
}
|
||||
tasks.Remove(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all tasks's list.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<(Identity, FutureTask<TResult>)> GetTasks()
|
||||
{
|
||||
return tasks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if has any task.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return tasks.Count == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all tasks and cancel them. If tasks are auto completed, make sure their results handled.
|
||||
/// Otherwise, the resource will be leaked.
|
||||
/// </summary>
|
||||
/// <param name="cancelTask"></param>
|
||||
public void Clear(bool cancelTask = true)
|
||||
{
|
||||
if (cancelTask)
|
||||
{
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
if (task.Item2 != null)
|
||||
{
|
||||
task.Item2.Cancel();
|
||||
task.Item2.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks.Clear();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,783 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using System.Threading.Tasks;
|
||||
using VIVE.OpenXR;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#if UNITY_STANDALONE
|
||||
private static Dictionary<XrPassthroughHTC, XrCompositionLayerPassthroughHTC> passthrough2Layer = new Dictionary<XrPassthroughHTC, XrCompositionLayerPassthroughHTC>();
|
||||
private static Dictionary<XrPassthroughHTC, IntPtr> passthrough2LayerPtr = new Dictionary<XrPassthroughHTC, IntPtr>();
|
||||
private static Dictionary<XrPassthroughHTC, bool> passthrough2IsUnderLay= new Dictionary<XrPassthroughHTC, bool>();
|
||||
private static Dictionary<XrPassthroughHTC, XrPassthroughMeshTransformInfoHTC> passthrough2meshTransform = new Dictionary<XrPassthroughHTC, XrPassthroughMeshTransformInfoHTC>();
|
||||
private static Dictionary<XrPassthroughHTC, IntPtr> passthrough2meshTransformInfoPtr = new Dictionary<XrPassthroughHTC, IntPtr>();
|
||||
#endif
|
||||
|
||||
#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,
|
||||
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.
|
||||
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PLANAR_HTC
|
||||
);
|
||||
|
||||
#if UNITY_ANDROID
|
||||
res = passthroughFeature.CreatePassthroughHTC(createInfo, out passthrough, layerType, compositionDepth, onDestroyPassthroughSessionHandler);
|
||||
DEBUG("CreatePlanarPassthrough() CreatePassthroughHTC result: " + res + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
#if UNITY_STANDALONE
|
||||
res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
|
||||
if(res == XrResult.XR_SUCCESS)
|
||||
{
|
||||
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
|
||||
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
|
||||
in_next: IntPtr.Zero,
|
||||
in_alpha: alpha);
|
||||
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
|
||||
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
|
||||
in_next: IntPtr.Zero,
|
||||
in_layerFlags: (UInt64)XrCompositionLayerFlagBits.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
|
||||
in_space: 0,
|
||||
in_passthrough: passthrough,
|
||||
in_color: passthroughColor);
|
||||
passthrough2Layer.Add(passthrough, compositionLayerPassthrough);
|
||||
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
|
||||
passthrough2LayerPtr.Add(passthrough, layerPtr);
|
||||
if (layerType == CompositionLayer.LayerType.Underlay)
|
||||
passthrough2IsUnderLay.Add(passthrough, true);
|
||||
if (layerType == CompositionLayer.LayerType.Overlay)
|
||||
passthrough2IsUnderLay.Add(passthrough, false);
|
||||
}
|
||||
#endif
|
||||
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,
|
||||
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.
|
||||
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PROJECTED_HTC
|
||||
);
|
||||
|
||||
#if UNITY_STANDALONE
|
||||
res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
|
||||
if (res == XrResult.XR_SUCCESS)
|
||||
{
|
||||
XrPassthroughMeshTransformInfoHTC PassthroughMeshTransformInfo = new XrPassthroughMeshTransformInfoHTC(
|
||||
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC,
|
||||
in_next: IntPtr.Zero,
|
||||
in_vertexCount: 0,
|
||||
in_vertices: new XrVector3f[0],
|
||||
in_indexCount: 0,
|
||||
in_indices: new UInt32[0],
|
||||
in_baseSpace: XR_HTC_passthrough.Interop.GetTrackingSpace(),
|
||||
in_time: XR_HTC_passthrough.Interop.GetFrameState().predictedDisplayTime,
|
||||
in_pose: new XrPosef(),
|
||||
in_scale: new XrVector3f()
|
||||
);
|
||||
IntPtr meshTransformInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrPassthroughMeshTransformInfoHTC)));
|
||||
Marshal.StructureToPtr(PassthroughMeshTransformInfo, meshTransformInfoPtr, false);
|
||||
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
|
||||
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
|
||||
in_next: IntPtr.Zero,
|
||||
in_alpha: alpha);
|
||||
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
|
||||
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
|
||||
in_next: meshTransformInfoPtr,
|
||||
in_layerFlags: (UInt64)XrCompositionLayerFlagBits.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
|
||||
in_space: 0,
|
||||
in_passthrough: passthrough,
|
||||
in_color: passthroughColor);
|
||||
passthrough2meshTransform.Add(passthrough, PassthroughMeshTransformInfo);
|
||||
passthrough2meshTransformInfoPtr.Add(passthrough, meshTransformInfoPtr);
|
||||
passthrough2Layer.Add(passthrough, compositionLayerPassthrough);
|
||||
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
|
||||
passthrough2LayerPtr.Add(passthrough, layerPtr);
|
||||
if (layerType == CompositionLayer.LayerType.Underlay)
|
||||
passthrough2IsUnderLay.Add(passthrough, true);
|
||||
if (layerType == CompositionLayer.LayerType.Overlay)
|
||||
passthrough2IsUnderLay.Add(passthrough, false);
|
||||
}
|
||||
#endif
|
||||
#if UNITY_ANDROID
|
||||
res = passthroughFeature.CreatePassthroughHTC(createInfo, out passthrough, layerType, compositionDepth, onDestroyPassthroughSessionHandler);
|
||||
DEBUG("CreateProjectedPassthrough() CreatePassthroughHTC result: " + res + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
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,
|
||||
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.
|
||||
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PROJECTED_HTC
|
||||
);
|
||||
|
||||
#if UNITY_STANDALONE
|
||||
res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
|
||||
if (res == XrResult.XR_SUCCESS)
|
||||
{
|
||||
XrPassthroughMeshTransformInfoHTC PassthroughMeshTransformInfo = new XrPassthroughMeshTransformInfoHTC(
|
||||
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC,
|
||||
in_next: IntPtr.Zero,
|
||||
in_vertexCount: 0,
|
||||
in_vertices: new XrVector3f[0],
|
||||
in_indexCount: 0,
|
||||
in_indices: new UInt32[0],
|
||||
in_baseSpace: XR_HTC_passthrough.Interop.GetTrackingSpace(),
|
||||
in_time: XR_HTC_passthrough.Interop.GetFrameState().predictedDisplayTime,
|
||||
in_pose: new XrPosef(),
|
||||
in_scale: new XrVector3f()
|
||||
);
|
||||
IntPtr meshTransformInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrPassthroughMeshTransformInfoHTC)));
|
||||
Marshal.StructureToPtr(PassthroughMeshTransformInfo, meshTransformInfoPtr, false);
|
||||
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
|
||||
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
|
||||
in_next: IntPtr.Zero,
|
||||
in_alpha: alpha);
|
||||
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
|
||||
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
|
||||
in_next: meshTransformInfoPtr,
|
||||
in_layerFlags: (UInt64)XrCompositionLayerFlagBits.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
|
||||
in_space: 0,
|
||||
in_passthrough: passthrough,
|
||||
in_color: passthroughColor);
|
||||
passthrough2meshTransform.Add(passthrough, PassthroughMeshTransformInfo);
|
||||
passthrough2meshTransformInfoPtr.Add(passthrough, meshTransformInfoPtr);
|
||||
passthrough2Layer.Add(passthrough, compositionLayerPassthrough);
|
||||
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
|
||||
passthrough2LayerPtr.Add(passthrough, layerPtr);
|
||||
if (layerType == CompositionLayer.LayerType.Underlay)
|
||||
passthrough2IsUnderLay.Add(passthrough, true);
|
||||
if (layerType == CompositionLayer.LayerType.Overlay)
|
||||
passthrough2IsUnderLay.Add(passthrough, false);
|
||||
}
|
||||
#endif
|
||||
#if UNITY_ANDROID
|
||||
res = passthroughFeature.CreatePassthroughHTC(createInfo, out passthrough, layerType, onDestroyPassthroughSessionHandler);
|
||||
DEBUG("CreateProjectedPassthrough() CreatePassthroughHTC result: " + res + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
if (res == XrResult.XR_SUCCESS)
|
||||
{
|
||||
SetPassthroughAlpha(passthrough, alpha);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#if UNITY_STANDALONE
|
||||
private static async void SubmitLayer()
|
||||
{
|
||||
await Task.Run(() => {
|
||||
int layerListCount = 0;
|
||||
while(layerListCount == 0)
|
||||
{
|
||||
System.Threading.Thread.Sleep(1);
|
||||
XR_HTC_passthrough.Interop.GetOriginEndFrameLayerList(out List<IntPtr> layerList);//GetOriginEndFrameLayers
|
||||
layerListCount = layerList.Count;
|
||||
foreach (var passthrough in passthrough2IsUnderLay.Keys)
|
||||
{
|
||||
//Get and submit layer list
|
||||
if (layerListCount != 0)
|
||||
{
|
||||
Marshal.StructureToPtr(passthrough2Layer[passthrough], passthrough2LayerPtr[passthrough], false);
|
||||
if (passthrough2IsUnderLay[passthrough])
|
||||
layerList.Insert(0, passthrough2LayerPtr[passthrough]);
|
||||
else
|
||||
layerList.Insert(1, passthrough2LayerPtr[passthrough]);
|
||||
}
|
||||
}
|
||||
if(layerListCount != 0)
|
||||
XR_HTC_passthrough.Interop.SubmitLayers(layerList);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <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 UNITY_STANDALONE
|
||||
XrPassthroughHTC pt = passthrough2Layer[passthrough].passthrough;
|
||||
XR_HTC_passthrough.xrDestroyPassthroughHTC(pt);
|
||||
passthrough2IsUnderLay.Remove(passthrough);
|
||||
SubmitLayer();
|
||||
passthrough2Layer.Remove(pt);
|
||||
if(passthrough2LayerPtr.ContainsKey(passthrough)) Marshal.FreeHGlobal(passthrough2LayerPtr[passthrough]);
|
||||
passthrough2LayerPtr.Remove(passthrough);
|
||||
if(passthrough2meshTransformInfoPtr.ContainsKey(passthrough)) Marshal.FreeHGlobal(passthrough2meshTransformInfoPtr[passthrough]);
|
||||
passthrough2meshTransformInfoPtr.Remove(passthrough);
|
||||
passthrough2meshTransform.Remove(passthrough);
|
||||
|
||||
res = XrResult.XR_SUCCESS;
|
||||
#elif UNITY_ANDROID
|
||||
res = passthroughFeature.DestroyPassthroughHTC(passthrough);
|
||||
DEBUG("DestroyPassthrough() DestroyPassthroughHTC result: " + res + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
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 UNITY_ANDROID
|
||||
if (autoClamp)
|
||||
{
|
||||
ret = passthroughFeature.SetAlpha(passthrough, Mathf.Clamp01(alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alpha < 0f || alpha > 1f)
|
||||
{
|
||||
ERROR("SetPassthroughAlpha: Alpha out of range");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = passthroughFeature.SetAlpha(passthrough, alpha);
|
||||
}
|
||||
DEBUG("SetPassthroughAlpha() SetAlpha result: " + ret + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
|
||||
#if UNITY_STANDALONE
|
||||
if (passthrough2Layer.ContainsKey(passthrough))
|
||||
{
|
||||
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthrough];
|
||||
layer.color.alpha = alpha;
|
||||
passthrough2Layer[passthrough] = layer;
|
||||
SubmitLayer();
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
||||
XrVector3f[] vertexBufferXrVector = new XrVector3f[vertexBuffer.Length];
|
||||
|
||||
for (int i = 0; i < vertexBuffer.Length; i++)
|
||||
{
|
||||
vertexBufferXrVector[i] = OpenXRHelper.ToOpenXRVector(vertexBuffer[i], convertFromUnityToOpenXR);
|
||||
}
|
||||
|
||||
uint[] indexBufferUint = new uint[indexBuffer.Length];
|
||||
|
||||
for (int i = 0; i < indexBuffer.Length; i++)
|
||||
{
|
||||
indexBufferUint[i] = (uint)indexBuffer[i];
|
||||
}
|
||||
|
||||
#if UNITY_STANDALONE
|
||||
if (passthrough2meshTransformInfoPtr.ContainsKey(passthrough))
|
||||
{
|
||||
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthrough];
|
||||
MeshTransformInfo.vertexCount = (uint)vertexBuffer.Length;
|
||||
MeshTransformInfo.vertices = vertexBufferXrVector;
|
||||
MeshTransformInfo.indexCount = (uint)indexBuffer.Length;
|
||||
MeshTransformInfo.indices = indexBufferUint;
|
||||
passthrough2meshTransform[passthrough] = MeshTransformInfo;
|
||||
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthrough], false);
|
||||
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthrough];
|
||||
layer.next = passthrough2meshTransformInfoPtr[passthrough];
|
||||
passthrough2Layer[passthrough] = layer;
|
||||
SubmitLayer();
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
#endif
|
||||
//Note: Ignore Clock-Wise definition of index buffer for now as passthrough extension does not have back-face culling
|
||||
#if UNITY_ANDROID
|
||||
ret = passthroughFeature.SetMesh(passthrough, (uint)vertexBuffer.Length, vertexBufferXrVector, (uint)indexBuffer.Length, indexBufferUint); ;
|
||||
DEBUG("SetProjectedPassthroughMesh() SetMesh result: " + ret + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <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 UNITY_STANDALONE
|
||||
if (passthrough2meshTransformInfoPtr.ContainsKey(passthrough))
|
||||
{
|
||||
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthrough];
|
||||
MeshTransformInfo.pose = meshXrPose;
|
||||
MeshTransformInfo.scale = meshXrScale;
|
||||
passthrough2meshTransform[passthrough] = MeshTransformInfo;
|
||||
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthrough], false);
|
||||
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthrough];
|
||||
layer.next = passthrough2meshTransformInfoPtr[passthrough];
|
||||
passthrough2Layer[passthrough] = layer;
|
||||
SubmitLayer();
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
#endif
|
||||
|
||||
#if UNITY_ANDROID
|
||||
ret = passthroughFeature.SetMeshTransform(passthrough, passthroughFeature.GetXrSpaceFromSpaceType(spaceType), meshXrPose, meshXrScale);
|
||||
DEBUG("SetProjectedPassthroughMeshTransform() SetMeshTransform result: " + ret + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <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 UNITY_STANDALONE
|
||||
if (passthrough2IsUnderLay.ContainsKey(passthrough))
|
||||
{
|
||||
passthrough2IsUnderLay[passthrough] = layerType == CompositionLayer.LayerType.Underlay ? true : false;
|
||||
SubmitLayer();
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
#endif
|
||||
|
||||
#if UNITY_ANDROID
|
||||
ret = passthroughFeature.SetLayerType(passthrough, layerType, compositionDepth);
|
||||
DEBUG("SetPassthroughLayerType() SetLayerType result: " + ret + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <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 UNITY_STANDALONE
|
||||
if (passthrough2meshTransformInfoPtr.ContainsKey(passthrough))
|
||||
{
|
||||
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthrough];
|
||||
MeshTransformInfo.baseSpace = passthroughFeature.GetXrSpaceFromSpaceType(spaceType);
|
||||
passthrough2meshTransform[passthrough] = MeshTransformInfo;
|
||||
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthrough], false);
|
||||
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthrough];
|
||||
layer.next = passthrough2meshTransformInfoPtr[passthrough];
|
||||
passthrough2Layer[passthrough] = layer;
|
||||
SubmitLayer();
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
#endif
|
||||
|
||||
#if UNITY_ANDROID
|
||||
ret = passthroughFeature.SetMeshTransformSpace(passthrough, passthroughFeature.GetXrSpaceFromSpaceType(spaceType));
|
||||
DEBUG("SetProjectedPassthroughSpaceType() SetMeshTransformSpace result: " + ret + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <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 UNITY_STANDALONE
|
||||
if (passthrough2meshTransformInfoPtr.ContainsKey(passthrough))
|
||||
{
|
||||
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthrough];
|
||||
XrPosef meshXrPose = MeshTransformInfo.pose;
|
||||
meshXrPose.position = OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR); ;
|
||||
MeshTransformInfo.pose = meshXrPose;
|
||||
passthrough2meshTransform[passthrough] = MeshTransformInfo;
|
||||
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthrough], false);
|
||||
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthrough];
|
||||
layer.next = passthrough2meshTransformInfoPtr[passthrough];
|
||||
passthrough2Layer[passthrough] = layer;
|
||||
SubmitLayer();
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
#endif
|
||||
|
||||
#if UNITY_ANDROID
|
||||
ret = passthroughFeature.SetMeshTransformPosition(passthrough, OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR));
|
||||
DEBUG("SetProjectedPassthroughMeshPosition() SetMeshTransformPosition result: " + ret + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <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 UNITY_STANDALONE
|
||||
if (passthrough2meshTransformInfoPtr.ContainsKey(passthrough))
|
||||
{
|
||||
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthrough];
|
||||
XrPosef meshXrPose = MeshTransformInfo.pose;
|
||||
meshXrPose.orientation = OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR);
|
||||
MeshTransformInfo.pose = meshXrPose;
|
||||
passthrough2meshTransform[passthrough] = MeshTransformInfo;
|
||||
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthrough], false);
|
||||
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthrough];
|
||||
layer.next = passthrough2meshTransformInfoPtr[passthrough];
|
||||
passthrough2Layer[passthrough] = layer;
|
||||
SubmitLayer();
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
#endif
|
||||
|
||||
#if UNITY_ANDROID
|
||||
ret = passthroughFeature.SetMeshTransformOrientation(passthrough, OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR));
|
||||
DEBUG("SetProjectedPassthroughMeshOrientation() SetMeshTransformOrientation result: " + ret + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <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 UNITY_STANDALONE
|
||||
if (passthrough2meshTransformInfoPtr.ContainsKey(passthrough))
|
||||
{
|
||||
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthrough];
|
||||
MeshTransformInfo.scale = OpenXRHelper.ToOpenXRVector(meshScale, false);
|
||||
passthrough2meshTransform[passthrough] = MeshTransformInfo;
|
||||
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthrough], false);
|
||||
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthrough];
|
||||
layer.next = passthrough2meshTransformInfoPtr[passthrough];
|
||||
passthrough2Layer[passthrough] = layer;
|
||||
SubmitLayer();
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
#endif
|
||||
|
||||
#if UNITY_ANDROID
|
||||
ret = passthroughFeature.SetMeshTransformScale(passthrough, OpenXRHelper.ToOpenXRVector(meshScale, false));
|
||||
DEBUG("SetProjectedPassthroughScale() SetMeshTransformScale result: " + ret + ", passthrough: " + passthrough);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <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:
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VIVE.OpenXR
|
||||
{
|
||||
public class MemoryTools
|
||||
{
|
||||
public static IntPtr ToIntPtr<T>(T[] array) where T : Enum
|
||||
{
|
||||
int size = Marshal.SizeOf(typeof(T)) * array.Length;
|
||||
IntPtr ptr = Marshal.AllocHGlobal(size);
|
||||
int[] intArray = new int[array.Length];
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
intArray[i] = (int)(object)array[i];
|
||||
Marshal.Copy(intArray, 0, ptr, array.Length);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Make the same size raw buffer from input array.
|
||||
public static IntPtr MakeRawMemory<T>(T[] refArray)
|
||||
{
|
||||
int size = Marshal.SizeOf(typeof(T)) * refArray.Length;
|
||||
return Marshal.AllocHGlobal(size);
|
||||
}
|
||||
|
||||
// Make the same size raw buffer from input array.
|
||||
public static void CopyFromRawMemory<T>(T[] array, IntPtr raw)
|
||||
{
|
||||
int step = Marshal.SizeOf(typeof(T));
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
array[i] = Marshal.PtrToStructure<T>(IntPtr.Add(raw, i * step));
|
||||
}
|
||||
}
|
||||
|
||||
// Make the same size raw buffer from input array. Make sure the raw has enough size.
|
||||
public static void CopyToRawMemory<T>(IntPtr raw, T[] array)
|
||||
{
|
||||
int step = Marshal.SizeOf(typeof(T));
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
Marshal.StructureToPtr<T>(array[i], IntPtr.Add(raw, i * step), false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReleaseRawMemory(IntPtr ptr)
|
||||
{
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Raycast.meta
Normal file
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Raycast.meta
Normal file
@@ -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:
|
||||
@@ -5,7 +5,7 @@ using UnityEditor;
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
[CustomEditor(typeof(HandMeshManager))]
|
||||
public class HandMeshManagerEditor : Editor
|
||||
public class HandMeshManagerEditor : UnityEditor.Editor
|
||||
{
|
||||
private HandMeshManager m_HandMesh;
|
||||
private SerializedProperty m_Handedness, m_EnableCollider, m_HandJoints;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,9 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
|
||||
[SerializeField]
|
||||
private Rigidbody m_Rigidbody = null;
|
||||
public new Rigidbody rigidbody => m_Rigidbody;
|
||||
#pragma warning disable
|
||||
public Rigidbody rigidbody => m_Rigidbody;
|
||||
#pragma warning enable
|
||||
|
||||
[SerializeField]
|
||||
private List<GrabPose> m_GrabPoses = new List<GrabPose>();
|
||||
@@ -82,15 +84,17 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
private bool m_ShowAllIndicator = false;
|
||||
private List<Collider> allColliders = new List<Collider>();
|
||||
private HandGrabInteractor closestGrabber = null;
|
||||
private OnBeginGrabbed beginGrabbed;
|
||||
private OnEndGrabbed endGrabbed;
|
||||
|
||||
[SerializeField]
|
||||
private IOneHandContraintMovement m_OneHandContraintMovement;
|
||||
public bool isContraint => m_OneHandContraintMovement != null;
|
||||
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()
|
||||
@@ -124,7 +128,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
|
||||
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
|
||||
UpdateBestGrabPose(handGrabber.isLeft, new Pose(wristPos, wristRot));
|
||||
beginGrabbed?.Invoke(this);
|
||||
m_OnBeginGrabbed?.Invoke(this);
|
||||
DEBUG($"{transform.name} is grabbed by {handGrabber.name}");
|
||||
}
|
||||
@@ -132,7 +135,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
m_Grabber = null;
|
||||
m_BestGrabPose = GrabPose.Identity;
|
||||
endGrabbed?.Invoke(this);
|
||||
m_OnEndGrabbed?.Invoke(this);
|
||||
DEBUG($"{transform.name} is released.");
|
||||
}
|
||||
@@ -185,46 +187,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
return distacne > grabDistance ? 0 : 1 - (distacne / grabDistance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a listener for the event triggered when the grabbable object is grabbed.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when the grabbable object is grabbed.</param>
|
||||
[Obsolete("Please use onBeginGrabbed instead.")]
|
||||
public void AddBeginGrabbedListener(OnBeginGrabbed handler)
|
||||
{
|
||||
beginGrabbed += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a listener for the event triggered when the grabbable object is grabbed.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||
[Obsolete("Please use onBeginGrabbed instead.")]
|
||||
public void RemoveBeginGrabbedListener(OnBeginGrabbed handler)
|
||||
{
|
||||
beginGrabbed -= handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a listener for the event triggered when the grabbable object is released.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when the grabbable object is released.</param>
|
||||
[Obsolete("Please use onEndGrabbed instead.")]
|
||||
public void AddEndGrabbedListener(OnEndGrabbed handler)
|
||||
{
|
||||
endGrabbed += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a listener for the event triggered when the grabbable object is released.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||
[Obsolete("Please use onEndGrabbed instead.")]
|
||||
public void RemoveEndGrabbedListener(OnEndGrabbed handler)
|
||||
{
|
||||
endGrabbed -= handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the position and rotation of the self with the pose of the hand that is grabbing it.
|
||||
/// </summary>
|
||||
@@ -302,16 +264,19 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
Vector3 closestPoint = Vector3.zero;
|
||||
float shortDistance = float.MaxValue;
|
||||
foreach (var collider in allColliders)
|
||||
for (int i = 0; i < allColliders.Count; i++)
|
||||
{
|
||||
Collider collider = allColliders[i];
|
||||
Vector3 closePoint = collider.ClosestPointOnBounds(sourcePos);
|
||||
float distance = Vector3.Distance(sourcePos, closePoint);
|
||||
if (collider.bounds.Contains(closePoint))
|
||||
{
|
||||
Vector3 direction = (closePoint - sourcePos).normalized;
|
||||
RaycastHit[] hits = Physics.RaycastAll(sourcePos, direction, distance);
|
||||
foreach (var hit in hits)
|
||||
Vector3 direction = closePoint - sourcePos;
|
||||
direction.Normalize();
|
||||
int hitCount = Physics.RaycastNonAlloc(sourcePos, direction, hitResults, distance);
|
||||
for (int j = 0; j < hitCount; j++)
|
||||
{
|
||||
RaycastHit hit = hitResults[j];
|
||||
if (hit.collider == collider)
|
||||
{
|
||||
float hitDistance = Vector3.Distance(sourcePos, hit.point);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -34,8 +35,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
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
|
||||
|
||||
@@ -79,8 +78,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
private GrabState m_State = GrabState.None;
|
||||
private Pose wristPose = Pose.identity;
|
||||
private Vector3[] fingerTipPosition = new Vector3[(int)FingerId.Count];
|
||||
private OnBeginGrab beginGrabHandler;
|
||||
private OnEndGrab endGrabHandler;
|
||||
|
||||
#region MonoBehaviour
|
||||
private void Awake()
|
||||
@@ -155,46 +152,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a listener for the event triggered when the grabber begins grabbing.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when the grabber begins grabbing.</param>
|
||||
[Obsolete("Please use onBeginGrab instead.")]
|
||||
public void AddBeginGrabListener(OnBeginGrab handler)
|
||||
{
|
||||
beginGrabHandler += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a listener for the event triggered when the grabber begins grabbing.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||
[Obsolete("Please use onBeginGrab instead.")]
|
||||
public void RemoveBeginGrabListener(OnBeginGrab handler)
|
||||
{
|
||||
beginGrabHandler -= handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a listener for the event triggered when the grabber ends grabbing.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when the grabber ends grabbing.</param>
|
||||
[Obsolete("Please use onEndGrab instead.")]
|
||||
public void AddEndGrabListener(OnEndGrab handler)
|
||||
{
|
||||
endGrabHandler += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a listener for the event triggered when the grabber ends grabbing.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||
[Obsolete("Please use onEndGrab instead.")]
|
||||
public void RemoveEndGrabListener(OnEndGrab handler)
|
||||
{
|
||||
endGrabHandler -= handler;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -231,13 +188,32 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
grabbable = null;
|
||||
maxScore = 0f;
|
||||
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
||||
{
|
||||
interactable.ShowIndicator(false, this);
|
||||
|
||||
foreach (Vector3 tipPos in fingerTipPosition)
|
||||
Collider[] nearColliders = Physics.OverlapSphere(wristPose.position, 0.5f);
|
||||
List<HandGrabInteractable> nearHandGrabInteractables = new List<HandGrabInteractable>();
|
||||
for (int i = 0; i < nearColliders.Length; i++)
|
||||
{
|
||||
HandGrabInteractable interactable = nearColliders[i].GetComponentInParent<HandGrabInteractable>();
|
||||
if (interactable && !nearHandGrabInteractables.Contains(interactable))
|
||||
{
|
||||
float distanceScore = interactable.CalculateDistanceScore(tipPos, grabDistance);
|
||||
nearHandGrabInteractables.Add(interactable);
|
||||
continue;
|
||||
}
|
||||
interactable = nearColliders[i].GetComponentInChildren<HandGrabInteractable>();
|
||||
if (interactable && !nearHandGrabInteractables.Contains(interactable))
|
||||
{
|
||||
nearHandGrabInteractables.Add(interactable);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < nearHandGrabInteractables.Count; i++)
|
||||
{
|
||||
HandGrabInteractable interactable = nearHandGrabInteractables[i];
|
||||
interactable.ShowIndicator(false, this);
|
||||
for (int j = 0; j < fingerTipPosition.Length; j++)
|
||||
{
|
||||
float distanceScore = interactable.CalculateDistanceScore(fingerTipPosition[j], grabDistance);
|
||||
if (distanceScore > maxScore)
|
||||
{
|
||||
maxScore = distanceScore;
|
||||
@@ -280,7 +256,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
m_Grabbable = currentCandidate;
|
||||
m_Grabbable.SetGrabber(this);
|
||||
m_Grabbable.ShowIndicator(false, this);
|
||||
beginGrabHandler?.Invoke(this);
|
||||
onBeginGrab?.Invoke(this);
|
||||
|
||||
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand begins to grab the {m_Grabbable.name}");
|
||||
@@ -296,7 +271,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand ends to grab the {m_Grabbable.name}");
|
||||
|
||||
endGrabHandler?.Invoke(this);
|
||||
onEndGrab?.Invoke(this);
|
||||
m_Grabbable.SetGrabber(null);
|
||||
m_Grabbable = null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
public class OneGrabMoveConstraint : IOneHandContraintMovement
|
||||
{
|
||||
@@ -30,21 +30,28 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
[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;
|
||||
|
||||
|
||||
@@ -40,9 +40,12 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
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;
|
||||
private float totalRotationAngle = 0.0f;
|
||||
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)
|
||||
@@ -99,17 +102,17 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
float angleDelta = Vector3.Angle(previousVector, targetVector);
|
||||
angleDelta *= Vector3.Dot(Vector3.Cross(previousVector, targetVector), worldAxis) > 0.0f ? 1.0f : -1.0f;
|
||||
|
||||
float previousAngle = totalRotationAngle;
|
||||
totalRotationAngle += angleDelta;
|
||||
float previousAngle = m_TotalDegrees;
|
||||
m_TotalDegrees += angleDelta;
|
||||
if (m_CounterclockwiseAngle.enableConstraint)
|
||||
{
|
||||
totalRotationAngle = Mathf.Max(totalRotationAngle, -m_CounterclockwiseAngle.value);
|
||||
m_TotalDegrees = Mathf.Max(m_TotalDegrees, -m_CounterclockwiseAngle.value);
|
||||
}
|
||||
if (m_ClockwiseAngle.enableConstraint)
|
||||
{
|
||||
totalRotationAngle = Mathf.Min(totalRotationAngle, m_ClockwiseAngle.value);
|
||||
m_TotalDegrees = Mathf.Min(m_TotalDegrees, m_ClockwiseAngle.value);
|
||||
}
|
||||
angleDelta = totalRotationAngle - previousAngle;
|
||||
angleDelta = m_TotalDegrees - previousAngle;
|
||||
m_Constraint.RotateAround(m_Pivot.position, worldAxis, angleDelta);
|
||||
|
||||
previousHandPose = handPose;
|
||||
|
||||
@@ -13,11 +13,7 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
|
||||
#if UNITY_XR_HANDS
|
||||
using UnityEngine.XR.Hands;
|
||||
#endif
|
||||
using VIVE.OpenXR.Toolkits.Common;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
@@ -60,31 +56,15 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
/// <summary>
|
||||
/// This class is designed to update hand tracking data.
|
||||
/// </summary>
|
||||
#if UNITY_XR_HANDS
|
||||
public static class DataWrapper
|
||||
{
|
||||
private static XRHandSubsystem handSubsystem = null;
|
||||
private static List<XRHandSubsystem> s_XRHandSubsystems = new List<XRHandSubsystem>();
|
||||
|
||||
/// <summary>
|
||||
/// Validate whether the hand tracking is active.
|
||||
/// </summary>
|
||||
/// <returns>True if the hand tracking is active; otherwise, false.</returns>
|
||||
public static bool Validate()
|
||||
{
|
||||
if (handSubsystem == null || !handSubsystem.running)
|
||||
{
|
||||
SubsystemManager.GetSubsystems(s_XRHandSubsystems);
|
||||
for (int i = 0; i < s_XRHandSubsystems.Count; i++)
|
||||
{
|
||||
if (handSubsystem != null)
|
||||
{
|
||||
handSubsystem = null;
|
||||
}
|
||||
handSubsystem = s_XRHandSubsystems[i];
|
||||
}
|
||||
}
|
||||
return handSubsystem != null && handSubsystem.running;
|
||||
return VIVEInput.IsHandValidate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,11 +74,8 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
/// <returns>True if the hand tracking is successfully tracking; otherwise, false.</returns>
|
||||
public static bool IsHandTracked(bool isLeft)
|
||||
{
|
||||
if (handSubsystem != null)
|
||||
{
|
||||
return isLeft ? handSubsystem.leftHand.isTracked : handSubsystem.rightHand.isTracked;
|
||||
}
|
||||
return false;
|
||||
Common.Handedness handedness = isLeft ? Common.Handedness.Left : Common.Handedness.Right;
|
||||
return VIVEInput.IsHandTracked(handedness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -111,53 +88,17 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
/// <returns></returns>
|
||||
public static bool GetJointPose(JointType jointType, ref Vector3 position, ref Quaternion rotation, bool isLeft)
|
||||
{
|
||||
if (IsHandTracked(isLeft))
|
||||
Common.Handedness handedness = isLeft ? Common.Handedness.Left : Common.Handedness.Right;
|
||||
if (IsHandTracked(isLeft) &&
|
||||
VIVEInput.GetJointPose(handedness, (HandJointType)jointType, out Pose jointPose))
|
||||
{
|
||||
XRHand hand = isLeft ? handSubsystem.leftHand : handSubsystem.rightHand;
|
||||
XRHandJoint xrHandJoint = hand.GetJoint(ConvertToXRHandJointID(jointType));
|
||||
if (xrHandJoint.TryGetPose(out Pose pose))
|
||||
{
|
||||
position = pose.position;
|
||||
rotation = pose.rotation;
|
||||
return true;
|
||||
}
|
||||
position = jointPose.position;
|
||||
rotation = jointPose.rotation;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static XRHandJointID ConvertToXRHandJointID(JointType jointType)
|
||||
{
|
||||
int id = (int)jointType;
|
||||
switch (id)
|
||||
{
|
||||
case 0:
|
||||
return XRHandJointID.Palm;
|
||||
case 1:
|
||||
return XRHandJointID.Wrist;
|
||||
default:
|
||||
return (XRHandJointID)(id + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
public static class DataWrapper
|
||||
{
|
||||
public static bool Validate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsHandTracked(bool isLeft)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool GetJointPose(JointType jointType, ref Vector3 position, ref Quaternion rotation, bool isLeft)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The enum is designed to define the IDs of joints.
|
||||
@@ -242,28 +183,38 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
public Vector3 direction = Vector3.zero;
|
||||
public JointData[] joints = null;
|
||||
public JointData joint0 {
|
||||
get {
|
||||
public JointData joint0
|
||||
{
|
||||
get
|
||||
{
|
||||
return joints[(Int32)JointId.Joint0];
|
||||
}
|
||||
}
|
||||
public JointData joint1 {
|
||||
get {
|
||||
public JointData joint1
|
||||
{
|
||||
get
|
||||
{
|
||||
return joints[(Int32)JointId.Joint1];
|
||||
}
|
||||
}
|
||||
public JointData joint2 {
|
||||
get {
|
||||
public JointData joint2
|
||||
{
|
||||
get
|
||||
{
|
||||
return joints[(Int32)JointId.Joint2];
|
||||
}
|
||||
}
|
||||
public JointData joint3 {
|
||||
get {
|
||||
public JointData joint3
|
||||
{
|
||||
get
|
||||
{
|
||||
return joints[(Int32)JointId.Joint3];
|
||||
}
|
||||
}
|
||||
public JointData tip {
|
||||
get {
|
||||
public JointData tip
|
||||
{
|
||||
get
|
||||
{
|
||||
return joints[(Int32)JointId.Tip];
|
||||
}
|
||||
}
|
||||
@@ -692,28 +643,38 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
public JointData palm;
|
||||
public JointData wrist;
|
||||
public FingerData[] fingers = null; // size: FingerId.Count
|
||||
public FingerData thumb {
|
||||
get {
|
||||
public FingerData thumb
|
||||
{
|
||||
get
|
||||
{
|
||||
return fingers[(Int32)FingerId.Thumb];
|
||||
}
|
||||
}
|
||||
public FingerData index {
|
||||
get {
|
||||
public FingerData index
|
||||
{
|
||||
get
|
||||
{
|
||||
return fingers[(Int32)FingerId.Index];
|
||||
}
|
||||
}
|
||||
public FingerData middle {
|
||||
get {
|
||||
public FingerData middle
|
||||
{
|
||||
get
|
||||
{
|
||||
return fingers[(Int32)FingerId.Middle];
|
||||
}
|
||||
}
|
||||
public FingerData ring {
|
||||
get {
|
||||
public FingerData ring
|
||||
{
|
||||
get
|
||||
{
|
||||
return fingers[(Int32)FingerId.Ring];
|
||||
}
|
||||
}
|
||||
public FingerData pinky {
|
||||
get {
|
||||
public FingerData pinky
|
||||
{
|
||||
get
|
||||
{
|
||||
return fingers[(Int32)FingerId.Pinky];
|
||||
}
|
||||
}
|
||||
@@ -1026,13 +987,17 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
|
||||
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandGrabState.FingerPinchState ";
|
||||
StringBuilder m_sb = null;
|
||||
StringBuilder sb {
|
||||
get {
|
||||
StringBuilder sb
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_sb == null) { m_sb = new StringBuilder(); }
|
||||
return m_sb;
|
||||
}
|
||||
}
|
||||
void DEBUG(StringBuilder msg) { Debug.Log(msg); }
|
||||
void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||
bool printIntervalLog = false;
|
||||
int logFrame = 0;
|
||||
|
||||
private bool isLeft = false;
|
||||
private FingerData thumbData;
|
||||
@@ -1107,10 +1072,10 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
fingerData.joint2.position};
|
||||
|
||||
float distance = float.PositiveInfinity;
|
||||
foreach (var fingerJointPos in fingerPos)
|
||||
for (int i = 0; i < fingerPos.Length; i++)
|
||||
{
|
||||
distance = Mathf.Min(distance, CalculateShortestDistance(fingerJointPos, thumbTip, thumbJoint2));
|
||||
distance = Mathf.Min(distance, CalculateShortestDistance(fingerJointPos, thumbJoint2, thumbJoint1));
|
||||
distance = Mathf.Min(distance, CalculateShortestDistance(fingerPos[i], thumbTip, thumbJoint2));
|
||||
distance = Mathf.Min(distance, CalculateShortestDistance(fingerPos[i], thumbJoint2, thumbJoint1));
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
@@ -1169,12 +1134,14 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
m_isPinching = true;
|
||||
minDistance = distance;
|
||||
|
||||
sb.Clear().Append(LOG_TAG).Append(isLeft ? "Left " : "Right ").Append(m_finger.Name())
|
||||
.Append(" UpdateState() pinch strength: ").Append(m_pinchStrength)
|
||||
.Append(", pinch on threshold: ").Append(kPinchStrengthOnThreshold)
|
||||
.Append(", is pinching: ").Append(m_isPinching)
|
||||
.Append(", pinch distance: ").Append(minDistance);
|
||||
DEBUG(sb);
|
||||
if (printIntervalLog)
|
||||
{
|
||||
DEBUG($"{(isLeft ? "Left " : "Right ")} {finger.Name()}" +
|
||||
$" UpdateState() pinch strength: {m_pinchStrength}" +
|
||||
$", pinch on threshold: {kPinchStrengthOnThreshold}" +
|
||||
$", is pinching: {m_isPinching}" +
|
||||
$", pinch distance: {minDistance}");
|
||||
}
|
||||
|
||||
updated = true;
|
||||
}
|
||||
@@ -1187,12 +1154,14 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
m_isPinching = false;
|
||||
|
||||
sb.Clear().Append(LOG_TAG).Append(isLeft ? "Left " : "Right ").Append(m_finger.Name())
|
||||
.Append(" UpdateState() pinch strength: ").Append(m_pinchStrength)
|
||||
.Append(", pinch off threshold: ").Append(kPinchStrengthOffThreshold)
|
||||
.Append(", is pinching: ").Append(m_isPinching)
|
||||
.Append(", pinch distance: ").Append(minDistance);
|
||||
DEBUG(sb);
|
||||
if (printIntervalLog)
|
||||
{
|
||||
DEBUG($"{(isLeft ? "Left " : "Right ")} {finger.Name()}" +
|
||||
$" UpdateState() pinch strength: {m_pinchStrength}" +
|
||||
$", pinch off threshold: {kPinchStrengthOffThreshold}" +
|
||||
$", is pinching: {m_isPinching}" +
|
||||
$", pinch distance: {minDistance}");
|
||||
}
|
||||
|
||||
updated = true;
|
||||
}
|
||||
@@ -1207,6 +1176,9 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
/// <param name="finger">The FingerData of the finger.</param>
|
||||
public void Update(FingerData thumb, FingerData finger)
|
||||
{
|
||||
logFrame++;
|
||||
logFrame %= 300;
|
||||
printIntervalLog = logFrame == 0;
|
||||
if (!Validate()) { return; }
|
||||
|
||||
this.thumbData = thumb;
|
||||
@@ -1901,15 +1873,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
[Serializable]
|
||||
public class HandGrabbableEvent : UnityEvent<IGrabbable> { };
|
||||
|
||||
[Obsolete("Please use HandGrabberEvent instead.")]
|
||||
public delegate void OnBeginGrab(IGrabber grabber);
|
||||
[Obsolete("Please use HandGrabberEvent instead.")]
|
||||
public delegate void OnEndGrab(IGrabber grabber);
|
||||
[Obsolete("Please use HandGrabbableEvent instead.")]
|
||||
public delegate void OnBeginGrabbed(IGrabbable grabbable);
|
||||
[Obsolete("Please use HandGrabbableEvent instead.")]
|
||||
public delegate void OnEndGrabbed(IGrabbable grabbable);
|
||||
|
||||
/// <summary>
|
||||
/// Interface for objects capable of grabbing.
|
||||
/// </summary>
|
||||
@@ -1919,15 +1882,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
bool isGrabbing { get; }
|
||||
HandGrabberEvent onBeginGrab { get; }
|
||||
HandGrabberEvent onEndGrab { get; }
|
||||
|
||||
[Obsolete("Please use onBeginGrab instead.")]
|
||||
void AddBeginGrabListener(OnBeginGrab handler);
|
||||
[Obsolete("Please use onBeginGrab instead.")]
|
||||
void RemoveBeginGrabListener(OnBeginGrab handler);
|
||||
[Obsolete("Please use onEndGrab instead.")]
|
||||
void AddEndGrabListener(OnEndGrab handler);
|
||||
[Obsolete("Please use onEndGrab instead.")]
|
||||
void RemoveEndGrabListener(OnEndGrab handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1950,15 +1904,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
HandGrabbableEvent onBeginGrabbed { get; }
|
||||
HandGrabbableEvent onEndGrabbed { get; }
|
||||
void SetGrabber(IGrabber grabber);
|
||||
|
||||
[Obsolete("Please use onBeginGrabbed instead.")]
|
||||
void AddBeginGrabbedListener(OnBeginGrabbed handler);
|
||||
[Obsolete("Please use onBeginGrabbed instead.")]
|
||||
void RemoveBeginGrabbedListener(OnBeginGrabbed handler);
|
||||
[Obsolete("Please use onEndGrabbed instead.")]
|
||||
void AddEndGrabbedListener(OnEndGrabbed handler);
|
||||
[Obsolete("Please use onEndGrabbed instead.")]
|
||||
void RemoveEndGrabbedListener(OnEndGrabbed handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using VIVE.OpenXR.Toolkits.Common;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
@@ -50,6 +51,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
[SerializeField]
|
||||
private Transform[] m_HandJoints = new Transform[k_JointCount];
|
||||
|
||||
private SkinnedMeshRenderer skinnedMeshRenderer = null;
|
||||
private const int k_JointCount = (int)JointType.Count;
|
||||
private const int k_RootId = (int)JointType.Wrist;
|
||||
private bool updateRoot = false;
|
||||
@@ -77,6 +79,8 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
|
||||
MeshHandPose meshHandPose = transform.gameObject.AddComponent<MeshHandPose>();
|
||||
meshHandPose.SetHandMeshRenderer(this);
|
||||
|
||||
skinnedMeshRenderer = transform.GetComponentInChildren<SkinnedMeshRenderer>();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
@@ -95,9 +99,9 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
|
||||
private void Update()
|
||||
{
|
||||
HandData handData = CachedHand.Get(isLeft);
|
||||
EnableHandModel(handData.isTracked);
|
||||
if (!handData.isTracked) { return; }
|
||||
bool isTracked = VIVEInput.IsHandTracked(isLeft ? Common.Handedness.Left : Common.Handedness.Right);
|
||||
EnableHandModel(isTracked);
|
||||
if (!isTracked) { return; }
|
||||
|
||||
//if (m_UseRuntimeModel || (!m_UseRuntimeModel && m_UseScale))
|
||||
//{
|
||||
@@ -118,22 +122,23 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
}
|
||||
if (!updateRoot)
|
||||
{
|
||||
Vector3 rootPosition = Vector3.zero;
|
||||
Quaternion rootRotation = Quaternion.identity;
|
||||
handData.GetJointPosition((JointType)k_RootId, ref rootPosition);
|
||||
handData.GetJointRotation((JointType)k_RootId, ref rootRotation);
|
||||
|
||||
m_HandJoints[k_RootId].position = m_HandJoints[k_RootId].parent.position + rootPosition;
|
||||
m_HandJoints[k_RootId].rotation = m_HandJoints[k_RootId].parent.rotation * rootRotation;
|
||||
VIVEInput.GetJointPose(isLeft ? Common.Handedness.Left : Common.Handedness.Right, HandJointType.Wrist, out Pose jointPose);
|
||||
m_HandJoints[k_RootId].localPosition = jointPose.position;
|
||||
m_HandJoints[k_RootId].localRotation = jointPose.rotation;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_HandJoints.Length; i++)
|
||||
{
|
||||
if (m_HandJoints[i] == null || i == k_RootId) { continue; }
|
||||
|
||||
Quaternion jointRotation = Quaternion.identity;
|
||||
handData.GetJointRotation((JointType)i, ref jointRotation);
|
||||
m_HandJoints[i].rotation = m_HandJoints[k_RootId].parent.rotation * jointRotation;
|
||||
VIVEInput.GetJointPose(isLeft ? Common.Handedness.Left : Common.Handedness.Right, (HandJointType)i, out Pose jointPose);
|
||||
m_HandJoints[i].rotation = m_HandJoints[k_RootId].parent.rotation * jointPose.rotation;
|
||||
}
|
||||
|
||||
if (VIVERig.Instance)
|
||||
{
|
||||
m_HandJoints[k_RootId].rotation = VIVERig.Instance.transform.rotation * m_HandJoints[k_RootId].localRotation;
|
||||
m_HandJoints[k_RootId].position = VIVERig.Instance.transform.position + VIVERig.Instance.transform.rotation * m_HandJoints[k_RootId].localPosition;
|
||||
}
|
||||
|
||||
if (isGrabbing)
|
||||
@@ -343,6 +348,11 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
if (m_HandJoints[k_RootId].gameObject.activeSelf != enable)
|
||||
{
|
||||
m_HandJoints[k_RootId].gameObject.SetActive(enable);
|
||||
|
||||
if (skinnedMeshRenderer)
|
||||
{
|
||||
skinnedMeshRenderer.enabled = enable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,11 +83,25 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
if (!HandPoseMap.ContainsKey(poseType))
|
||||
{
|
||||
UnityEngine.Object[] handObjects = UnityEngine.Object.FindObjectsOfType(typeof(RealHandPose));
|
||||
for (int i = 0; i < handObjects.Length; i++)
|
||||
{
|
||||
UnityEngine.Object handObject = handObjects[i];
|
||||
if (handObject is RealHandPose realHand &&
|
||||
(realHand.isLeft ? poseType == HandPoseType.HAND_LEFT : poseType == HandPoseType.HAND_RIGHT))
|
||||
{
|
||||
realHand.SetType(poseType);
|
||||
RegisterHandPose(poseType, realHand);
|
||||
return realHand;
|
||||
}
|
||||
}
|
||||
|
||||
GameObject handPoseObject = new GameObject(poseName);
|
||||
RealHandPose realHandPose = handPoseObject.AddComponent<RealHandPose>();
|
||||
realHandPose.SetType(poseType);
|
||||
RegisterHandPose(poseType, realHandPose);
|
||||
return realHandPose;
|
||||
RealHandPose newRealHand = handPoseObject.AddComponent<RealHandPose>();
|
||||
newRealHand.SetType(poseType);
|
||||
RegisterHandPose(poseType, newRealHand);
|
||||
return newRealHand;
|
||||
|
||||
}
|
||||
return HandPoseMap[poseType];
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
[SerializeField]
|
||||
private Handedness m_Handedness;
|
||||
private bool isLeft => m_Handedness == Handedness.Left;
|
||||
public bool isLeft => m_Handedness == Handedness.Left;
|
||||
private bool keepUpdate = false;
|
||||
|
||||
protected override void OnEnable()
|
||||
@@ -66,8 +66,8 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
if (handData.GetJointPosition((JointType)i, ref position) && handData.GetJointRotation((JointType)i, ref rotation))
|
||||
{
|
||||
m_Position[i] = position;
|
||||
m_Rotation[i] = rotation;
|
||||
m_Position[i] = transform.position + transform.rotation * position;
|
||||
m_Rotation[i] = transform.rotation * rotation;
|
||||
m_LocalPosition[i] = position;
|
||||
m_LocalRotation[i] = rotation;
|
||||
}
|
||||
|
||||
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Spectator.meta
Normal file
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Spectator.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1695418a852690c43b0206d5f2b54abd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3be1cbe32bf01284f9038c49e248393a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67f99d00d315f5247a283247f1d1a046
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Android;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator.Helper
|
||||
{
|
||||
public static class AndroidProcessHelper
|
||||
{
|
||||
private static AndroidJavaObject _activity;
|
||||
|
||||
public static AndroidJavaObject Activity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_activity != null)
|
||||
{
|
||||
return _activity;
|
||||
}
|
||||
|
||||
var unityPlayer = new AndroidJavaClass(ANDROID_CLASS_UNITY_PLAYER);
|
||||
_activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
|
||||
|
||||
return _activity;
|
||||
}
|
||||
}
|
||||
|
||||
// Reference: https://stackoverflow.com/q/58728596/10467387
|
||||
public static IEnumerator RequestPermission(Dictionary<string, PermissionCallbacks> permissions)
|
||||
{
|
||||
var permissionGranted = Enumerable.Repeat(false, permissions.Count).ToList();
|
||||
var permissionAsked = Enumerable.Repeat(false, permissions.Count).ToList();
|
||||
var permissionAction = new List<Action>();
|
||||
|
||||
for (var i = 0; i < permissions.Count; i++)
|
||||
{
|
||||
int currentCount = i;
|
||||
(string permission, PermissionCallbacks permissionCallbacks) = permissions.ElementAt(currentCount);
|
||||
permissionAction.Add(() =>
|
||||
{
|
||||
permissionGranted[currentCount] = Permission.HasUserAuthorizedPermission(permission);
|
||||
if (permissionGranted[currentCount] || permissionAsked[currentCount])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Permission.RequestUserPermission(permission, permissionCallbacks);
|
||||
permissionAsked[currentCount] = true;
|
||||
});
|
||||
}
|
||||
|
||||
for (var i = 0; i < permissions.Count;)
|
||||
{
|
||||
permissionAction[i].Invoke();
|
||||
if (permissionAsked[i])
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
}
|
||||
|
||||
public const string ANDROID_CLASS_UNITY_PLAYER = "com.unity3d.player.UnityPlayer";
|
||||
public const string ANDROID_CLASS_MEDIA_STORE_IMAGE_MEDIA = "android.provider.MediaStore$Images$Media";
|
||||
public const string ANDROID_CLASS_GRAPHICS_BITMAP_FACTORY = "android.graphics.BitmapFactory";
|
||||
public const string ANDROID_CLASS_GRAPHICS_BITMAP_COMPRESS_FORMAT = "android.graphics.Bitmap$CompressFormat";
|
||||
public const string ANDROID_CLASS_OS_ENVIRONMENT = "android.os.Environment";
|
||||
public const string ANDROID_CLASS_OS_BUILD_VERSION = "android.os.Build$VERSION";
|
||||
public const string ANDROID_CLASS_CONTENT_INTENT = "android.content.Intent";
|
||||
public const string ANDROID_CLASS_CONTENT_VALUES = "android.content.ContentValues";
|
||||
public const string ANDROID_CLASS_NET_URI = "android.net.Uri";
|
||||
public const string JAVA_CLASS_IO_FILE = "java.io.File";
|
||||
public const string JAVA_CLASS_IO_OUTPUTSTREAM = "java.io.OutputStream";
|
||||
public const string JAVA_CLASS_IO_BYTEARRAYOUTPUTSTREAM = "java.io.ByteArrayOutputStream";
|
||||
public const string JAVA_CLASS_IO_BYTEARRAYINPUTSTREAM = "java.io.ByteArrayInputStream";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52cbc1ad9d288d348854b4e81d8aa57c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator.Helper
|
||||
{
|
||||
public static class EditorHelper
|
||||
{
|
||||
// Microsoft definition
|
||||
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.conventions.backingfieldconvention?view=efcore-7.0#definition
|
||||
private const string BackingFieldConventionPrefix = "<";
|
||||
private const string BackingFieldConventionEndString = "k__BackingField";
|
||||
|
||||
public static void ShowDefaultInspector(SerializedObject obj)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
obj.UpdateIfRequiredOrScript();
|
||||
|
||||
SerializedProperty iterator = obj.GetIterator();
|
||||
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath))
|
||||
{
|
||||
string originalLabelText = iterator.name;
|
||||
if (originalLabelText.EndsWith(BackingFieldConventionEndString))
|
||||
{
|
||||
string fixLabelText = Regex.Replace(
|
||||
originalLabelText.Substring(
|
||||
1,
|
||||
originalLabelText.Length - 1 - BackingFieldConventionPrefix.Length -
|
||||
BackingFieldConventionEndString.Length),
|
||||
"([a-z])([A-Z])",
|
||||
"$1 $2");
|
||||
|
||||
EditorGUILayout.PropertyField(
|
||||
property: iterator,
|
||||
label: new GUIContent(fixLabelText),
|
||||
includeChildren: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.PropertyField(iterator, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
|
||||
public static string PropertyName(string propertyName)
|
||||
{
|
||||
// Microsoft definition
|
||||
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.conventions.backingfieldconvention?view=efcore-7.0#definition
|
||||
return $"<{propertyName}>k__BackingField";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2fb164a4bcc8f04e9337299f4c030d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator.Helper
|
||||
{
|
||||
public static class IOProcessHelper
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
/// <summary>
|
||||
/// Get the path of pictures folder in external storage in Android.
|
||||
/// </summary>
|
||||
/// <returns>Return Pictures folder directory if get successfully, otherwise return empty string</returns>
|
||||
public static string GetAndroidExternalStoragePicturesDirectory()
|
||||
{
|
||||
Debug.Log("GetAndroidExternalStoragePicturesDirectory called");
|
||||
|
||||
string path = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
// Init the class in java code
|
||||
using (AndroidJavaClass environment =
|
||||
new AndroidJavaClass(AndroidProcessHelper.ANDROID_CLASS_OS_ENVIRONMENT))
|
||||
{
|
||||
// Call static method to get the path of pictures folder in external storage
|
||||
path = environment.CallStatic<AndroidJavaObject>("getExternalStoragePublicDirectory",
|
||||
environment.GetStatic<string>("DIRECTORY_PICTURES"))
|
||||
.Call<string>("getAbsolutePath");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Error on getting the path of pictures folder in external storage in Android: {e}");
|
||||
}
|
||||
|
||||
Debug.Log($"Get path in GetAndroidExternalStoragePicturesDirectory: {path}");
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string GetAndroidPrimaryExternalStorageDirectory()
|
||||
{
|
||||
Debug.Log("GetAndroidPrimaryExternalStorageDirectory called");
|
||||
|
||||
string path = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
using (AndroidJavaClass environment =
|
||||
new AndroidJavaClass(AndroidProcessHelper.ANDROID_CLASS_OS_ENVIRONMENT))
|
||||
{
|
||||
path = environment.CallStatic<AndroidJavaObject>("getExternalStorageDirectory")
|
||||
.Call<string>("getAbsolutePath");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Error on getting the path of pictures folder in external storage in Android: {e}");
|
||||
}
|
||||
|
||||
Debug.Log($"Get path in GetAndroidPrimaryExternalStorageDirectory: {path}");
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static Dictionary<ExternalStorageType, string> GetAndroidAllExternalStorageDirectory()
|
||||
{
|
||||
Debug.Log("GetAndroidAllExternalStorageDirectory called");
|
||||
|
||||
// Get all available external file directories (emulated or removable (aka sd card))
|
||||
AndroidJavaObject[] externalFilesDirectories =
|
||||
AndroidProcessHelper.Activity.Call<AndroidJavaObject[]>("getExternalFilesDirs", (object)null);
|
||||
|
||||
var result = new Dictionary<ExternalStorageType, string>();
|
||||
using (var environment = new AndroidJavaClass(AndroidProcessHelper.ANDROID_CLASS_OS_ENVIRONMENT))
|
||||
{
|
||||
foreach (var item in externalFilesDirectories)
|
||||
{
|
||||
string directory = item.Call<string>("getAbsolutePath");
|
||||
Debug.Log($"Find the path in GetAndroidExternalStorageDirectory: {directory}");
|
||||
|
||||
if (environment.CallStatic<bool>("isExternalStorageRemovable", item))
|
||||
{
|
||||
result.Add(ExternalStorageType.Removable, directory);
|
||||
}
|
||||
else if (environment.CallStatic<bool>("isExternalStorageEmulated", item))
|
||||
{
|
||||
result.Add(ExternalStorageType.Emulated, directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public enum ExternalStorageType
|
||||
{
|
||||
Removable,
|
||||
Emulated
|
||||
}
|
||||
#endif
|
||||
|
||||
public static Task SaveByteDataToDisk(byte[] bytes, string saveDirectory, string fileNameWithFileExtension)
|
||||
{
|
||||
Directory.CreateDirectory(saveDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
string fullPath = Path.Combine(saveDirectory, fileNameWithFileExtension);
|
||||
System.IO.File.WriteAllBytes(fullPath, bytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Error on writing byte data to disk: {e}");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static byte[] OpenFile(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Debug.LogError("File not exist: " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] data = File.ReadAllBytes(path);
|
||||
if (data.Length == 0)
|
||||
{
|
||||
Debug.LogError("File is empty: " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static byte[] OpenJpeg(string path)
|
||||
{
|
||||
byte[] data = OpenFile(path);
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
Debug.LogError("Open Jpeg error");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data[0] != 0xFF || data[1] != 0xD8)
|
||||
{
|
||||
Debug.LogError("File is not JPEG: " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d28163e4ec0b605478774041d1222262
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
#endif
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator.Helper
|
||||
{
|
||||
public static class LayerMaskHelper
|
||||
{
|
||||
public static bool HasLayer(this LayerMask layerMask, int layer)
|
||||
{
|
||||
if (layerMask == (layerMask | (1 << layer)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool[] HasLayers(this LayerMask layerMask)
|
||||
{
|
||||
var hasLayers = new bool[32];
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
if (layerMask == (layerMask | (1 << i)))
|
||||
{
|
||||
Debug.Log($"LayerMask.LayerToName = {LayerMask.LayerToName(i)}");
|
||||
hasLayers[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasLayers;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static class LayerMaskDrawer
|
||||
{
|
||||
public static int LayerMaskField(string label, int layermask)
|
||||
{
|
||||
return FieldToLayerMask(EditorGUILayout.MaskField(label, LayerMaskToField(layermask),
|
||||
InternalEditorUtility.layers));
|
||||
}
|
||||
|
||||
public static int LayerMaskField(Rect position, string label, int layermask)
|
||||
{
|
||||
return FieldToLayerMask(EditorGUI.MaskField(position, label, LayerMaskToField(layermask),
|
||||
InternalEditorUtility.layers));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts field LayerMask values to in game LayerMask values
|
||||
/// </summary>
|
||||
/// <param name="field"></param>
|
||||
/// <returns></returns>
|
||||
private static int FieldToLayerMask(int field)
|
||||
{
|
||||
if (field == -1) return -1;
|
||||
int mask = 0;
|
||||
var layers = InternalEditorUtility.layers;
|
||||
for (int c = 0; c < layers.Length; c++)
|
||||
{
|
||||
if ((field & (1 << c)) != 0)
|
||||
{
|
||||
mask |= 1 << LayerMask.NameToLayer(layers[c]);
|
||||
}
|
||||
else
|
||||
{
|
||||
mask &= ~(1 << LayerMask.NameToLayer(layers[c]));
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts in game LayerMask values to field LayerMask values
|
||||
/// </summary>
|
||||
/// <param name="mask"></param>
|
||||
/// <returns></returns>
|
||||
private static int LayerMaskToField(int mask)
|
||||
{
|
||||
if (mask == -1) return -1;
|
||||
int field = 0;
|
||||
var layers = InternalEditorUtility.layers;
|
||||
for (int c = 0; c < layers.Length; c++)
|
||||
{
|
||||
if ((mask & (1 << LayerMask.NameToLayer(layers[c]))) != 0)
|
||||
{
|
||||
field |= 1 << c;
|
||||
}
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05c260c7d911cff49b0333f5ba628420
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,516 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator.Helper
|
||||
{
|
||||
public class SpectatorCameraHelper
|
||||
{
|
||||
# region Attribute value range
|
||||
|
||||
public const float VERTICAL_FOV_MIN = 10f;
|
||||
public const float VERTICAL_FOV_MAX = 130f;
|
||||
|
||||
public const float FRUSTUM_LINE_WIDTH_MIN = .02f;
|
||||
public const float FRUSTUM_LINE_WIDTH_MAX = .05f;
|
||||
|
||||
public const float FRUSTUM_CENTER_LINE_WIDTH_MIN = .01f;
|
||||
public const float FRUSTUM_CENTER_LINE_WIDTH_MAX = .04f;
|
||||
|
||||
public const int PANORAMA_RESOLUTION_MIN = 512;
|
||||
public const int PANORAMA_RESOLUTION_MAX = 4096;
|
||||
|
||||
public const int SMOOTH_CAMERA_MOVEMENT_MIN = 8;
|
||||
public const int SMOOTH_CAMERA_MOVEMENT_MAX = 16;
|
||||
|
||||
public const float COMPARE_FLOAT_SUPER_SMALL_THRESHOLD = .001f;
|
||||
public const float COMPARE_FLOAT_EXTRA_SMALL_THRESHOLD = .01f;
|
||||
public const float COMPARE_FLOAT_SMALL_THRESHOLD = .1f;
|
||||
public const float COMPARE_FLOAT_MEDIUM_THRESHOLD = 1f;
|
||||
public const float COMPARE_FLOAT_LARGE_THRESHOLD = 10f;
|
||||
|
||||
public const float INTERVAL_SECOND_GET_SPECTATOR_HANDLER = .5f;
|
||||
public const float MAX_SECOND_GET_SPECTATOR_HANDLER = 5f;
|
||||
|
||||
public const float INTERVAL_SECOND_INTERNAL_SPECTATOR_CAMERA = 1f;
|
||||
public const float MAX_SECOND_GET_INTERNAL_SPECTATOR_CAMERA = 2f;
|
||||
|
||||
# endregion
|
||||
|
||||
public static readonly Vector3 SpectatorCameraSpherePrefabScaleDefault = new Vector3(.08f, .08f, .08f);
|
||||
|
||||
#region Attribute default value definition
|
||||
|
||||
public const int TEXTURE_WIDTH = 1920;
|
||||
public const int TEXTURE_HEIGHT = 1080;
|
||||
|
||||
public const CameraSourceRef CAMERA_SOURCE_REF_DEFAULT = CameraSourceRef.Hmd;
|
||||
public static readonly Vector3 PositionDefault = new Vector3(0f, 1.7f, 0f);
|
||||
public static readonly Quaternion RotationDefault = Quaternion.identity;
|
||||
public static readonly LayerMask LayerMaskDefault = -1;
|
||||
|
||||
public const bool IS_SMOOTH_CAMERA_MOVEMENT_DEFAULT = true;
|
||||
public const int SMOOTH_CAMERA_MOVEMENT_SPEED_DEFAULT = 10;
|
||||
|
||||
public const bool IS_FRUSTUM_SHOWED_DEFAULT = false;
|
||||
|
||||
public const float VERTICAL_FOV_DEFAULT = 60f;
|
||||
|
||||
public const SpectatorCameraPanoramaResolution PANORAMA_RESOLUTION_DEFAULT =
|
||||
SpectatorCameraPanoramaResolution._2048;
|
||||
|
||||
public const TextureProcessHelper.PictureOutputFormat PANORAMA_OUTPUT_FORMAT_DEFAULT =
|
||||
TextureProcessHelper.PictureOutputFormat.PNG;
|
||||
|
||||
public const TextureProcessHelper.PanoramaType PANORAMA_TYPE_DEFAULT =
|
||||
TextureProcessHelper.PanoramaType.Monoscopic;
|
||||
|
||||
public const float STEREO_SEPARATION_DEFAULT = 0.065f;
|
||||
|
||||
public const FrustumLineCount FRUSTUM_LINE_COUNT_DEFAULT =
|
||||
FrustumLineCount.Four;
|
||||
|
||||
public const FrustumCenterLineCount FRUSTUM_CENTER_LINE_COUNT_DEFAULT =
|
||||
FrustumCenterLineCount.Center;
|
||||
|
||||
public const float FRUSTUM_LINE_WIDTH_DEFAULT = .02f;
|
||||
public const float FRUSTUM_CENTER_LINE_WIDTH_DEFAULT = .01f;
|
||||
|
||||
public const float FRUSTUM_LINE_BEGIN_DEFAULT = .3f;
|
||||
|
||||
// Define by Unity: https://docs.unity3d.com/ScriptReference/Camera.CalculateFrustumCorners.html
|
||||
public const int FRUSTUM_OUT_CORNERS_COUNT = 4;
|
||||
|
||||
public static readonly Color LineColorDefault = Color.white;
|
||||
public const string LINE_SHADER_NAME_DEFAULT = "UI/Default";
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public const bool IS_DEBUG_SPECTATOR_CAMERA = true;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Frustum game object name definition
|
||||
|
||||
public const string FRUSTUM_LINE_ROOT_NAME_DEFAULT = "FrustumLines";
|
||||
public const string FRUSTUM_LINE_NAME_PREFIX_DEFAULT = "Frustum";
|
||||
public const string FRUSTUM_CENTER_LINE_ROOT_NAME_DEFAULT = "FrustumCenterLines";
|
||||
public const string FRUSTUM_CENTER_LINE_NAME_PREFIX_DEFAULT = "FrustumCenter";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Save file definition
|
||||
|
||||
public const string SAVE_PHOTO360_ALBUM_NAME = "Screenshots";
|
||||
|
||||
public const string ATTRIBUTE_FILE_PREFIX_NAME = "SpectatorCameraAttribute";
|
||||
|
||||
public const string ATTRIBUTE_FILE_EXTENSION = "json";
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Load the attribute file from resource folder
|
||||
/// </summary>
|
||||
/// <param name="sceneName">The corresponding scene name of the attribute file</param>
|
||||
/// <param name="gameObjectName">The corresponding gameObject name of the attribute file</param>
|
||||
/// <param name="data">The attribute data which save in attribute file</param>
|
||||
/// <returns>True if get the attribute data successfully. Otherwise, return false.</returns>
|
||||
public static bool LoadAttributeFileFromResourcesFolder(
|
||||
in string sceneName,
|
||||
in string gameObjectName,
|
||||
out SpectatorCameraAttribute data)
|
||||
{
|
||||
Debug.Log("Get spectator camera attribute at resources folder");
|
||||
|
||||
data = new SpectatorCameraAttribute();
|
||||
TextAsset json;
|
||||
|
||||
// Name format: {PREFIX}_{SCENE NAME}_{GAME OBJECT NAME}.json
|
||||
var fileName = GetSpectatorCameraAttributeFileNamePattern(sceneName, gameObjectName, false);
|
||||
|
||||
try
|
||||
{
|
||||
json = Resources.Load<TextAsset>(fileName);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogWarning("Get attribute data from resources folder fail:");
|
||||
Debug.LogWarning($"{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (json == null)
|
||||
{
|
||||
Debug.LogWarning("The attribute data at resources folder is empty or null");
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug.Log($"The attribute value is: {json.text}");
|
||||
data = JsonUtility.FromJson<SpectatorCameraAttribute>(json.text);
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
Debug.LogWarning("Convert attribute data from resources folder fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug.Log("Get attribute data from resources folder successful");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the attribute file from full path
|
||||
/// </summary>
|
||||
/// <param name="fullPathWithFileNameAndExtension">Full path</param>
|
||||
/// <param name="data">The attribute data which save in persistent folder</param>
|
||||
/// <returns>True if get the attribute data successfully. Otherwise, return false.</returns>
|
||||
public static bool LoadAttributeFileFromFolder(
|
||||
string fullPathWithFileNameAndExtension,
|
||||
out SpectatorCameraAttribute data)
|
||||
{
|
||||
Debug.Log($"Get spectator camera attribute at {fullPathWithFileNameAndExtension}");
|
||||
|
||||
return LoadAttributeData(fullPathWithFileNameAndExtension, out data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the attribute file from persistent folder
|
||||
/// </summary>
|
||||
/// <param name="sceneName">The corresponding scene name of the attribute file</param>
|
||||
/// <param name="gameObjectName">The corresponding gameObject name of the attribute file</param>
|
||||
/// <param name="data">The attribute data which save in persistent folder</param>
|
||||
/// <returns>True if get the attribute data successfully. Otherwise, return false.</returns>
|
||||
public static bool LoadAttributeFileFromPersistentFolder(
|
||||
in string sceneName,
|
||||
in string gameObjectName,
|
||||
out SpectatorCameraAttribute data)
|
||||
{
|
||||
// Name format: {PREFIX}_{SCENE NAME}_{GAME OBJECT NAME}.json
|
||||
var fileNameWithExtension = GetSpectatorCameraAttributeFileNamePattern(sceneName, gameObjectName);
|
||||
|
||||
var attributeFileDirectoryAndFileNameWithExtension =
|
||||
Path.Combine(Application.persistentDataPath, fileNameWithExtension);
|
||||
|
||||
Debug.Log($"Get spectator camera attribute at {attributeFileDirectoryAndFileNameWithExtension}");
|
||||
|
||||
return LoadAttributeData(attributeFileDirectoryAndFileNameWithExtension, out data);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Save the spectator camera attribute as a JSON file to the resources folder located in the Unity project's assets folder.
|
||||
/// </summary>
|
||||
public static void SaveAttributeData2ResourcesFolder(
|
||||
in string sceneName,
|
||||
in string gameObjectName,
|
||||
in SpectatorCameraAttribute data)
|
||||
{
|
||||
// Name format: {PREFIX}_{SCENE NAME}_{GAME OBJECT NAME}.json
|
||||
var fileNameWithExtension = GetSpectatorCameraAttributeFileNamePattern(sceneName, gameObjectName);
|
||||
string resourcesFolderPath = Path.Combine(Application.dataPath, "Resources");
|
||||
SaveAttributeData(resourcesFolderPath, fileNameWithExtension, data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Save the spectator camera attribute as a JSON file to a persistent folder.
|
||||
/// </summary>
|
||||
public static void SaveAttributeData2PersistentFolder(
|
||||
in string sceneName,
|
||||
in string gameObjectName,
|
||||
in SpectatorCameraAttribute data)
|
||||
{
|
||||
// Name format: {PREFIX}_{SCENE NAME}_{GAME OBJECT NAME}.json
|
||||
var fileNameWithExtension = GetSpectatorCameraAttributeFileNamePattern(sceneName, gameObjectName);
|
||||
|
||||
SaveAttributeData(Application.persistentDataPath, fileNameWithExtension, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the spectator camera attribute as a JSON file on disk.
|
||||
/// </summary>
|
||||
/// <param name="fullPathWithFileNameAndExtension">The path (string) that include directory, file name and extension</param>
|
||||
/// <param name="data">The data of spectator camera attribute</param>
|
||||
/// <returns>True if get the attribute data successfully. Otherwise, return false.</returns>
|
||||
private static bool LoadAttributeData(
|
||||
string fullPathWithFileNameAndExtension,
|
||||
out SpectatorCameraAttribute data)
|
||||
{
|
||||
data = new SpectatorCameraAttribute();
|
||||
string json;
|
||||
try
|
||||
{
|
||||
json = File.ReadAllText(fullPathWithFileNameAndExtension);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogWarning($"Get attribute data from {fullPathWithFileNameAndExtension} failed: {e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
Debug.LogWarning($"The attribute data at {fullPathWithFileNameAndExtension} is empty or null");
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug.Log($"The attribute value is: {json}");
|
||||
data = JsonUtility.FromJson<SpectatorCameraAttribute>(json);
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
Debug.LogWarning($"Convert attribute data from {fullPathWithFileNameAndExtension} failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug.Log($"Get attribute data from {fullPathWithFileNameAndExtension} successful");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the spectator camera attribute as a JSON file on disk.
|
||||
/// </summary>
|
||||
/// <param name="saveFileDirectory">The directory that the spectator camera attribute (JSON file) will save to</param>
|
||||
/// <param name="saveFileNameWithExtension">The file name of the spectator camera attribute (JSON file)</param>
|
||||
/// <param name="data">The data of spectator camera attribute</param>
|
||||
private static void SaveAttributeData(
|
||||
in string saveFileDirectory,
|
||||
in string saveFileNameWithExtension,
|
||||
in SpectatorCameraAttribute data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(saveFileDirectory) || string.IsNullOrEmpty(saveFileNameWithExtension))
|
||||
{
|
||||
Debug.LogError("The saving file directory or name is null or empty");
|
||||
return;
|
||||
}
|
||||
|
||||
string fullPath = Path.Combine(saveFileDirectory, saveFileNameWithExtension);
|
||||
|
||||
// Convert to string format
|
||||
string json = JsonUtility.ToJson(data);
|
||||
|
||||
// Make sure the file path is exist
|
||||
if (!Directory.Exists(saveFileDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(saveFileDirectory);
|
||||
}
|
||||
|
||||
File.WriteAllText(fullPath, json);
|
||||
|
||||
Debug.Log($"The configuration save at {fullPath}");
|
||||
}
|
||||
|
||||
public static string GetSpectatorCameraAttributeFileNamePattern(
|
||||
in string sceneName,
|
||||
in string gameObjectName,
|
||||
bool withExtension = true)
|
||||
{
|
||||
var fileNamePattern = $"{ATTRIBUTE_FILE_PREFIX_NAME}_{sceneName}_{gameObjectName}";
|
||||
if (withExtension)
|
||||
{
|
||||
fileNamePattern += $".{ATTRIBUTE_FILE_EXTENSION}";
|
||||
}
|
||||
|
||||
return fileNamePattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the panorama resolution is power of two or not. If not, convert to power of two.
|
||||
/// </summary>
|
||||
/// <param name="inputResolution">The panorama resolution</param>
|
||||
/// <returns></returns>
|
||||
public static int CheckAndConvertPanoramaResolution(in int inputResolution)
|
||||
{
|
||||
int result;
|
||||
|
||||
// Check is power of two
|
||||
if ((inputResolution != 0) && ((inputResolution & (inputResolution - 1)) == 0))
|
||||
{
|
||||
result = Mathf.Clamp(inputResolution, PANORAMA_RESOLUTION_MIN, PANORAMA_RESOLUTION_MAX);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not power of two, convert to power of two
|
||||
int clampInputValue = Mathf.Clamp(inputResolution, PANORAMA_RESOLUTION_MIN, PANORAMA_RESOLUTION_MAX);
|
||||
var base2LogarithmOfClampInputValue = (int)System.Math.Log(clampInputValue, 2);
|
||||
|
||||
var base2LogarithmOfClampInputValueLowerStepValue =
|
||||
(int)System.Math.Pow(2, base2LogarithmOfClampInputValue);
|
||||
var base2LogarithmOfClampInputValueUpperStepValue =
|
||||
(int)System.Math.Pow(2, base2LogarithmOfClampInputValue + 1);
|
||||
|
||||
// Check which one is closer
|
||||
if (clampInputValue - base2LogarithmOfClampInputValueLowerStepValue <=
|
||||
base2LogarithmOfClampInputValueUpperStepValue - clampInputValue)
|
||||
{
|
||||
result = base2LogarithmOfClampInputValueLowerStepValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = base2LogarithmOfClampInputValueUpperStepValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region Functions of changing camera culling mask
|
||||
|
||||
/// <summary>
|
||||
/// Set the layer that the camera can watch
|
||||
/// </summary>
|
||||
/// <param name="targetCullingMask">The camera culling mask</param>
|
||||
/// <param name="shownLayer">The layer number that you want to set the camera can watch</param>
|
||||
/// <returns>The new camera culling mask after modify</returns>
|
||||
public static LayerMask SetCameraVisualizationLayer(in LayerMask targetCullingMask, in int shownLayer)
|
||||
{
|
||||
LayerMask targetLayerMaskAfterModify = targetCullingMask;
|
||||
targetLayerMaskAfterModify |= 1 << shownLayer;
|
||||
return targetLayerMaskAfterModify;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the layer that the camera cannot watch
|
||||
/// </summary>
|
||||
/// <param name="targetCullingMask">The camera culling mask</param>
|
||||
/// <param name="hiddenLayer">The layer number that you want to set the camera cannot watch</param>
|
||||
/// <returns>The new camera culling mask after modify</returns>
|
||||
public static LayerMask SetCameraHiddenLayer(in LayerMask targetCullingMask, in int hiddenLayer)
|
||||
{
|
||||
LayerMask targetLayerMaskAfterModify = targetCullingMask;
|
||||
targetLayerMaskAfterModify &= ~(1 << hiddenLayer);
|
||||
return targetLayerMaskAfterModify;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inverse specific layer in camera culling mask
|
||||
/// </summary>
|
||||
/// <param name="targetCullingMask">The camera culling mask</param>
|
||||
/// <param name="inverseLayer">The layer number that you want to inverse</param>
|
||||
/// <returns>The new camera culling mask after modify</returns>
|
||||
public static LayerMask InverseCameraLayer(in LayerMask targetCullingMask, in int inverseLayer)
|
||||
{
|
||||
LayerMask targetLayerMaskAfterModify = targetCullingMask;
|
||||
targetLayerMaskAfterModify ^= 1 << inverseLayer;
|
||||
return targetLayerMaskAfterModify;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[System.Serializable]
|
||||
public class SpectatorCameraAttribute
|
||||
{
|
||||
#region Serializable class field
|
||||
|
||||
public CameraSourceRef source;
|
||||
public Vector3 position;
|
||||
public Quaternion rotation;
|
||||
public LayerMask layerMask;
|
||||
public bool isSmoothCameraMovement;
|
||||
public int smoothCameraMovementSpeed;
|
||||
public bool isFrustumShowed;
|
||||
public float verticalFov;
|
||||
public SpectatorCameraPanoramaResolution panoramaResolution;
|
||||
public TextureProcessHelper.PictureOutputFormat panoramaOutputFormat;
|
||||
public TextureProcessHelper.PanoramaType panoramaOutputType;
|
||||
public FrustumLineCount frustumLineCount;
|
||||
public FrustumCenterLineCount frustumCenterLineCount;
|
||||
public float frustumLineWidth;
|
||||
public float frustumCenterLineWidth;
|
||||
public Color frustumLineColor;
|
||||
public Color frustumCenterLineColor;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public SpectatorCameraAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
public SpectatorCameraAttribute(
|
||||
CameraSourceRef source,
|
||||
Vector3 position,
|
||||
Quaternion rotation,
|
||||
LayerMask layerMask,
|
||||
bool isSmoothCameraMovement,
|
||||
int smoothCameraMovementSpeed,
|
||||
bool isFrustumShowed,
|
||||
float verticalFov,
|
||||
SpectatorCameraPanoramaResolution panoramaResolution,
|
||||
TextureProcessHelper.PictureOutputFormat panoramaOutputFormat,
|
||||
TextureProcessHelper.PanoramaType panoramaOutputType,
|
||||
FrustumLineCount frustumLineCount,
|
||||
FrustumCenterLineCount frustumCenterLineCount,
|
||||
float frustumLineWidth,
|
||||
float frustumCenterLineWidth,
|
||||
Color frustumLineColor,
|
||||
Color frustumCenterLineColor)
|
||||
{
|
||||
this.source = source;
|
||||
this.position = position;
|
||||
this.rotation = rotation;
|
||||
this.layerMask = layerMask;
|
||||
this.isSmoothCameraMovement = isSmoothCameraMovement;
|
||||
this.smoothCameraMovementSpeed = smoothCameraMovementSpeed;
|
||||
this.isFrustumShowed = isFrustumShowed;
|
||||
this.verticalFov = verticalFov;
|
||||
this.panoramaResolution = panoramaResolution;
|
||||
this.panoramaOutputFormat = panoramaOutputFormat;
|
||||
this.panoramaOutputType = panoramaOutputType;
|
||||
this.frustumLineCount = frustumLineCount;
|
||||
this.frustumCenterLineCount = frustumCenterLineCount;
|
||||
this.frustumLineWidth = frustumLineWidth;
|
||||
this.frustumCenterLineWidth = frustumCenterLineWidth;
|
||||
this.frustumLineColor = frustumLineColor;
|
||||
this.frustumCenterLineColor = frustumCenterLineColor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Enum definition
|
||||
|
||||
public enum CameraSourceRef
|
||||
{
|
||||
Hmd,
|
||||
Tracker
|
||||
}
|
||||
|
||||
public enum FrustumLineCount
|
||||
{
|
||||
None = 0,
|
||||
Four = 4,
|
||||
Eight = 8,
|
||||
Sixteen = 16,
|
||||
ThirtyTwo = 32,
|
||||
OneTwentyEight = 128,
|
||||
}
|
||||
|
||||
public enum FrustumCenterLineCount
|
||||
{
|
||||
None = 0,
|
||||
Center = 1,
|
||||
RuleOfThirds = 4,
|
||||
CenterAndRuleOfThirds = 5,
|
||||
}
|
||||
|
||||
public enum SpectatorCameraPanoramaResolution
|
||||
{
|
||||
_512 = 512,
|
||||
_1024 = 1024,
|
||||
_2048 = 2048,
|
||||
_4096 = 4096
|
||||
}
|
||||
|
||||
public enum AttributeFileLocation
|
||||
{
|
||||
ResourceFolder,
|
||||
PersistentFolder
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3136b6864f4cb9c4da5234953d3a75b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09984648d0f364648af4f0ac5a03de55
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using VIVE.OpenXR.Toolkits.Spectator.Helper;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator
|
||||
{
|
||||
/// <summary>
|
||||
/// Name: ISpectatorCameraSetting.cs
|
||||
/// Role: Contract
|
||||
/// Responsibility: Define the setting attribute of the spectator camera.
|
||||
/// </summary>
|
||||
public interface ISpectatorCameraSetting
|
||||
{
|
||||
#region Property
|
||||
|
||||
/// <summary>
|
||||
/// The struct UnityEngine.LayerMask defines which layer the camera can see or not.
|
||||
/// </summary>
|
||||
LayerMask LayerMask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable the feature of smoothing the spectator camera movement.
|
||||
/// </summary>
|
||||
bool IsSmoothCameraMovement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The speed factor to control the smoothing impact.
|
||||
/// </summary>
|
||||
int SmoothCameraMovementSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if visualize the spectator camera vertical FOV.
|
||||
/// </summary>
|
||||
bool IsFrustumShowed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The spectator camera vertical FOV.
|
||||
/// </summary>
|
||||
float VerticalFov { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The panorama image resolution.
|
||||
/// </summary>
|
||||
SpectatorCameraHelper.SpectatorCameraPanoramaResolution PanoramaResolution { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The panorama image output format.
|
||||
/// </summary>
|
||||
TextureProcessHelper.PictureOutputFormat PanoramaOutputFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The panorama types.
|
||||
/// </summary>
|
||||
TextureProcessHelper.PanoramaType PanoramaOutputType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How many frustum lines will be shown?
|
||||
/// </summary>
|
||||
SpectatorCameraHelper.FrustumLineCount FrustumLineCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How many frustum center lines will be shown?
|
||||
/// </summary>
|
||||
SpectatorCameraHelper.FrustumCenterLineCount FrustumCenterLineCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Frustum line width.
|
||||
/// </summary>
|
||||
float FrustumLineWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Frustum center line width.
|
||||
/// </summary>
|
||||
float FrustumCenterLineWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Frustum line color.
|
||||
/// </summary>
|
||||
Color FrustumLineColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Frustum center line color.
|
||||
/// </summary>
|
||||
Color FrustumCenterLineColor { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Function
|
||||
|
||||
/// <summary>
|
||||
/// Reset the spectator camera setting to the default value.
|
||||
/// </summary>
|
||||
void ResetSetting();
|
||||
|
||||
/// <summary>
|
||||
/// Export the current spectator camera setting as a JSON file and then save it to the resource folder or persistent folder.
|
||||
/// </summary>
|
||||
/// <param name="attributeFileLocation">The enum SpectatorCameraHelper.AttributeFileLocation.</param>
|
||||
void ExportSetting2JsonFile(in SpectatorCameraHelper.AttributeFileLocation attributeFileLocation);
|
||||
|
||||
/// <summary>
|
||||
/// Load the setting (JSON) file via input full file path.
|
||||
/// </summary>
|
||||
/// <param name="jsonFilePath">The setting file’s full path (including file name and JSON extension).</param>
|
||||
void LoadSettingFromJsonFile(in string jsonFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Load the setting (JSON) file via input scene name, GameObject (hmd) name, and the file location (resource folder or persistent folder).
|
||||
/// </summary>
|
||||
/// <param name="sceneName">The scene name.</param>
|
||||
/// <param name="gameObjectName">The GameObject name.</param>
|
||||
/// <param name="attributeFileLocation"> The enum SpectatorCameraHelper.AttributeFileLocation.</param>
|
||||
void LoadSettingFromJsonFile(
|
||||
in string sceneName,
|
||||
in string gameObjectName,
|
||||
in SpectatorCameraHelper.AttributeFileLocation attributeFileLocation);
|
||||
|
||||
/// <summary>
|
||||
/// Apply the spectator camera setting to the current component.
|
||||
/// </summary>
|
||||
/// <param name="data">The data you want to apply.</param>
|
||||
void ApplyData(in SpectatorCameraHelper.SpectatorCameraAttribute data);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 913ccea163eb50d4e911f0e8cadaadeb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,748 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VIVE.OpenXR.Toolkits.Spectator.Helper;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator
|
||||
{
|
||||
/// <summary>
|
||||
/// Name: SpectatorCameraManager.Editor.cs
|
||||
/// Role: General script use in Unity Editor only
|
||||
/// Responsibility: Display the SpectatorCameraManager.cs in Unity Inspector
|
||||
/// </summary>
|
||||
public partial class SpectatorCameraManager
|
||||
{
|
||||
[SerializeField] private Material spectatorCameraViewMaterial;
|
||||
|
||||
/// <summary>
|
||||
/// Material that show the spectator camera view
|
||||
/// </summary>
|
||||
private Material SpectatorCameraViewMaterial
|
||||
{
|
||||
// get; private set;
|
||||
get => spectatorCameraViewMaterial;
|
||||
set
|
||||
{
|
||||
spectatorCameraViewMaterial = value;
|
||||
if (SpectatorCameraBased && value)
|
||||
{
|
||||
SpectatorCameraBased.SpectatorCameraViewMaterial = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[field: SerializeField] private bool IsShowHmdPart { get; set; }
|
||||
[field: SerializeField] private bool IsShowTrackerPart { get; set; }
|
||||
[field: SerializeField] private bool IsRequireLoadJsonFile { get; set; }
|
||||
|
||||
[CustomEditor(typeof(SpectatorCameraManager))]
|
||||
public class SpectatorCameraManagerEditor : UnityEditor.Editor
|
||||
{
|
||||
private static readonly Color HighlightRegionBackgroundColor = new Color(.2f, .2f, .2f, .1f);
|
||||
|
||||
private SerializedProperty IsShowHmdPart { get; set; }
|
||||
private SerializedProperty IsShowTrackerPart { get; set; }
|
||||
private SerializedProperty IsRequireLoadJsonFile { get; set; }
|
||||
private List<string> JsonFileList { get; set; }
|
||||
private Vector2 JsonFileScrollViewVector { get; set; }
|
||||
|
||||
private SerializedProperty IsSmoothCameraMovement { get; set; }
|
||||
private SerializedProperty SmoothCameraMovementSpeed { get; set; }
|
||||
private SerializedProperty PanoramaResolution { get; set; }
|
||||
private SerializedProperty PanoramaOutputFormat { get; set; }
|
||||
private SerializedProperty PanoramaOutputType { get; set; }
|
||||
|
||||
private SerializedProperty SpectatorCameraPrefab { get; set; }
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
IsShowHmdPart = serializedObject.FindProperty(EditorHelper.PropertyName("IsShowHmdPart"));
|
||||
IsShowTrackerPart = serializedObject.FindProperty(EditorHelper.PropertyName("IsShowTrackerPart"));
|
||||
IsRequireLoadJsonFile =
|
||||
serializedObject.FindProperty(EditorHelper.PropertyName("IsRequireLoadJsonFile"));
|
||||
JsonFileList = new List<string>();
|
||||
JsonFileScrollViewVector = Vector2.zero;
|
||||
|
||||
IsSmoothCameraMovement =
|
||||
serializedObject.FindProperty(EditorHelper.PropertyName("IsSmoothCameraMovement"));
|
||||
SmoothCameraMovementSpeed =
|
||||
serializedObject.FindProperty(EditorHelper.PropertyName("SmoothCameraMovementSpeed"));
|
||||
|
||||
PanoramaResolution = serializedObject.FindProperty(EditorHelper.PropertyName("PanoramaResolution"));
|
||||
PanoramaOutputFormat = serializedObject.FindProperty(EditorHelper.PropertyName("PanoramaOutputFormat"));
|
||||
PanoramaOutputType = serializedObject.FindProperty(EditorHelper.PropertyName("PanoramaOutputType"));
|
||||
|
||||
SpectatorCameraPrefab =
|
||||
serializedObject.FindProperty(EditorHelper.PropertyName("SpectatorCameraPrefab"));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
// Just return if not "SpectatorCameraManager" class
|
||||
if (!(target is SpectatorCameraManager))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
DrawGUI();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawGUI()
|
||||
{
|
||||
#region GUIStyle
|
||||
|
||||
var labelStyle = new GUIStyle()
|
||||
{
|
||||
richText = true,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
normal = new GUIStyleState
|
||||
{
|
||||
textColor = EditorGUIUtility.isProSkin ? Color.green : Color.black
|
||||
}
|
||||
};
|
||||
|
||||
var resetButtonStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fontStyle = FontStyle.Bold,
|
||||
normal = new GUIStyleState
|
||||
{
|
||||
textColor = EditorGUIUtility.isProSkin ? Color.yellow : Color.red
|
||||
},
|
||||
hover = new GUIStyleState
|
||||
{
|
||||
textColor = Color.red
|
||||
},
|
||||
active = new GUIStyleState
|
||||
{
|
||||
textColor = Color.cyan
|
||||
},
|
||||
};
|
||||
|
||||
var boldButtonStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
var script = (SpectatorCameraManager)target;
|
||||
|
||||
// Button for reset value
|
||||
if (GUILayout.Button("Reset to default value", resetButtonStyle))
|
||||
{
|
||||
Undo.RecordObject(target, "Reset SpectatorCameraManager to default value");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.ResetSetting();
|
||||
}
|
||||
|
||||
// Button for export setting
|
||||
if (GUILayout.Button("Export Spectator Camera HMD Setting", boldButtonStyle))
|
||||
{
|
||||
script.ExportSetting2JsonFile(SpectatorCameraHelper.AttributeFileLocation.ResourceFolder);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
#region Load Setting From JSON File
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
EditorGUI.BeginDisabledGroup(IsRequireLoadJsonFile.boolValue);
|
||||
if (GUILayout.Button("Load Setting From JSON File in Resources Folder", boldButtonStyle) ||
|
||||
IsRequireLoadJsonFile.boolValue)
|
||||
{
|
||||
IsRequireLoadJsonFile.boolValue = true;
|
||||
var searchPattern =
|
||||
$"{SpectatorCameraHelper.ATTRIBUTE_FILE_PREFIX_NAME}*.{SpectatorCameraHelper.ATTRIBUTE_FILE_EXTENSION}";
|
||||
|
||||
if (JsonFileList == null)
|
||||
{
|
||||
JsonFileList = new List<string>();
|
||||
}
|
||||
|
||||
JsonFileList.Clear();
|
||||
|
||||
var dir = new DirectoryInfo(Path.Combine(Application.dataPath, "Resources"));
|
||||
var files = dir.GetFiles(searchPattern);
|
||||
foreach (var item in files)
|
||||
{
|
||||
JsonFileList.Add(item.Name);
|
||||
}
|
||||
|
||||
if (JsonFileList.Count == 0)
|
||||
{
|
||||
Debug.Log(
|
||||
"Can't find any JSON file related to the spectator camera setting in the Resources folder.");
|
||||
IsRequireLoadJsonFile.boolValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (IsRequireLoadJsonFile.boolValue)
|
||||
{
|
||||
if (GUILayout.Button("Cancel"))
|
||||
{
|
||||
IsRequireLoadJsonFile.boolValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (IsRequireLoadJsonFile.boolValue)
|
||||
{
|
||||
Rect r = EditorGUILayout.BeginVertical();
|
||||
|
||||
JsonFileScrollViewVector = EditorGUILayout.BeginScrollView(JsonFileScrollViewVector,
|
||||
GUILayout.Width(r.width),
|
||||
GUILayout.Height(80));
|
||||
|
||||
for (int i = 0; i < JsonFileList.Count; i++)
|
||||
{
|
||||
if (GUILayout.Button(JsonFileList[i]))
|
||||
{
|
||||
var path = Path.Combine(
|
||||
System.IO.Path.Combine(Application.dataPath, "Resources"),
|
||||
JsonFileList[i]);
|
||||
Undo.RecordObject(target, $"Load {JsonFileList[i]} setting to {target.name} SpectatorCameraManager");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.LoadSettingFromJsonFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
// Spectator camera prefab
|
||||
EditorGUILayout.PropertyField(SpectatorCameraPrefab, new GUIContent("Spectator Camera Prefab"));
|
||||
if (SpectatorCameraPrefab.objectReferenceValue != null &&
|
||||
PrefabUtility.GetPrefabAssetType(SpectatorCameraPrefab.objectReferenceValue) ==
|
||||
PrefabAssetType.NotAPrefab)
|
||||
{
|
||||
// The assign object is scene object
|
||||
Debug.Log("Please assign the object as prefab only.");
|
||||
SpectatorCameraPrefab.objectReferenceValue = null;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ General Setting ]</b>", labelStyle);
|
||||
|
||||
// Setting of spectator camera reference source
|
||||
// EditorGUILayout.PropertyField(CameraSourceRef, new GUIContent("Camera Source"));
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentCameraSourceRef = (SpectatorCameraHelper.CameraSourceRef)
|
||||
EditorGUILayout.EnumPopup("Camera Source", script.CameraSourceRef);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager CameraSourceRef");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.CameraSourceRef = currentCameraSourceRef;
|
||||
}
|
||||
|
||||
#region Tracker Region
|
||||
|
||||
if (script.CameraSourceRef == SpectatorCameraHelper.CameraSourceRef.Tracker)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentFollowSpectatorCameraTracker = EditorGUILayout.ObjectField(
|
||||
"Tracker",
|
||||
script.FollowSpectatorCameraTracker,
|
||||
typeof(SpectatorCameraTracker),
|
||||
true) as SpectatorCameraTracker;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager FollowSpectatorCameraTracker");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FollowSpectatorCameraTracker = currentFollowSpectatorCameraTracker;
|
||||
}
|
||||
|
||||
if (script.FollowSpectatorCameraTracker == null)
|
||||
{
|
||||
// The assign object is null
|
||||
EditorGUILayout.HelpBox("Please assign the SpectatorCameraTracker", MessageType.Info, false);
|
||||
}
|
||||
else if (PrefabUtility.GetPrefabAssetType(script.FollowSpectatorCameraTracker) !=
|
||||
PrefabAssetType.NotAPrefab)
|
||||
{
|
||||
// Don't allow assign object is prefab
|
||||
Debug.Log("Please assign the scene object.");
|
||||
script.FollowSpectatorCameraTracker = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The assign object is scene object => ok
|
||||
EditorGUILayout.LabelField("\n");
|
||||
IsShowTrackerPart.boolValue =
|
||||
EditorGUILayout.Foldout(IsShowTrackerPart.boolValue, "Tracker Setting");
|
||||
if (IsShowTrackerPart.boolValue)
|
||||
{
|
||||
// If show the tracker setting
|
||||
Rect r = EditorGUILayout.BeginVertical();
|
||||
|
||||
SpectatorCameraTracker trackerObject = script.FollowSpectatorCameraTracker;
|
||||
|
||||
if (trackerObject != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
$"You are now editing the tracker setting in \"{trackerObject.gameObject.name}\" GameObject",
|
||||
MessageType.Info,
|
||||
true);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectLayerMask =
|
||||
LayerMaskHelper.LayerMaskDrawer.LayerMaskField("Camera Layer Mask",
|
||||
trackerObject.LayerMask);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} LayerMask");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.LayerMask = currentTrackerObjectLayerMask;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectIsSmoothCameraMovement=
|
||||
EditorGUILayout.Toggle("Enable Smoothing Camera Movement",
|
||||
trackerObject.IsSmoothCameraMovement);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} IsSmoothCameraMovement");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.IsSmoothCameraMovement = currentTrackerObjectIsSmoothCameraMovement;
|
||||
}
|
||||
|
||||
if (trackerObject.IsSmoothCameraMovement)
|
||||
{
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ Smooth Camera Movement Speed Setting ]</b>",
|
||||
labelStyle);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectSmoothCameraMovementSpeed =
|
||||
EditorGUILayout.IntSlider(
|
||||
new GUIContent("Speed of Smoothing Camera Movement"),
|
||||
trackerObject.SmoothCameraMovementSpeed,
|
||||
SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_MIN,
|
||||
SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} SmoothCameraMovementSpeed");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.SmoothCameraMovementSpeed = currentTrackerObjectSmoothCameraMovementSpeed;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Spectator camera frustum show/hide
|
||||
var currentTrackerObjectIsFrustumShowed =
|
||||
EditorGUILayout.Toggle("Enable Camera FOV Frustum", trackerObject.IsFrustumShowed);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} IsFrustumShowed");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.IsFrustumShowed = currentTrackerObjectIsFrustumShowed;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region VerticalFov
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ Vertical FOV Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectVerticalFov = EditorGUILayout.Slider(
|
||||
"Vertical FOV",
|
||||
trackerObject.VerticalFov,
|
||||
SpectatorCameraHelper.VERTICAL_FOV_MIN,
|
||||
SpectatorCameraHelper.VERTICAL_FOV_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} VerticalFov");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.VerticalFov = currentTrackerObjectVerticalFov;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Setting related to panorama capturing of spectator camera
|
||||
|
||||
// Panorama resolution
|
||||
EditorGUILayout.LabelField("<b>[ Panorama Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Panorama output resolution
|
||||
var currentTrackerObjectPanoramaResolution =
|
||||
(SpectatorCameraHelper.SpectatorCameraPanoramaResolution)
|
||||
EditorGUILayout.EnumPopup("Resolution", trackerObject.PanoramaResolution);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} PanoramaResolution");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.PanoramaResolution = currentTrackerObjectPanoramaResolution;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Panorama output format
|
||||
var currentTrackerObjectPanoramaOutputFormat = (TextureProcessHelper.PictureOutputFormat)
|
||||
EditorGUILayout.EnumPopup("Output Format", trackerObject.PanoramaOutputFormat);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} PanoramaOutputFormat");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.PanoramaOutputFormat = currentTrackerObjectPanoramaOutputFormat;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Panorama output type
|
||||
var currentTrackerObjectPanoramaOutputType = (TextureProcessHelper.PanoramaType)
|
||||
EditorGUILayout.EnumPopup("Output Type", trackerObject.PanoramaOutputType);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} PanoramaOutputType");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.PanoramaOutputType = currentTrackerObjectPanoramaOutputType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Setting related to frustum
|
||||
|
||||
if (trackerObject.IsFrustumShowed)
|
||||
{
|
||||
EditorGUILayout.LabelField("<b>[ Frustum Setting ]</b>",
|
||||
labelStyle);
|
||||
|
||||
#region Count of frustum and frustum center line
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectFrustumLineCount = (SpectatorCameraHelper.FrustumLineCount)
|
||||
EditorGUILayout.EnumPopup("Frustum Line Total", trackerObject.FrustumLineCount);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} FrustumLineCount");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.FrustumLineCount = currentTrackerObjectFrustumLineCount;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectFrustumCenterLineCount =
|
||||
(SpectatorCameraHelper.FrustumCenterLineCount)
|
||||
EditorGUILayout.EnumPopup("Frustum Center Line Total",
|
||||
trackerObject.FrustumCenterLineCount);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} FrustumCenterLineCount");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.FrustumCenterLineCount = currentTrackerObjectFrustumCenterLineCount;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Width of frustum and frustum center line
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectFrustumLineWidth =
|
||||
EditorGUILayout.Slider(
|
||||
"Frustum Line Width",
|
||||
trackerObject.FrustumLineWidth,
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MIN,
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} FrustumLineWidth");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.FrustumLineWidth = currentTrackerObjectFrustumLineWidth;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectFrustumCenterLineWidth =
|
||||
EditorGUILayout.Slider(
|
||||
"Frustum Center Line Width",
|
||||
trackerObject.FrustumCenterLineWidth,
|
||||
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MIN,
|
||||
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} FrustumCenterLineWidth");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.FrustumCenterLineWidth = currentTrackerObjectFrustumCenterLineWidth;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Material of frustum and frustum center line
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectFrustumLineColor = EditorGUILayout.ColorField(
|
||||
"Frustum Line Color", trackerObject.FrustumLineColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} FrustumLineColor");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.FrustumLineColor = currentTrackerObjectFrustumLineColor;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentTrackerObjectFrustumCenterLineColor = EditorGUILayout.ColorField(
|
||||
"Frustum Center Line Color",
|
||||
trackerObject.FrustumCenterLineColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(trackerObject, $"Modified {trackerObject.name} FrustumCenterLineColor");
|
||||
EditorUtility.SetDirty(trackerObject);
|
||||
trackerObject.FrustumCenterLineColor = currentTrackerObjectFrustumCenterLineColor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
r = new Rect(r.x, r.y, r.width, r.height);
|
||||
EditorGUI.DrawRect(r, HighlightRegionBackgroundColor);
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HMD Region
|
||||
|
||||
IsShowHmdPart.boolValue = EditorGUILayout.Foldout(IsShowHmdPart.boolValue, "HMD Setting");
|
||||
if (IsShowHmdPart.boolValue)
|
||||
{
|
||||
Rect r = EditorGUILayout.BeginVertical();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Setting of spectator camera layer mask
|
||||
var currentLayerMask =
|
||||
LayerMaskHelper.LayerMaskDrawer.LayerMaskField("Camera Layer Mask", script.LayerMask);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager LayerMask");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.LayerMask = currentLayerMask;
|
||||
}
|
||||
|
||||
// Setting of smooth spectator camera movement
|
||||
EditorGUILayout.PropertyField(IsSmoothCameraMovement,
|
||||
new GUIContent("Enable Smoothing Camera Movement"));
|
||||
if (IsSmoothCameraMovement.boolValue)
|
||||
{
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ Smooth Camera Movement Speed Setting ]</b>", labelStyle);
|
||||
|
||||
// Setting of smooth spectator camera movement speed
|
||||
EditorGUILayout.IntSlider(
|
||||
SmoothCameraMovementSpeed,
|
||||
SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_MIN,
|
||||
SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_MAX,
|
||||
"Speed of Smoothing Camera Movement");
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Spectator camera frustum show/hide
|
||||
var currentIsFrustumShowed =
|
||||
EditorGUILayout.Toggle("Enable Camera FOV Frustum", script.IsFrustumShowed);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager IsFrustumShowed");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.IsFrustumShowed = currentIsFrustumShowed;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region VerticalFov
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ Vertical FOV Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// FOV
|
||||
var currentVerticalFov = EditorGUILayout.Slider(
|
||||
"Vertical FOV",
|
||||
script.VerticalFov,
|
||||
SpectatorCameraHelper.VERTICAL_FOV_MIN,
|
||||
SpectatorCameraHelper.VERTICAL_FOV_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager VerticalFov");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.VerticalFov = currentVerticalFov;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Setting related to panorama capturing of spectator camera
|
||||
|
||||
// Panorama resolution
|
||||
EditorGUILayout.LabelField("<b>[ Panorama Setting ]</b>", labelStyle);
|
||||
EditorGUILayout.PropertyField(PanoramaResolution, new GUIContent("Resolution"));
|
||||
|
||||
// Panorama output format
|
||||
EditorGUILayout.PropertyField(PanoramaOutputFormat, new GUIContent("Output Format"));
|
||||
|
||||
// Panorama output type
|
||||
EditorGUILayout.PropertyField(PanoramaOutputType, new GUIContent("Output Type"));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Setting related to frustum
|
||||
|
||||
if (script.IsFrustumShowed)
|
||||
{
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ Frustum Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Count of frustum line
|
||||
var currentFrustumLineCount = (SpectatorCameraHelper.FrustumLineCount)
|
||||
EditorGUILayout.EnumPopup("Frustum Line Total", script.FrustumLineCount);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager FrustumLineCount");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumLineCount = currentFrustumLineCount;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Count of frustum center line
|
||||
var currentFrustumCenterLineCount = (SpectatorCameraHelper.FrustumCenterLineCount)
|
||||
EditorGUILayout.EnumPopup("Frustum Center Line Total", script.FrustumCenterLineCount);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager FrustumCenterLineCount");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumCenterLineCount = currentFrustumCenterLineCount;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Width of frustum line
|
||||
var currentFrustumLineWidth =
|
||||
EditorGUILayout.Slider(
|
||||
"Frustum Line Width",
|
||||
script.FrustumLineWidth,
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MIN,
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager FrustumLineWidth");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumLineWidth = currentFrustumLineWidth;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Width of frustum center line
|
||||
var currentFrustumCenterLineWidth =
|
||||
EditorGUILayout.Slider(
|
||||
"Frustum Center Line Width",
|
||||
script.FrustumCenterLineWidth,
|
||||
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MIN,
|
||||
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager FrustumCenterLineWidth");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumCenterLineWidth = currentFrustumCenterLineWidth;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Color of frustum line
|
||||
var currentFrustumLineColor = EditorGUILayout.ColorField(
|
||||
"Frustum Line Color", script.FrustumLineColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager FrustumLineColor");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumLineColor = currentFrustumLineColor;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Color of frustum center line
|
||||
var currentFrustumCenterLineColor = EditorGUILayout.ColorField(
|
||||
"Frustum Center Line Color",
|
||||
script.FrustumCenterLineColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager FrustumCenterLineColor");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumCenterLineColor = currentFrustumCenterLineColor;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
r = new Rect(r.x, r.y, r.width, r.height);
|
||||
EditorGUI.DrawRect(r, HighlightRegionBackgroundColor);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Test 360 Output
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ Debug Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var currentSpectatorCameraViewMaterial = EditorGUILayout.ObjectField(
|
||||
"Spectator Camera View Material",
|
||||
script.SpectatorCameraViewMaterial,
|
||||
typeof(Material),
|
||||
false) as Material;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, "Modified SpectatorCameraManager SpectatorCameraViewMaterial");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.SpectatorCameraViewMaterial = currentSpectatorCameraViewMaterial;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox("Test - Output 360 photo", MessageType.Info, true);
|
||||
if (GUILayout.Button("Test - Output 360 photo"))
|
||||
{
|
||||
script.CaptureSpectatorCamera360Photo();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5a03d623c31c2344a1f99005572762a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fda46617f9e447e47863938d3006d5bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,374 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VIVE.OpenXR.Toolkits.Spectator.Helper;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator
|
||||
{
|
||||
/// <summary>
|
||||
/// Name: SpectatorCameraTracker.Editor.cs
|
||||
/// Role: General script use in Unity Editor only
|
||||
/// Responsibility: Display the SpectatorCameraTracker.cs in Unity Inspector
|
||||
/// </summary>
|
||||
public partial class SpectatorCameraTracker
|
||||
{
|
||||
[field: SerializeField] private bool IsRequireLoadJsonFile { get; set; }
|
||||
|
||||
[CustomEditor(typeof(SpectatorCameraTracker))]
|
||||
public class SpectatorCameraTrackerSettingEditor : UnityEditor.Editor
|
||||
{
|
||||
private SerializedProperty IsRequireLoadJsonFile { get; set; }
|
||||
private List<string> JsonFileList { get; set; }
|
||||
private Vector2 JsonFileScrollViewVector { get; set; }
|
||||
|
||||
private SerializedProperty IsSmoothCameraMovement { get; set; }
|
||||
private SerializedProperty SmoothCameraMovementSpeed { get; set; }
|
||||
private SerializedProperty PanoramaResolution { get; set; }
|
||||
private SerializedProperty PanoramaOutputFormat { get; set; }
|
||||
private SerializedProperty PanoramaOutputType { get; set; }
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
IsRequireLoadJsonFile =
|
||||
serializedObject.FindProperty(EditorHelper.PropertyName("IsRequireLoadJsonFile"));
|
||||
JsonFileList = new List<string>();
|
||||
JsonFileScrollViewVector = Vector2.zero;
|
||||
|
||||
IsSmoothCameraMovement =
|
||||
serializedObject.FindProperty(EditorHelper.PropertyName("IsSmoothCameraMovement"));
|
||||
SmoothCameraMovementSpeed =
|
||||
serializedObject.FindProperty(EditorHelper.PropertyName("SmoothCameraMovementSpeed"));
|
||||
PanoramaResolution = serializedObject.FindProperty(EditorHelper.PropertyName("PanoramaResolution"));
|
||||
PanoramaOutputFormat = serializedObject.FindProperty(EditorHelper.PropertyName("PanoramaOutputFormat"));
|
||||
PanoramaOutputType = serializedObject.FindProperty(EditorHelper.PropertyName("PanoramaOutputType"));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
// Just return if not "SpectatorCameraTrackerSetting" class
|
||||
if (!(target is SpectatorCameraTracker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
DrawGUI();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawGUI()
|
||||
{
|
||||
#region GUIStyle
|
||||
|
||||
var labelStyle = new GUIStyle()
|
||||
{
|
||||
richText = true,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
normal = new GUIStyleState
|
||||
{
|
||||
textColor = EditorGUIUtility.isProSkin ? Color.green : Color.black
|
||||
}
|
||||
};
|
||||
|
||||
var resetButtonStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fontStyle = FontStyle.Bold,
|
||||
normal = new GUIStyleState
|
||||
{
|
||||
textColor = EditorGUIUtility.isProSkin ? Color.yellow : Color.red
|
||||
},
|
||||
hover = new GUIStyleState
|
||||
{
|
||||
textColor = Color.red
|
||||
},
|
||||
active = new GUIStyleState
|
||||
{
|
||||
textColor = Color.cyan
|
||||
},
|
||||
};
|
||||
|
||||
var boldButtonStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
var script = (SpectatorCameraTracker)target;
|
||||
|
||||
// Button for reset value
|
||||
if (GUILayout.Button("Reset to default value", resetButtonStyle))
|
||||
{
|
||||
Undo.RecordObject(target, $"Reset {target.name} SpectatorCameraTracker to default value");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.ResetSetting();
|
||||
}
|
||||
|
||||
// Button for export setting
|
||||
if (GUILayout.Button("Export Spectator Camera Tracker Setting", boldButtonStyle))
|
||||
{
|
||||
script.ExportSetting2JsonFile(SpectatorCameraHelper.AttributeFileLocation.ResourceFolder);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
#region Load setting from JSON file
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
EditorGUI.BeginDisabledGroup(IsRequireLoadJsonFile.boolValue);
|
||||
if (GUILayout.Button("Load Setting From JSON File in Resources Folder", boldButtonStyle) ||
|
||||
IsRequireLoadJsonFile.boolValue)
|
||||
{
|
||||
IsRequireLoadJsonFile.boolValue = true;
|
||||
var searchPattern =
|
||||
$"{SpectatorCameraHelper.ATTRIBUTE_FILE_PREFIX_NAME}*.{SpectatorCameraHelper.ATTRIBUTE_FILE_EXTENSION}";
|
||||
|
||||
if (JsonFileList == null)
|
||||
{
|
||||
JsonFileList = new List<string>();
|
||||
}
|
||||
|
||||
JsonFileList.Clear();
|
||||
|
||||
var dir = new DirectoryInfo(Path.Combine(Application.dataPath, "Resources"));
|
||||
var files = dir.GetFiles(searchPattern);
|
||||
foreach (var item in files)
|
||||
{
|
||||
JsonFileList.Add(item.Name);
|
||||
}
|
||||
|
||||
if (JsonFileList.Count == 0)
|
||||
{
|
||||
Debug.Log(
|
||||
"Can't find any JSON file related to the spectator camera setting in the Resources folder.");
|
||||
IsRequireLoadJsonFile.boolValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (IsRequireLoadJsonFile.boolValue)
|
||||
{
|
||||
if (GUILayout.Button("Cancel"))
|
||||
{
|
||||
IsRequireLoadJsonFile.boolValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (IsRequireLoadJsonFile.boolValue)
|
||||
{
|
||||
Rect r = EditorGUILayout.BeginVertical();
|
||||
|
||||
JsonFileScrollViewVector = EditorGUILayout.BeginScrollView(JsonFileScrollViewVector,
|
||||
GUILayout.Width(r.width),
|
||||
GUILayout.Height(80));
|
||||
|
||||
for (int i = 0; i < JsonFileList.Count; i++)
|
||||
{
|
||||
if (GUILayout.Button(JsonFileList[i]))
|
||||
{
|
||||
var path = Path.Combine(
|
||||
Path.Combine(Application.dataPath, "Resources"),
|
||||
JsonFileList[i]);
|
||||
Undo.RecordObject(target, $"Load {JsonFileList[i]} setting to {target.name} SpectatorCameraTracker");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.LoadSettingFromJsonFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Setting related to spectator camera and its frustum show/hide
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ General Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Setting of spectator camera layer mask
|
||||
var currentLayerMask =
|
||||
LayerMaskHelper.LayerMaskDrawer.LayerMaskField("Camera Layer Mask", script.LayerMask);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker LayerMask");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.LayerMask = currentLayerMask;
|
||||
}
|
||||
|
||||
// Setting of smooth spectator camera movement
|
||||
EditorGUILayout.PropertyField(IsSmoothCameraMovement,
|
||||
new GUIContent("Enable Smoothing Camera Movement"));
|
||||
if (IsSmoothCameraMovement.boolValue)
|
||||
{
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ Smooth Camera Movement Speed Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUILayout.IntSlider(
|
||||
SmoothCameraMovementSpeed,
|
||||
SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_MIN,
|
||||
SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_MAX,
|
||||
"Speed of Smoothing Camera Movement");
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Spectator camera frustum show/hide
|
||||
var currentIsFrustumShowed =
|
||||
EditorGUILayout.Toggle("Enable Camera FOV Frustum", script.IsFrustumShowed);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker IsFrustumShowed");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.IsFrustumShowed = currentIsFrustumShowed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region VerticalFov
|
||||
|
||||
EditorGUILayout.LabelField("<b>[ Vertical FOV Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// FOV
|
||||
var currentVerticalFov = EditorGUILayout.Slider(
|
||||
"Vertical FOV",
|
||||
script.VerticalFov,
|
||||
SpectatorCameraHelper.VERTICAL_FOV_MIN,
|
||||
SpectatorCameraHelper.VERTICAL_FOV_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker VerticalFov");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.VerticalFov = currentVerticalFov;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Setting related to panorama capturing of spectator camera
|
||||
|
||||
// Panorama resolution
|
||||
EditorGUILayout.LabelField("<b>[ Panorama Setting ]</b>", labelStyle);
|
||||
EditorGUILayout.PropertyField(PanoramaResolution, new GUIContent("Resolution"));
|
||||
|
||||
// Panorama output format
|
||||
EditorGUILayout.PropertyField(PanoramaOutputFormat, new GUIContent("Output Format"));
|
||||
|
||||
// Panorama output type
|
||||
EditorGUILayout.PropertyField(PanoramaOutputType, new GUIContent("Output Type"));
|
||||
|
||||
#endregion
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
#region Setting related to frustum
|
||||
|
||||
if (script.IsFrustumShowed)
|
||||
{
|
||||
EditorGUILayout.LabelField("<b>[ Frustum Setting ]</b>", labelStyle);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Count of frustum line
|
||||
var currentFrustumLineCount = (SpectatorCameraHelper.FrustumLineCount)
|
||||
EditorGUILayout.EnumPopup("Frustum Line Total", script.FrustumLineCount);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker FrustumLineCount");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumLineCount = currentFrustumLineCount;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Count of frustum center line
|
||||
var currentFrustumCenterLineCount = (SpectatorCameraHelper.FrustumCenterLineCount)
|
||||
EditorGUILayout.EnumPopup("Frustum Center Line Total", script.FrustumCenterLineCount);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker FrustumCenterLineCount");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumCenterLineCount = currentFrustumCenterLineCount;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Width of frustum line
|
||||
var currentFrustumLineWidth =
|
||||
EditorGUILayout.Slider(
|
||||
"Frustum Line Width",
|
||||
script.FrustumLineWidth,
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MIN,
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker FrustumLineWidth");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumLineWidth = currentFrustumLineWidth;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Width of frustum center line
|
||||
var currentFrustumCenterLineWidth =
|
||||
EditorGUILayout.Slider(
|
||||
"Frustum Center Line Width",
|
||||
script.FrustumCenterLineWidth,
|
||||
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MIN,
|
||||
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MAX);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker FrustumCenterLineWidth");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumCenterLineWidth = currentFrustumCenterLineWidth;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Color of frustum line
|
||||
var currentFrustumLineColor = EditorGUILayout.ColorField(
|
||||
"Frustum Line Color", script.FrustumLineColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker FrustumLineColor");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumLineColor = currentFrustumLineColor;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// Color of frustum center line
|
||||
var currentFrustumCenterLineColor = EditorGUILayout.ColorField(
|
||||
"Frustum Center Line Color",
|
||||
script.FrustumCenterLineColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(target, $"Modified {target.name} SpectatorCameraTracker FrustumCenterLineColor");
|
||||
EditorUtility.SetDirty(target);
|
||||
script.FrustumCenterLineColor = currentFrustumCenterLineColor;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 523f24465def2a246b2f27dca663a530
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,479 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using VIVE.OpenXR.Toolkits.Spectator.Helper;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Spectator
|
||||
{
|
||||
/// <summary>
|
||||
/// Name: SpectatorCameraTracker.cs
|
||||
/// Role: General script
|
||||
/// Responsibility: To implement the spectator camera tracker setting and I/O function
|
||||
/// </summary>
|
||||
public partial class SpectatorCameraTracker : MonoBehaviour, ISpectatorCameraSetting
|
||||
{
|
||||
private const int TryAddTrackerIntervalSecond = 1;
|
||||
|
||||
private SpectatorCameraManager SpectatorCameraManager => SpectatorCameraManager.Instance;
|
||||
public SpectatorCameraHelper.CameraSourceRef CameraSourceRef => SpectatorCameraHelper.CameraSourceRef.Tracker;
|
||||
|
||||
public Vector3 Position
|
||||
{
|
||||
get => transform.position;
|
||||
set => transform.position = value;
|
||||
}
|
||||
|
||||
public Quaternion Rotation
|
||||
{
|
||||
get => transform.rotation;
|
||||
set => transform.rotation = value;
|
||||
}
|
||||
|
||||
[SerializeField] private LayerMask layerMask = SpectatorCameraHelper.LayerMaskDefault;
|
||||
|
||||
public LayerMask LayerMask
|
||||
{
|
||||
get => layerMask;
|
||||
set
|
||||
{
|
||||
if (layerMask == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
layerMask = value;
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SpectatorCameraBased.SpectatorCamera.cullingMask = layerMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[field: SerializeField]
|
||||
public bool IsSmoothCameraMovement { get; set; } = SpectatorCameraHelper.IS_SMOOTH_CAMERA_MOVEMENT_DEFAULT;
|
||||
|
||||
[field: SerializeField]
|
||||
public int SmoothCameraMovementSpeed { get; set; } = SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_SPEED_DEFAULT;
|
||||
|
||||
[SerializeField] private bool isFrustumShowed = SpectatorCameraHelper.IS_FRUSTUM_SHOWED_DEFAULT;
|
||||
|
||||
public bool IsFrustumShowed
|
||||
{
|
||||
get => isFrustumShowed;
|
||||
set
|
||||
{
|
||||
if (isFrustumShowed == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isFrustumShowed = value;
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SetupFrustum();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private float verticalFov = SpectatorCameraHelper.VERTICAL_FOV_DEFAULT;
|
||||
|
||||
public float VerticalFov
|
||||
{
|
||||
get => verticalFov;
|
||||
set
|
||||
{
|
||||
if (Math.Abs(verticalFov - value) < SpectatorCameraHelper.COMPARE_FLOAT_MEDIUM_THRESHOLD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
verticalFov = Mathf.Clamp(
|
||||
value,
|
||||
SpectatorCameraHelper.VERTICAL_FOV_MIN,
|
||||
SpectatorCameraHelper.VERTICAL_FOV_MAX);
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SpectatorCameraBased.SpectatorCamera.fieldOfView = verticalFov;
|
||||
SpectatorCameraManager.SetupFrustum();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Panorama properties
|
||||
|
||||
[field: SerializeField]
|
||||
public SpectatorCameraHelper.SpectatorCameraPanoramaResolution PanoramaResolution { get; set; } =
|
||||
SpectatorCameraHelper.PANORAMA_RESOLUTION_DEFAULT;
|
||||
|
||||
[field: SerializeField]
|
||||
public TextureProcessHelper.PictureOutputFormat PanoramaOutputFormat { get; set; } =
|
||||
SpectatorCameraHelper.PANORAMA_OUTPUT_FORMAT_DEFAULT;
|
||||
|
||||
[field: SerializeField]
|
||||
public TextureProcessHelper.PanoramaType PanoramaOutputType { get; set; } =
|
||||
SpectatorCameraHelper.PANORAMA_TYPE_DEFAULT;
|
||||
|
||||
#endregion
|
||||
|
||||
[SerializeField] private SpectatorCameraHelper.FrustumLineCount frustumLineCount =
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_COUNT_DEFAULT;
|
||||
|
||||
public SpectatorCameraHelper.FrustumLineCount FrustumLineCount
|
||||
{
|
||||
get => frustumLineCount;
|
||||
set
|
||||
{
|
||||
if (frustumLineCount == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
frustumLineCount = value;
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SetupFrustumLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private SpectatorCameraHelper.FrustumCenterLineCount frustumCenterLineCount =
|
||||
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_COUNT_DEFAULT;
|
||||
|
||||
public SpectatorCameraHelper.FrustumCenterLineCount FrustumCenterLineCount
|
||||
{
|
||||
get => frustumCenterLineCount;
|
||||
set
|
||||
{
|
||||
if (frustumCenterLineCount == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
frustumCenterLineCount = value;
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SetupFrustumCenterLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private float frustumLineWidth = SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_DEFAULT;
|
||||
|
||||
public float FrustumLineWidth
|
||||
{
|
||||
get => frustumLineWidth;
|
||||
set
|
||||
{
|
||||
if (Math.Abs(frustumLineWidth - value) < SpectatorCameraHelper.COMPARE_FLOAT_SUPER_SMALL_THRESHOLD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
frustumLineWidth = Mathf.Clamp(
|
||||
value,
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MIN,
|
||||
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MAX);
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SetupFrustumLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private float frustumCenterLineWidth = SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_DEFAULT;
|
||||
|
||||
public float FrustumCenterLineWidth
|
||||
{
|
||||
get => frustumCenterLineWidth;
|
||||
set
|
||||
{
|
||||
if (Math.Abs(frustumCenterLineWidth - value) <
|
||||
SpectatorCameraHelper.COMPARE_FLOAT_SUPER_SMALL_THRESHOLD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
frustumCenterLineWidth = Mathf.Clamp(value, SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MIN,
|
||||
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MAX);
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SetupFrustumCenterLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private Color frustumLineColor = SpectatorCameraHelper.LineColorDefault;
|
||||
|
||||
public Color FrustumLineColor
|
||||
{
|
||||
get => frustumLineColor;
|
||||
set
|
||||
{
|
||||
if (frustumLineColor == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
frustumLineColor = value;
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SetupFrustumLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private Color frustumCenterLineColor = SpectatorCameraHelper.LineColorDefault;
|
||||
|
||||
public Color FrustumCenterLineColor
|
||||
{
|
||||
get => frustumCenterLineColor;
|
||||
set
|
||||
{
|
||||
if (frustumCenterLineColor == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
frustumCenterLineColor = value;
|
||||
|
||||
if (Application.isPlaying &&
|
||||
SpectatorCameraManager != null &&
|
||||
SpectatorCameraManager.IsCameraSourceAsTracker() &&
|
||||
SpectatorCameraManager.IsFollowTrackerEqualTo(this))
|
||||
{
|
||||
SpectatorCameraManager.SetupFrustumCenterLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetSetting()
|
||||
{
|
||||
LayerMask = SpectatorCameraHelper.LayerMaskDefault;
|
||||
IsSmoothCameraMovement = SpectatorCameraHelper.IS_SMOOTH_CAMERA_MOVEMENT_DEFAULT;
|
||||
SmoothCameraMovementSpeed = SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_SPEED_DEFAULT;
|
||||
IsFrustumShowed = SpectatorCameraHelper.IS_FRUSTUM_SHOWED_DEFAULT;
|
||||
VerticalFov = SpectatorCameraHelper.VERTICAL_FOV_DEFAULT;
|
||||
PanoramaResolution = SpectatorCameraHelper.PANORAMA_RESOLUTION_DEFAULT;
|
||||
PanoramaOutputFormat = SpectatorCameraHelper.PANORAMA_OUTPUT_FORMAT_DEFAULT;
|
||||
PanoramaOutputType = SpectatorCameraHelper.PANORAMA_TYPE_DEFAULT;
|
||||
FrustumLineCount = SpectatorCameraHelper.FRUSTUM_LINE_COUNT_DEFAULT;
|
||||
FrustumCenterLineCount = SpectatorCameraHelper.FRUSTUM_CENTER_LINE_COUNT_DEFAULT;
|
||||
FrustumLineWidth = SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_DEFAULT;
|
||||
FrustumCenterLineWidth = SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_DEFAULT;
|
||||
FrustumLineColor = SpectatorCameraHelper.LineColorDefault;
|
||||
FrustumCenterLineColor = SpectatorCameraHelper.LineColorDefault;
|
||||
}
|
||||
|
||||
public void ExportSetting2JsonFile(in SpectatorCameraHelper.AttributeFileLocation attributeFileLocation)
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.ResourceFolder)
|
||||
{
|
||||
Debug.LogError("It's not allowed to save setting to resource folder in runtime mode");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
var data = new SpectatorCameraHelper.SpectatorCameraAttribute(
|
||||
CameraSourceRef,
|
||||
Position,
|
||||
Rotation,
|
||||
LayerMask,
|
||||
IsSmoothCameraMovement,
|
||||
SmoothCameraMovementSpeed,
|
||||
IsFrustumShowed,
|
||||
VerticalFov,
|
||||
PanoramaResolution,
|
||||
PanoramaOutputFormat,
|
||||
PanoramaOutputType,
|
||||
FrustumLineCount,
|
||||
FrustumCenterLineCount,
|
||||
FrustumLineWidth,
|
||||
FrustumCenterLineWidth,
|
||||
FrustumLineColor,
|
||||
FrustumCenterLineColor);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.ResourceFolder)
|
||||
{
|
||||
SpectatorCameraHelper.SaveAttributeData2ResourcesFolder(
|
||||
SceneManager.GetActiveScene().name,
|
||||
gameObject.name,
|
||||
data);
|
||||
}
|
||||
else if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.PersistentFolder)
|
||||
{
|
||||
SpectatorCameraHelper.SaveAttributeData2PersistentFolder(
|
||||
SceneManager.GetActiveScene().name,
|
||||
gameObject.name,
|
||||
data);
|
||||
}
|
||||
#else
|
||||
SpectatorCameraHelper.SaveAttributeData2PersistentFolder(
|
||||
SceneManager.GetActiveScene().name,
|
||||
gameObject.name,
|
||||
data);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void LoadSettingFromJsonFile(in string jsonFilePath)
|
||||
{
|
||||
bool loadSuccess = SpectatorCameraHelper.LoadAttributeFileFromFolder(
|
||||
jsonFilePath,
|
||||
out SpectatorCameraHelper.SpectatorCameraAttribute data);
|
||||
if (loadSuccess)
|
||||
{
|
||||
ApplyData(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Load setting from {jsonFilePath} file to scene gameObject {gameObject.name} failed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadSettingFromJsonFile(
|
||||
in string sceneName,
|
||||
in string trackerName,
|
||||
in SpectatorCameraHelper.AttributeFileLocation attributeFileLocation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sceneName) || string.IsNullOrEmpty(trackerName))
|
||||
{
|
||||
Debug.LogError("sceneName or trackerName is null or empty");
|
||||
return;
|
||||
}
|
||||
|
||||
var loadSuccess = false;
|
||||
SpectatorCameraHelper.SpectatorCameraAttribute data = new SpectatorCameraHelper.SpectatorCameraAttribute();
|
||||
if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.ResourceFolder)
|
||||
{
|
||||
loadSuccess = SpectatorCameraHelper.LoadAttributeFileFromResourcesFolder(
|
||||
sceneName,
|
||||
trackerName,
|
||||
out data);
|
||||
}
|
||||
else if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.PersistentFolder)
|
||||
{
|
||||
loadSuccess = SpectatorCameraHelper.LoadAttributeFileFromPersistentFolder(
|
||||
sceneName,
|
||||
trackerName,
|
||||
out data);
|
||||
}
|
||||
|
||||
if (loadSuccess)
|
||||
{
|
||||
ApplyData(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
var fileDirectory = string.Empty;
|
||||
if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.ResourceFolder)
|
||||
{
|
||||
fileDirectory = System.IO.Path.Combine(Application.dataPath, "Resources");
|
||||
}
|
||||
else if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.PersistentFolder)
|
||||
{
|
||||
fileDirectory = Application.persistentDataPath;
|
||||
}
|
||||
|
||||
var fileName =
|
||||
SpectatorCameraHelper.GetSpectatorCameraAttributeFileNamePattern(sceneName, trackerName);
|
||||
|
||||
Debug.Log(
|
||||
$"Load setting from {fileDirectory}/{fileName} file to scene gameObject {gameObject.name} failed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyData(in SpectatorCameraHelper.SpectatorCameraAttribute data)
|
||||
{
|
||||
Transform gameObjectTransform = transform;
|
||||
gameObjectTransform.position = data.position;
|
||||
gameObjectTransform.rotation = data.rotation;
|
||||
|
||||
LayerMask = data.layerMask;
|
||||
IsSmoothCameraMovement = data.isSmoothCameraMovement;
|
||||
SmoothCameraMovementSpeed = data.smoothCameraMovementSpeed;
|
||||
IsFrustumShowed = data.isFrustumShowed;
|
||||
VerticalFov = data.verticalFov;
|
||||
PanoramaResolution = data.panoramaResolution;
|
||||
PanoramaOutputFormat = data.panoramaOutputFormat;
|
||||
PanoramaOutputType = data.panoramaOutputType;
|
||||
FrustumLineCount = data.frustumLineCount;
|
||||
FrustumCenterLineCount = data.frustumCenterLineCount;
|
||||
FrustumLineWidth = data.frustumLineWidth;
|
||||
FrustumCenterLineWidth = data.frustumCenterLineWidth;
|
||||
FrustumLineColor = data.frustumLineColor;
|
||||
FrustumCenterLineColor = data.frustumCenterLineColor;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (SpectatorCameraManager != null)
|
||||
{
|
||||
SpectatorCameraManager.AddSpectatorCameraTracker(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
InvokeRepeating(nameof(AddTracker), TryAddTrackerIntervalSecond, TryAddTrackerIntervalSecond);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (SpectatorCameraManager != null)
|
||||
{
|
||||
SpectatorCameraManager.AddSpectatorCameraTracker(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (SpectatorCameraManager != null)
|
||||
{
|
||||
SpectatorCameraManager.RemoveSpectatorCameraTracker(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTracker()
|
||||
{
|
||||
if (SpectatorCameraManager != null)
|
||||
{
|
||||
SpectatorCameraManager.AddSpectatorCameraTracker(this);
|
||||
CancelInvoke(nameof(AddTracker));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0b61457a6a405c49b000f7a3ed895bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user