version 2.5.0

This commit is contained in:
Sean Lu
2025-01-10 17:17:03 +08:00
parent ddc3c4c6d8
commit 2372c9429a
1086 changed files with 290974 additions and 77367 deletions

View File

@@ -0,0 +1,180 @@
# XR_HTC_anchor XR_HTC_anchor_persistence
## Name String
XR_htc_anchor XR_HTC_anchor_persistence
## Revision
1
## Overview
This document provides an overview of how to use the AnchorManager to manage anchors in an OpenXR application, specifically using the XR_HTC_anchor and XR_HTC_anchor_persistence extensions.
Introduction
Anchors in OpenXR allow applications to track specific points in space over time. The XR_HTC_anchor extension provides the basic functionality for creating and managing anchors, while the XR_HTC_anchor_persistence extension allows anchors to be persisted across sessions. The AnchorManager class simplifies the use of these extensions by providing high-level methods for common operations.
Checking Extension Support
Before using any anchor-related functions, it's important to check if the extensions are supported on the current system.
```csharp
bool isAnchorSupported = AnchorManager.IsSupported();
bool isPersistedAnchorSupported = AnchorManager.IsPersistedAnchorSupported();
```
## Creating and Managing Anchors
### Creating an Anchor
To create a new anchor, use the CreateAnchor method. This method requires a Pose representing the anchor's position and orientation relative to the tracking space, and a name for the anchor.
```csharp
Pose anchorPose = new Pose(new Vector3(0, 0, 0), Quaternion.identity);
AnchorManager.Anchor newAnchor = AnchorManager.CreateAnchor(anchorPose, "MyAnchor");
```
### Getting an Anchor's Name
To retrieve the name of an existing anchor, use the GetSpatialAnchorName method.
```csharp
string anchorName;
bool success = AnchorManager.GetSpatialAnchorName(newAnchor, out anchorName);
if (success) {
Debug.Log("Anchor name: " + anchorName);
}
```
### Tracking Space and Pose
To get the current tracking space, use the GetTrackingSpace method. To retrieve the pose of an anchor relative to the current tracking space, use the GetTrackingSpacePose method.
```csharp
XrSpace trackingSpace = AnchorManager.GetTrackingSpace();
Pose anchorPose;
bool poseValid = AnchorManager.GetTrackingSpacePose(newAnchor, out anchorPose);
if (poseValid) {
Debug.Log("Anchor pose: " + anchorPose.position + ", " + anchorPose.rotation);
}
```
## Persisting Anchors
### Creating a Persisted Anchor Collection
To enable anchor persistence, create a persisted anchor collection using the CreatePersistedAnchorCollection method.
```csharp
Task createCollectionTask = AnchorManager.CreatePersistedAnchorCollection();
createCollectionTask.Wait();
```
### Persisting an Anchor
To persist an anchor, use the PersistAnchor method with the anchor and a unique name for the persisted anchor.
```csharp
string persistedAnchorName = "MyPersistedAnchor";
XrResult result = AnchorManager.PersistAnchor(newAnchor, persistedAnchorName);
if (result == XrResult.XR_SUCCESS) {
Debug.Log("Anchor persisted successfully.");
}
```
### Unpersisting an Anchor
To remove a persisted anchor, use the UnpersistAnchor method with the name of the persisted anchor.
```csharp
XrResult result = AnchorManager.UnpersistAnchor(persistedAnchorName);
if (result == XrResult.XR_SUCCESS) {
Debug.Log("Anchor unpersisted successfully.");
}
```
### Enumerating Persisted Anchors
To get a list of all persisted anchors, use the EnumeratePersistedAnchorNames method.
```csharp
string[] persistedAnchorNames;
XrResult result = AnchorManager.EnumeratePersistedAnchorNames(out persistedAnchorNames);
if (result == XrResult.XR_SUCCESS) {
foreach (var name in persistedAnchorNames) {
Debug.Log("Persisted anchor: " + name);
}
}
```
### Creating an Anchor from a Persisted Anchor
To create an anchor from a persisted anchor, use the CreateSpatialAnchorFromPersistedAnchor method.
```csharp
AnchorManager.Anchor trackableAnchor;
XrResult result = AnchorManager.CreateSpatialAnchorFromPersistedAnchor(persistedAnchorName, "NewAnchor", out trackableAnchor);
if (result == XrResult.XR_SUCCESS) {
Debug.Log("Anchor created from persisted anchor.");
}
```
## Exporting and Importing Persisted Anchors
### Exporting a Persisted Anchor
To export a persisted anchor to a buffer, use the ExportPersistedAnchor method.
```csharp
Task<(XrResult, string, byte[])> exportTask = AnchorManager.ExportPersistedAnchor(persistedAnchorName);
exportTask.Wait();
var (exportResult, exportName, buffer) = exportTask.Result;
if (exportResult == XrResult.XR_SUCCESS) {
// Save buffer to a file or use as needed
File.WriteAllBytes("anchor.pa", buffer);
}
```
### Importing a Persisted Anchor
To import a persisted anchor from a buffer, use the ImportPersistedAnchor method.
```csharp
byte[] buffer = File.ReadAllBytes("anchor.pa");
Task<XrResult> importTask = AnchorManager.ImportPersistedAnchor(buffer);
importTask.Wait();
if (importTask.Result == XrResult.XR_SUCCESS) {
Debug.Log("Anchor imported successfully.");
}
```
### Clearing Persisted Anchors
To clear all persisted anchors, use the ClearPersistedAnchors method.
```csharp
XrResult result = AnchorManager.ClearPersistedAnchors();
if (result == XrResult.XR_SUCCESS) {
Debug.Log("All persisted anchors cleared.");
}
```
## Conclusion
The AnchorManager class simplifies the management of anchors in OpenXR applications. By using the methods provided, you can easily create, persist, and manage anchors, ensuring that spatial data can be maintained across sessions. This document covers the basic operations; for more advanced usage, refer to the OpenXR specification and the implementation details of the AnchorManager class.

View File

@@ -1,4 +1,4 @@
// Copyright HTC Corporation All Rights Reserved.
// Copyright HTC Corporation All Rights Reserved.
// Remove FAKE_DATA if editor or windows is supported.
#if UNITY_EDITOR
@@ -10,222 +10,666 @@ using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using VIVE.OpenXR.Feature;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.Anchor
namespace VIVE.OpenXR.Feature
{
using XrPersistedAnchorCollectionHTC = System.IntPtr;
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Anchor",
Desc = "VIVE's implementaion of the XR_HTC_anchor.",
Company = "HTC",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = kOpenxrExtensionString,
Version = "1.0.0",
BuildTargetGroups = new[] { BuildTargetGroup.Android },
FeatureId = featureId
)]
[OpenXRFeature(UiName = "VIVE XR Anchor (Beta)",
Desc = "VIVE's implementaion of the XR_HTC_anchor.",
Company = "HTC",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = kOpenxrExtensionString,
Version = "1.0.0",
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
FeatureId = featureId
)]
#endif
public class ViveAnchor : OpenXRFeature
{
public const string kOpenxrExtensionString = "XR_HTC_anchor";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.wave.openxr.feature.htcanchor";
private XrInstance m_XrInstance = 0;
private XrSession session = 0;
private XrSystemId m_XrSystemId = 0;
public class ViveAnchor : OpenXRFeature
{
public const string kOpenxrExtensionString = "XR_HTC_anchor XR_EXT_future XR_HTC_anchor_persistence";
#region struct, enum, const of this extensions
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.htcanchor";
public struct XrSystemAnchorPropertiesHTC
{
public XrStructureType type;
public System.IntPtr next;
public XrBool32 supportsAnchor;
}
/// <summary>
/// Enable or disable the persisted anchor feature. Set it only valid in feature settings.
/// </summary>
public bool enablePersistedAnchor = true;
private XrInstance m_XrInstance = 0;
private XrSession session = 0;
private XrSystemId m_XrSystemId = 0;
private bool IsInited = false;
private bool IsPAInited = false;
private bool useFakeData = false;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct XrSpatialAnchorNameHTC
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string name;
}
#region struct, enum, const of this extensions
public struct XrSpatialAnchorCreateInfoHTC
{
public XrStructureType type;
public System.IntPtr next;
public XrSpace space;
public XrPosef poseInSpace;
public XrSpatialAnchorNameHTC name;
}
/// <summary>
/// An application can inspect whether the system is capable of anchor functionality by
/// chaining an XrSystemAnchorPropertiesHTC structure to the XrSystemProperties when calling
/// xrGetSystemProperties.The runtime must return XR_ERROR_FEATURE_UNSUPPORTED if
/// XrSystemAnchorPropertiesHTC::supportsAnchor was XR_FALSE.
/// supportsAnchor indicates if current system is capable of anchor functionality.
/// </summary>
public struct XrSystemAnchorPropertiesHTC
{
public XrStructureType type;
public System.IntPtr next;
public XrBool32 supportsAnchor;
}
#endregion
/// <summary>
/// name is a null-terminated UTF-8 string whose length is less than or equal to XR_MAX_SPATIAL_ANCHOR_NAME_SIZE_HTC.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrSpatialAnchorNameHTC
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public byte[] name;
#region delegates and delegate instances
delegate XrResult DelegateXrCreateSpatialAnchorHTC(XrSession session, ref XrSpatialAnchorCreateInfoHTC createInfo, ref XrSpace anchor);
delegate XrResult DelegateXrGetSpatialAnchorNameHTC(XrSpace anchor, ref XrSpatialAnchorNameHTC name);
public XrSpatialAnchorNameHTC(string anchorName)
{
name = new byte[256];
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(anchorName);
Array.Copy(utf8Bytes, name, Math.Min(utf8Bytes.Length, 255));
name[255] = 0;
}
DelegateXrCreateSpatialAnchorHTC XrCreateSpatialAnchorHTC;
DelegateXrGetSpatialAnchorNameHTC XrGetSpatialAnchorNameHTC;
#endregion delegates and delegate instances
public XrSpatialAnchorNameHTC(XrSpatialAnchorNameHTC anchorName)
{
name = new byte[256];
Array.Copy(anchorName.name, name, 256);
name[255] = 0;
}
#region override functions
/// <inheritdoc />
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
Debug.Log("ViveAnchor HookGetInstanceProcAddr() ");
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
}
public override readonly string ToString() {
if (name == null)
return string.Empty;
return System.Text.Encoding.UTF8.GetString(name).TrimEnd('\0');
}
}
/// <inheritdoc />
protected override bool OnInstanceCreate(ulong xrInstance)
{
//Debug.Log("VIVEAnchor OnInstanceCreate() ");
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
Debug.LogWarning("ViveAnchor OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled.");
return false;
}
public struct XrSpatialAnchorCreateInfoHTC
{
public XrStructureType type;
public System.IntPtr next;
public XrSpace space;
public XrPosef poseInSpace;
public XrSpatialAnchorNameHTC name;
}
m_XrInstance = xrInstance;
//Debug.Log("OnInstanceCreate() " + m_XrInstance);
CommonWrapper.Instance.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr);
SpaceWrapper.Instance.OnInstanceCreate(xrInstance, CommonWrapper.Instance.GetInstanceProcAddr);
public struct XrPersistedAnchorCollectionAcquireInfoHTC
{
public XrStructureType type;
public System.IntPtr next;
}
return GetXrFunctionDelegates(m_XrInstance);
}
public struct XrPersistedAnchorCollectionAcquireCompletionHTC
{
public XrStructureType type;
public System.IntPtr next;
public XrResult futureResult;
public System.IntPtr persistedAnchorCollection;
}
protected override void OnInstanceDestroy(ulong xrInstance)
{
CommonWrapper.Instance.OnInstanceDestroy();
SpaceWrapper.Instance.OnInstanceDestroy();
}
public struct XrSpatialAnchorPersistInfoHTC
{
public XrStructureType type;
public System.IntPtr next;
public XrSpace anchor;
public XrSpatialAnchorNameHTC persistedAnchorName;
}
/// <inheritdoc />
protected override void OnSessionCreate(ulong xrSession)
{
Debug.Log("ViveAnchor OnSessionCreate() ");
public struct XrSpatialAnchorFromPersistedAnchorCreateInfoHTC
{
public XrStructureType type;
public System.IntPtr next;
public System.IntPtr persistedAnchorCollection;
public XrSpatialAnchorNameHTC persistedAnchorName;
public XrSpatialAnchorNameHTC spatialAnchorName;
}
// here's one way you can grab the session
Debug.Log($"EXT: Got xrSession: {xrSession}");
session = xrSession;
}
public struct XrSpatialAnchorFromPersistedAnchorCreateCompletionHTC
{
public XrStructureType type;
public System.IntPtr next;
public XrResult futureResult;
public XrSpace anchor;
}
/// <inheritdoc />
protected override void OnSessionBegin(ulong xrSession)
{
Debug.Log("ViveAnchor OnSessionBegin() ");
Debug.Log($"EXT: xrBeginSession: {xrSession}");
}
public struct XrPersistedAnchorPropertiesGetInfoHTC
{
public XrStructureType type;
public System.IntPtr next;
public uint maxPersistedAnchorCount;
}
/// <inheritdoc />
protected override void OnSessionEnd(ulong xrSession)
{
Debug.Log("ViveAnchor OnSessionEnd() ");
Debug.Log($"EXT: about to xrEndSession: {xrSession}");
}
#endregion
// XXX Every millisecond the AppSpace switched from one space to another space. I don't know what is going on.
//private ulong appSpace;
//protected override void OnAppSpaceChange(ulong space)
//{
// //Debug.Log($"VIVEAnchor OnAppSpaceChange({appSpace} -> {space})");
// appSpace = space;
//}
#region delegates and delegate instances
public delegate XrResult DelegateXrCreateSpatialAnchorHTC(XrSession session, ref XrSpatialAnchorCreateInfoHTC createInfo, ref XrSpace anchor);
public delegate XrResult DelegateXrGetSpatialAnchorNameHTC(XrSpace anchor, ref XrSpatialAnchorNameHTC name);
public delegate XrResult DelegateXrAcquirePersistedAnchorCollectionAsyncHTC(XrSession session, ref XrPersistedAnchorCollectionAcquireInfoHTC acquireInfo, out IntPtr future);
public delegate XrResult DelegateXrAcquirePersistedAnchorCollectionCompleteHTC(IntPtr future, out XrPersistedAnchorCollectionAcquireCompletionHTC completion);
public delegate XrResult DelegateXrReleasePersistedAnchorCollectionHTC(IntPtr persistedAnchorCollection);
public delegate XrResult DelegateXrPersistSpatialAnchorAsyncHTC(XrPersistedAnchorCollectionHTC persistedAnchorCollection, ref XrSpatialAnchorPersistInfoHTC persistInfo, out IntPtr future);
public delegate XrResult DelegateXrPersistSpatialAnchorCompleteHTC(IntPtr future, out FutureWrapper.XrFutureCompletionEXT completion);
public delegate XrResult DelegateXrUnpersistSpatialAnchorHTC(IntPtr persistedAnchorCollection, ref XrSpatialAnchorNameHTC persistedAnchorName);
public delegate XrResult DelegateXrEnumeratePersistedAnchorNamesHTC( IntPtr persistedAnchorCollection, uint persistedAnchorNameCapacityInput, ref uint persistedAnchorNameCountOutput, [Out] XrSpatialAnchorNameHTC[] persistedAnchorNames);
public delegate XrResult DelegateXrCreateSpatialAnchorFromPersistedAnchorAsyncHTC(XrSession session, ref XrSpatialAnchorFromPersistedAnchorCreateInfoHTC spatialAnchorCreateInfo, out IntPtr future);
public delegate XrResult DelegateXrCreateSpatialAnchorFromPersistedAnchorCompleteHTC(IntPtr future, out XrSpatialAnchorFromPersistedAnchorCreateCompletionHTC completion);
public delegate XrResult DelegateXrClearPersistedAnchorsHTC(IntPtr persistedAnchorCollection);
public delegate XrResult DelegateXrGetPersistedAnchorPropertiesHTC(IntPtr persistedAnchorCollection, ref XrPersistedAnchorPropertiesGetInfoHTC getInfo);
public delegate XrResult DelegateXrExportPersistedAnchorHTC(IntPtr persistedAnchorCollection, ref XrSpatialAnchorNameHTC persistedAnchorName, uint dataCapacityInput, ref uint dataCountOutput, [Out] byte[] data);
public delegate XrResult DelegateXrImportPersistedAnchorHTC(IntPtr persistedAnchorCollection, uint dataCount, [In] byte[] data);
public delegate XrResult DelegateXrGetPersistedAnchorNameFromBufferHTC(IntPtr persistedAnchorCollection, uint bufferCount, byte[] buffer, ref XrSpatialAnchorNameHTC name);
/// <inheritdoc />
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
Debug.Log("ViveAnchor OnSystemChange() " + m_XrSystemId);
}
DelegateXrCreateSpatialAnchorHTC XrCreateSpatialAnchorHTC;
DelegateXrGetSpatialAnchorNameHTC XrGetSpatialAnchorNameHTC;
DelegateXrAcquirePersistedAnchorCollectionAsyncHTC XrAcquirePersistedAnchorCollectionAsyncHTC;
DelegateXrAcquirePersistedAnchorCollectionCompleteHTC XrAcquirePersistedAnchorCollectionCompleteHTC;
DelegateXrReleasePersistedAnchorCollectionHTC XrReleasePersistedAnchorCollectionHTC;
DelegateXrPersistSpatialAnchorAsyncHTC XrPersistSpatialAnchorAsyncHTC;
DelegateXrPersistSpatialAnchorCompleteHTC XrPersistSpatialAnchorCompleteHTC;
DelegateXrUnpersistSpatialAnchorHTC XrUnpersistSpatialAnchorHTC;
DelegateXrEnumeratePersistedAnchorNamesHTC XrEnumeratePersistedAnchorNamesHTC;
DelegateXrCreateSpatialAnchorFromPersistedAnchorAsyncHTC XrCreateSpatialAnchorFromPersistedAnchorAsyncHTC;
DelegateXrCreateSpatialAnchorFromPersistedAnchorCompleteHTC XrCreateSpatialAnchorFromPersistedAnchorCompleteHTC;
DelegateXrClearPersistedAnchorsHTC XrClearPersistedAnchorsHTC;
DelegateXrGetPersistedAnchorPropertiesHTC XrGetPersistedAnchorPropertiesHTC;
DelegateXrExportPersistedAnchorHTC XrExportPersistedAnchorHTC;
DelegateXrImportPersistedAnchorHTC XrImportPersistedAnchorHTC;
DelegateXrGetPersistedAnchorNameFromBufferHTC XrGetPersistedAnchorNameFromBufferHTC;
#endregion delegates and delegate instances
#endregion override functions
#region override functions
private bool GetXrFunctionDelegates(XrInstance xrInstance)
{
Debug.Log("ViveAnchor GetXrFunctionDelegates() ");
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
// For LocateSpace, need WaitFrame's predictedDisplayTime.
ViveInterceptors.Instance.AddRequiredFunction("xrWaitFrame");
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
}
bool ret = true;
IntPtr funcPtr = IntPtr.Zero;
OpenXRHelper.xrGetInstanceProcAddrDelegate GetAddr = CommonWrapper.Instance.GetInstanceProcAddr; // shorter name
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrCreateSpatialAnchorHTC", out XrCreateSpatialAnchorHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrGetSpatialAnchorNameHTC", out XrGetSpatialAnchorNameHTC);
/// <inheritdoc />
protected override bool OnInstanceCreate(ulong xrInstance)
{
#if FAKE_DATA
Debug.LogError("ViveAnchor OnInstanceCreate() Use FakeData");
useFakeData = true;
#endif
IsInited = false;
bool ret = true;
ret &= CommonWrapper.Instance.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr);
ret &= SpaceWrapper.Instance.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr);
return ret;
}
if (!ret)
{
Debug.LogError("ViveAnchor OnInstanceCreate() failed.");
return false;
}
#region functions of extension
/// <summary>
/// Helper function to get this feature' properties.
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystemProperties">xrGetSystemProperties</see>
/// </summary>
public XrResult GetProperties(out XrSystemAnchorPropertiesHTC anchorProperties)
{
anchorProperties = new XrSystemAnchorPropertiesHTC();
anchorProperties.type = XrStructureType.XR_TYPE_SYSTEM_ANCHOR_PROPERTIES_HTC;
//Debug.Log("VIVEAnchor OnInstanceCreate() ");
if (!OpenXRRuntime.IsExtensionEnabled("XR_HTC_anchor") && !useFakeData)
{
Debug.LogWarning("ViveAnchor OnInstanceCreate() XR_HTC_anchor is NOT enabled.");
return false;
}
IsInited = GetXrFunctionDelegates(xrInstance);
if (!IsInited)
{
Debug.LogError("ViveAnchor OnInstanceCreate() failed to get function delegates.");
return false;
}
m_XrInstance = xrInstance;
bool hasFuture = FutureWrapper.Instance.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr);
// No error log because future will print.
#if FAKE_DATA
hasFuture = true;
#endif
IsPAInited = false;
bool hasPersistedAnchor = false;
do
{
if (!hasFuture)
{
Debug.LogWarning("ViveAnchor OnInstanceCreate() XR_HTC_anchor_persistence is NOT enabled because no XR_EXT_future.");
hasPersistedAnchor = false;
break;
}
hasPersistedAnchor = enablePersistedAnchor && OpenXRRuntime.IsExtensionEnabled("XR_HTC_anchor_persistence");
#if FAKE_DATA
hasPersistedAnchor = enablePersistedAnchor;
#endif
} while(false);
//Debug.Log("OnInstanceCreate() " + m_XrInstance);
if (hasPersistedAnchor)
IsPAInited = GetXrFunctionDelegatesPersistance(xrInstance);
if (!IsPAInited)
Debug.LogWarning("ViveAnchor OnInstanceCreate() XR_HTC_anchor_persistence is NOT enabled.");
return IsInited;
}
protected override void OnInstanceDestroy(ulong xrInstance)
{
m_XrInstance = 0;
IsInited = false;
IsPAInited = false;
CommonWrapper.Instance.OnInstanceDestroy();
SpaceWrapper.Instance.OnInstanceDestroy();
FutureWrapper.Instance.OnInstanceDestroy();
Debug.Log("ViveAnchor: OnInstanceDestroy()");
}
/// <inheritdoc />
protected override void OnSessionCreate(ulong xrSession)
{
//Debug.Log("ViveAnchor OnSessionCreate() ");
session = xrSession;
}
/// <inheritdoc />
protected override void OnSessionDestroy(ulong xrSession)
{
//Debug.Log("ViveAnchor OnSessionDestroy() ");
session = 0;
}
// XXX Every millisecond the AppSpace switched from one space to another space. I don't know what is going on.
//private ulong appSpace;
//protected override void OnAppSpaceChange(ulong space)
//{
// //Debug.Log($"VIVEAnchor OnAppSpaceChange({appSpace} -> {space})");
// appSpace = space;
//}
/// <inheritdoc />
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
//Debug.Log("ViveAnchor OnSystemChange() " + m_XrSystemId);
}
#endregion override functions
private bool GetXrFunctionDelegates(XrInstance inst)
{
Debug.Log("ViveAnchor GetXrFunctionDelegates() ");
bool ret = true;
OpenXRHelper.xrGetInstanceProcAddrDelegate GetAddr = CommonWrapper.Instance.GetInstanceProcAddr; // shorter name
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrCreateSpatialAnchorHTC", out XrCreateSpatialAnchorHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrGetSpatialAnchorNameHTC", out XrGetSpatialAnchorNameHTC);
return ret;
}
private bool GetXrFunctionDelegatesPersistance(XrInstance inst)
{
Debug.Log("ViveAnchor GetXrFunctionDelegatesPersistance() ");
bool ret = true;
OpenXRHelper.xrGetInstanceProcAddrDelegate GetAddr = CommonWrapper.Instance.GetInstanceProcAddr; // shorter name
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrAcquirePersistedAnchorCollectionAsyncHTC", out XrAcquirePersistedAnchorCollectionAsyncHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrAcquirePersistedAnchorCollectionCompleteHTC", out XrAcquirePersistedAnchorCollectionCompleteHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrReleasePersistedAnchorCollectionHTC", out XrReleasePersistedAnchorCollectionHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrPersistSpatialAnchorAsyncHTC", out XrPersistSpatialAnchorAsyncHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrPersistSpatialAnchorCompleteHTC", out XrPersistSpatialAnchorCompleteHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrUnpersistSpatialAnchorHTC", out XrUnpersistSpatialAnchorHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrEnumeratePersistedAnchorNamesHTC", out XrEnumeratePersistedAnchorNamesHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrCreateSpatialAnchorFromPersistedAnchorAsyncHTC", out XrCreateSpatialAnchorFromPersistedAnchorAsyncHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrCreateSpatialAnchorFromPersistedAnchorCompleteHTC", out XrCreateSpatialAnchorFromPersistedAnchorCompleteHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrClearPersistedAnchorsHTC", out XrClearPersistedAnchorsHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrGetPersistedAnchorPropertiesHTC", out XrGetPersistedAnchorPropertiesHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrExportPersistedAnchorHTC", out XrExportPersistedAnchorHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrImportPersistedAnchorHTC", out XrImportPersistedAnchorHTC);
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, inst, "xrGetPersistedAnchorNameFromBufferHTC", out XrGetPersistedAnchorNameFromBufferHTC);
return ret;
}
#region functions of extension
/// <summary>
/// Helper function to get this feature's properties.
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystemProperties">xrGetSystemProperties</see>
/// </summary>
/// <param name="anchorProperties">Output parameter to hold anchor properties.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult GetProperties(out XrSystemAnchorPropertiesHTC anchorProperties)
{
anchorProperties = new XrSystemAnchorPropertiesHTC();
anchorProperties.type = XrStructureType.XR_TYPE_SYSTEM_ANCHOR_PROPERTIES_HTC;
#if FAKE_DATA
if (Application.isEditor)
{
anchorProperties.type = XrStructureType.XR_TYPE_SYSTEM_ANCHOR_PROPERTIES_HTC;
anchorProperties.supportsAnchor = true;
return XrResult.XR_SUCCESS;
}
if (Application.isEditor)
{
anchorProperties.type = XrStructureType.XR_TYPE_SYSTEM_ANCHOR_PROPERTIES_HTC;
anchorProperties.supportsAnchor = true;
return XrResult.XR_SUCCESS;
}
#endif
return CommonWrapper.Instance.GetProperties(m_XrInstance, m_XrSystemId, ref anchorProperties);
}
return CommonWrapper.Instance.GetProperties(m_XrInstance, m_XrSystemId, ref anchorProperties);
}
public XrResult CreateSpatialAnchor(XrSpatialAnchorCreateInfoHTC createInfo, out XrSpace anchor)
{
anchor = default;
#if FAKE_DATA
if (Application.isEditor)
return XrResult.XR_SUCCESS;
#endif
var ret = XrCreateSpatialAnchorHTC(session, ref createInfo, ref anchor);
Debug.Log("ViveAnchor CreateSpatialAnchor() r=" + ret + ", a=" + anchor + ", bs=" + createInfo.space +
", pos=(" + createInfo.poseInSpace.position.x + "," + createInfo.poseInSpace.position.y + "," + createInfo.poseInSpace.position.z +
"), rot=(" + createInfo.poseInSpace.orientation.x + "," + createInfo.poseInSpace.orientation.y + "," + createInfo.poseInSpace.orientation.z + "," + createInfo.poseInSpace.orientation.w +
"), n=" + createInfo.name.name);
return ret;
}
/// <summary>
/// The CreateSpatialAnchor function creates a spatial anchor with specified base space and pose in the space.
/// The anchor is represented by an XrSpace and its pose can be tracked via xrLocateSpace.
/// Once the anchor is no longer needed, call xrDestroySpace to erase the anchor.
/// </summary>
/// <param name="createInfo">Information required to create the spatial anchor.</param>
/// <param name="anchor">Output parameter to hold the created anchor.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult CreateSpatialAnchor(XrSpatialAnchorCreateInfoHTC createInfo, out XrSpace anchor)
{
anchor = default;
if (!IsInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
if (session == 0)
return XrResult.XR_ERROR_SESSION_LOST;
public XrResult GetSpatialAnchorName(XrSpace anchor, out XrSpatialAnchorNameHTC name)
{
name = default;
#if FAKE_DATA
if (Application.isEditor)
{
name.name = "fake anchor";
return XrResult.XR_SUCCESS;
}
#endif
return XrGetSpatialAnchorNameHTC(anchor, ref name);
}
var ret = XrCreateSpatialAnchorHTC(session, ref createInfo, ref anchor);
//Debug.Log("ViveAnchor CreateSpatialAnchor() r=" + ret + ", a=" + anchor + ", bs=" + createInfo.space +
// ", pos=(" + createInfo.poseInSpace.position.x + "," + createInfo.poseInSpace.position.y + "," + createInfo.poseInSpace.position.z +
// "), rot=(" + createInfo.poseInSpace.orientation.x + "," + createInfo.poseInSpace.orientation.y + "," + createInfo.poseInSpace.orientation.z + "," + createInfo.poseInSpace.orientation.w +
// "), n=" + createInfo.name.name);
return ret;
}
#endregion
/// <summary>
/// The GetSpatialAnchorName function retrieves 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>XrResult indicating success or failure.</returns>
public XrResult GetSpatialAnchorName(XrSpace anchor, out XrSpatialAnchorNameHTC name)
{
name = new XrSpatialAnchorNameHTC();
if (!IsInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrGetSpatialAnchorNameHTC(anchor, ref name);
}
#region tools for user
/// <summary>
/// If the extension is supported and enabled, return true.
/// </summary>
/// <returns>True if persisted anchor extension is supported, false otherwise.</returns>
public bool IsPersistedAnchorSupported()
{
return IsPAInited;
}
/// <summary>
/// According to XRInputSubsystem's tracking origin mode, return the corresponding XrSpace.
/// </summary>
/// <returns></returns>
public XrSpace GetTrackingSpace()
{
var s = GetCurrentAppSpace();
Debug.Log("ViveAnchor GetTrackingSpace() s=" + s);
return s;
}
#endregion
}
}
/// <summary>
/// Creates a persisted anchor collection. This collection can be used to persist spatial anchors across sessions.
/// Many persisted anchor APIs need a persisted anchor collection to operate.
/// </summary>
/// <param name="future">Output the async future handle. Check the future to get the PersitedAnchorCollection handle.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult AcquirePersistedAnchorCollectionAsync(out IntPtr future)
{
future = IntPtr.Zero;
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
if (session == 0)
return XrResult.XR_ERROR_SESSION_LOST;
XrPersistedAnchorCollectionAcquireInfoHTC acquireInfo = new XrPersistedAnchorCollectionAcquireInfoHTC
{
type = XrStructureType.XR_TYPE_PERSISTED_ANCHOR_COLLECTION_ACQUIRE_INFO_HTC,
next = IntPtr.Zero,
};
return XrAcquirePersistedAnchorCollectionAsyncHTC(session, ref acquireInfo, out future);
}
public XrResult AcquirePersistedAnchorCollectionComplete(IntPtr future, out XrPersistedAnchorCollectionAcquireCompletionHTC completion)
{
completion = new XrPersistedAnchorCollectionAcquireCompletionHTC();
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrAcquirePersistedAnchorCollectionCompleteHTC(future, out completion);
}
/// <summary>
/// Destroys the persisted anchor collection.
/// </summary>
/// <param name="persistedAnchorCollection">The persisted anchor collection to be destroyed.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult ReleasePersistedAnchorCollection(IntPtr persistedAnchorCollection)
{
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrReleasePersistedAnchorCollectionHTC(persistedAnchorCollection);
}
/// <summary>
/// Persists a spatial anchor with the given name. The name should be unique.
/// </summary>
/// <param name="persistedAnchorCollection">The persisted anchor collection to operate.</param>
/// <param name="anchor">The spatial anchor to be persisted.</param>
/// <param name="name">The name of the persisted anchor.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult PersistSpatialAnchorAsync(IntPtr persistedAnchorCollection, XrSpace anchor, XrSpatialAnchorNameHTC name, out IntPtr future)
{
future = IntPtr.Zero;
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
XrSpatialAnchorPersistInfoHTC persistInfo = new XrSpatialAnchorPersistInfoHTC
{
type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_PERSIST_INFO_HTC,
anchor = anchor,
persistedAnchorName = name
};
return XrPersistSpatialAnchorAsyncHTC(persistedAnchorCollection, ref persistInfo, out future);
}
public XrResult PersistSpatialAnchorComplete(IntPtr future, out FutureWrapper.XrFutureCompletionEXT completion)
{
completion = new FutureWrapper.XrFutureCompletionEXT() {
type = XrStructureType.XR_TYPE_FUTURE_COMPLETION_EXT,
next = IntPtr.Zero,
futureResult = XrResult.XR_SUCCESS
};
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrPersistSpatialAnchorCompleteHTC(future, out completion);
}
/// <summary>
/// Unpersists the anchor with the given name.
/// </summary>
/// <param name="persistedAnchorCollection">The persisted anchor collection to operate.</param>
/// <param name="name">The name of the anchor to be unpersisted.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult UnpersistSpatialAnchor(IntPtr persistedAnchorCollection, XrSpatialAnchorNameHTC name)
{
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrUnpersistSpatialAnchorHTC(persistedAnchorCollection, ref name);
}
/// <summary>
/// Enumerates all persisted anchor names.
/// </summary>
/// <param name="persistedAnchorCollection">The persisted anchor collection to operate.</param>
/// <param name="persistedAnchorNameCapacityInput">The capacity of the input buffer.</param>
/// <param name="persistedAnchorNameCountOutput">Output parameter to hold the count of persisted anchor names.</param>
/// <param name="persistedAnchorNames">Output parameter to hold the names of persisted anchors.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult EnumeratePersistedAnchorNames(IntPtr persistedAnchorCollection, uint persistedAnchorNameCapacityInput,
ref uint persistedAnchorNameCountOutput, ref XrSpatialAnchorNameHTC[] persistedAnchorNames)
{
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrEnumeratePersistedAnchorNamesHTC(persistedAnchorCollection, persistedAnchorNameCapacityInput, ref persistedAnchorNameCountOutput, persistedAnchorNames);
}
/// <summary>
/// Creates a spatial anchor from a persisted anchor.
/// </summary>
/// <param name="spatialAnchorCreateInfo">Information required to create the spatial anchor from persisted anchor.</param>
/// <param name="anchor">Output parameter to hold the created spatial anchor.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult CreateSpatialAnchorFromPersistedAnchorAsync(XrSpatialAnchorFromPersistedAnchorCreateInfoHTC spatialAnchorCreateInfo, out IntPtr future)
{
future = IntPtr.Zero;
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
if (session == 0)
return XrResult.XR_ERROR_SESSION_LOST;
return XrCreateSpatialAnchorFromPersistedAnchorAsyncHTC(session, ref spatialAnchorCreateInfo, out future);
}
/// <summary>
/// When the future is ready, call this function to get the result.
/// </summary>
/// <param name="future"></param>
/// <param name="completion"></param>
/// <returns></returns>
public XrResult CreateSpatialAnchorFromPersistedAnchorComplete(IntPtr future, out XrSpatialAnchorFromPersistedAnchorCreateCompletionHTC completion)
{
completion = new XrSpatialAnchorFromPersistedAnchorCreateCompletionHTC()
{
type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_COMPLETION_HTC,
next = IntPtr.Zero,
futureResult = XrResult.XR_SUCCESS,
anchor = 0
};
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrCreateSpatialAnchorFromPersistedAnchorCompleteHTC(future, out completion);
}
/// <summary>
/// Clears all persisted anchors.
/// </summary>
/// <param name="persistedAnchorCollection">The persisted anchor collection to operate.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult ClearPersistedAnchors(IntPtr persistedAnchorCollection)
{
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrClearPersistedAnchorsHTC(persistedAnchorCollection);
}
/// <summary>
/// Gets the properties of the persisted anchor.
/// </summary>
/// <param name="persistedAnchorCollection">The persisted anchor collection to operate.</param>
/// <param name="getInfo">Output parameter to hold the properties of the persisted anchor.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult GetPersistedAnchorProperties(IntPtr persistedAnchorCollection, out XrPersistedAnchorPropertiesGetInfoHTC getInfo)
{
getInfo = new XrPersistedAnchorPropertiesGetInfoHTC
{
type = XrStructureType.XR_TYPE_PERSISTED_ANCHOR_PROPERTIES_GET_INFO_HTC
};
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrGetPersistedAnchorPropertiesHTC(persistedAnchorCollection, ref getInfo);
}
/// <summary>
/// Exports the persisted anchor to a buffer. The buffer can be used to import the anchor later or save to a file.
/// </summary>
/// <param name="persistedAnchorCollection">The persisted anchor collection to operate.</param>
/// <param name="persistedAnchorName">The name of the persisted anchor to be exported.</param>
/// <param name="data">Output parameter to hold the buffer containing the exported anchor.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult ExportPersistedAnchor(IntPtr persistedAnchorCollection, XrSpatialAnchorNameHTC persistedAnchorName, out byte[] data)
{
data = null;
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
uint dataCountOutput = 0;
uint dataCapacityInput = 0;
XrResult ret = XrExportPersistedAnchorHTC(persistedAnchorCollection, ref persistedAnchorName, dataCapacityInput, ref dataCountOutput, null);
if (ret != XrResult.XR_SUCCESS)
{
Debug.LogError("ExportPersistedAnchor failed to get data size. ret=" + ret);
data = null;
return ret;
}
dataCapacityInput = dataCountOutput;
data = new byte[dataCountOutput];
ret = XrExportPersistedAnchorHTC(persistedAnchorCollection, ref persistedAnchorName, dataCapacityInput, ref dataCountOutput, data);
return ret;
}
/// <summary>
/// Imports the persisted anchor from a buffer. The buffer should be created by ExportPersistedAnchor.
/// </summary>
/// <param name="persistedAnchorCollection">The persisted anchor collection to operate.</param>
/// <param name="data">The buffer containing the persisted anchor data.</param>
/// <returns>XrResult indicating success or failure.</returns>
public XrResult ImportPersistedAnchor(IntPtr persistedAnchorCollection, byte[] data)
{
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
return XrImportPersistedAnchorHTC(persistedAnchorCollection, (uint)data.Length, data);
}
/// <summary>
/// Gets the name of the persisted anchor from a buffer. The buffer should be created by ExportPersistedAnchor.
/// </summary>
/// <param name="persistedAnchorCollection"></param>
/// <param name="buffer"></param>
/// <param name="name"></param>
/// <returns></returns>
public XrResult GetPersistedAnchorNameFromBuffer(IntPtr persistedAnchorCollection, byte[] buffer, out XrSpatialAnchorNameHTC name)
{
name = new XrSpatialAnchorNameHTC();
if (!IsPAInited)
return XrResult.XR_ERROR_EXTENSION_NOT_PRESENT;
if (buffer == null)
return XrResult.XR_ERROR_VALIDATION_FAILURE;
return XrGetPersistedAnchorNameFromBufferHTC(persistedAnchorCollection, (uint)buffer.Length, buffer, ref name);
}
#endregion
#region tools for user
/// <summary>
/// According to XRInputSubsystem's tracking origin mode, return the corresponding XrSpace.
/// </summary>
/// <returns></returns>
public XrSpace GetTrackingSpace()
{
var s = GetCurrentAppSpace();
//Debug.Log("ViveAnchor GetTrackingSpace() s=" + s);
return s;
}
#endregion
}
}

View File

@@ -0,0 +1,203 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEditor;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine;
using System;
using System.Runtime.InteropServices;
#if UNITY_EDITOR
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.CompositionLayer
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Composition Layer (Extra Settings) (Beta)",
Desc = "Enable this feature to use the Composition Layer Extra Settings.",
Company = "HTC",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = kOpenxrExtensionStrings,
Version = "1.0.0",
BuildTargetGroups = new[] { BuildTargetGroup.Android },
FeatureId = featureId
)]
#endif
public class ViveCompositionLayerExtraSettings : OpenXRFeature
{
const string LOG_TAG = "VIVE.OpenXR.ViveCompositionLayer.ExtraSettings";
static void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
static void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); }
static void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
/// <summary>
/// Settings Editor Enable Sharpening or Not.
/// </summary>
public bool SettingsEditorEnableSharpening = false;
/// <summary>
/// Support Sharpening or Not.
/// </summary>
public bool supportSharpening = false;
/// <summary>
/// Settings Editor Sharpening Mode
/// </summary>
public XrSharpeningModeHTC SettingsEditorSharpeningMode = XrSharpeningModeHTC.FAST;
/// <summary>
/// Settings Editor Sharpening Levell
/// </summary>
[Range(0.0f, 1.0f)]
public float SettingsEditorSharpeningLevel = 1.0f;
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.compositionlayer.extrasettings";
/// <summary>
/// OpenXR specification.
/// </summary>
public const string kOpenxrExtensionStrings = "XR_HTC_composition_layer_extra_settings";
#region OpenXR Life Cycle
private bool m_XrInstanceCreated = false;
/// <summary>
/// The XR instance is created or not.
/// </summary>
public bool XrInstanceCreated
{
get { return m_XrInstanceCreated; }
}
private XrInstance m_XrInstance = 0;
protected override bool OnInstanceCreate(ulong xrInstance)
{
foreach (string kOpenxrExtensionString in kOpenxrExtensionStrings.Split(' '))
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
WARNING("OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled.");
}
}
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
DEBUG("OnInstanceCreate() " + m_XrInstance);
return true;
}
protected override void OnInstanceDestroy(ulong xrInstance)
{
m_XrInstanceCreated = false;
DEBUG("OnInstanceDestroy() " + m_XrInstance);
}
private XrSystemId m_XrSystemId = 0;
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
DEBUG("OnSystemChange() " + m_XrSystemId);
}
private bool m_XrSessionCreated = false;
/// <summary>
/// The XR session is created or not.
/// </summary>
public bool XrSessionCreated
{
get { return m_XrSessionCreated; }
}
private XrSession m_XrSession = 0;
protected override void OnSessionCreate(ulong xrSession)
{
m_XrSession = xrSession;
m_XrSessionCreated = true;
DEBUG("OnSessionCreate() " + m_XrSession);
}
private bool m_XrSessionEnding = false;
/// <summary>
/// The XR session is ending or not.
/// </summary>
public bool XrSessionEnding
{
get { return m_XrSessionEnding; }
}
protected override void OnSessionBegin(ulong xrSession)
{
m_XrSessionEnding = false;
DEBUG("OnSessionBegin() " + m_XrSession);
//enable Sharpening
if (OpenXRRuntime.IsExtensionEnabled("XR_HTC_composition_layer_extra_settings"))
{
ViveCompositionLayer_UpdateSystemProperties(m_XrInstance, m_XrSystemId);
supportSharpening = ViveCompositionLayer_IsSupportSharpening();
if (supportSharpening && SettingsEditorEnableSharpening)
{
EnableSharpening(SettingsEditorSharpeningMode, SettingsEditorSharpeningLevel);
}
}
}
protected override void OnSessionEnd(ulong xrSession)
{
m_XrSessionEnding = true;
DEBUG("OnSessionEnd() " + m_XrSession);
}
protected override void OnSessionDestroy(ulong xrSession)
{
m_XrSessionCreated = false;
DEBUG("OnSessionDestroy() " + xrSession);
}
#endregion
#region Wrapper Functions
private const string ExtLib = "viveopenxr";
[DllImportAttribute(ExtLib, EntryPoint = "viveCompositionLayer_UpdateSystemProperties")]
private static extern int VIVEOpenXR_ViveCompositionLayer_UpdateSystemProperties(XrInstance instance, XrSystemId system_id);
private int ViveCompositionLayer_UpdateSystemProperties(XrInstance instance, XrSystemId system_id)
{
return VIVEOpenXR_ViveCompositionLayer_UpdateSystemProperties(instance, system_id);
}
[DllImportAttribute(ExtLib, EntryPoint = "viveCompositionLayer_IsSupportSharpening")]
private static extern bool VIVEOpenXR_ViveCompositionLayer_IsSupportSharpening();
private bool ViveCompositionLayer_IsSupportSharpening()
{
return VIVEOpenXR_ViveCompositionLayer_IsSupportSharpening();
}
[DllImportAttribute(ExtLib, EntryPoint = "viveCompositionLayer_enableSharpening")]
private static extern int VIVEOpenXR_ViveCompositionLayer_enableSharpening(XrSharpeningModeHTC sharpeningMode, float sharpeningLevel);
/// <summary>
/// Enable the sharpening setting applying to the projection layer.
/// </summary>
/// <param name="sharpeningMode">The sharpening mode in <see cref="XrSharpeningModeHTC"/>.</param>
/// <param name="sharpeningLevel">The sharpening level in float [0, 1].</param>
/// <returns>True for success.</returns>
public bool EnableSharpening(XrSharpeningModeHTC sharpeningMode, float sharpeningLevel)
{
return (VIVEOpenXR_ViveCompositionLayer_enableSharpening(sharpeningMode, sharpeningLevel) == 0);
}
[DllImportAttribute(ExtLib, EntryPoint = "viveCompositionLayer_disableSharpening")]
private static extern int VIVEOpenXR_ViveCompositionLayer_DisableSharpening();
/// <summary>
/// Disable the sharpening setting on the projection layer.
/// </summary>
/// <returns>True for success</returns>
public bool DisableSharpening()
{
return (VIVEOpenXR_ViveCompositionLayer_DisableSharpening() == 0);
}
#endregion
}
}

View File

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

View File

@@ -1,4 +1,4 @@
// Copyright HTC Corporation All Rights Reserved.
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections.Generic;
@@ -67,65 +67,6 @@ namespace VIVE.OpenXR.CompositionLayer
}
}
public struct XrCompositionLayerFlags : IEquatable<UInt64>
{
private readonly UInt64 value;
public XrCompositionLayerFlags(UInt64 u)
{
value = u;
}
public static implicit operator UInt64(XrCompositionLayerFlags xrBool)
{
return xrBool.value;
}
public static implicit operator XrCompositionLayerFlags(UInt64 u)
{
return new XrCompositionLayerFlags(u);
}
public bool Equals(XrCompositionLayerFlags other)
{
return value == other.value;
}
public bool Equals(UInt64 other)
{
return value == other;
}
public override bool Equals(object obj)
{
return obj is XrCompositionLayerFlags && Equals((XrCompositionLayerFlags)obj);
}
public override int GetHashCode()
{
return value.GetHashCode();
}
public override string ToString()
{
return value.ToString();
}
public static bool operator ==(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return a.Equals(b); }
public static bool operator !=(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return !a.Equals(b); }
public static bool operator >=(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return a.value >= b.value; }
public static bool operator <=(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return a.value <= b.value; }
public static bool operator >(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return a.value > b.value; }
public static bool operator <(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return a.value < b.value; }
public static XrCompositionLayerFlags operator +(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return a.value + b.value; }
public static XrCompositionLayerFlags operator -(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return a.value - b.value; }
public static XrCompositionLayerFlags operator *(XrCompositionLayerFlags a, XrCompositionLayerFlags b) { return a.value * b.value; }
public static XrCompositionLayerFlags operator /(XrCompositionLayerFlags a, XrCompositionLayerFlags b)
{
if (b.value == 0)
{
throw new DivideByZeroException();
}
return a.value / b.value;
}
}
public struct XrSwapchainCreateFlags : IEquatable<UInt64>
{
@@ -288,6 +229,36 @@ namespace VIVE.OpenXR.CompositionLayer
public XrColor4f colorScale;
public XrColor4f colorBias;
}
[StructLayout(LayoutKind.Sequential)]
public struct XrCompositionLayerSharpeningSettingHTC
{
public XrStructureType type;
public IntPtr next;
public XrSharpeningModeHTC mode;
public float sharpeningLevel;
}
[StructLayout(LayoutKind.Sequential)]
public struct XrCompositionLayerSuperSamplingSettingHTC
{
public XrStructureType type;
public IntPtr next;
public XrSuperSamplingModeHTC mode;
}
public enum XrSharpeningModeHTC
{
FAST = 0,
NORMAL = 1,
QUALITY = 2,
AUTOMATIC = 3,
}
public enum XrSuperSamplingModeHTC
{
FAST = 0,
NORMAL = 1,
QUALITY = 2,
AUTOMATIC = 3,
}
public enum GraphicsAPI
{
GLES3 = 1,
@@ -410,29 +381,6 @@ namespace VIVE.OpenXR.CompositionLayer
}
};
/// <summary>
/// The XrCompositionLayerBaseHeader structure is not intended to be directly used, but forms a basis for defining current and future structures containing composition layer information. The XrFrameEndInfo structure contains an array of pointers to these polymorphic header structures.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrCompositionLayerBaseHeader
{
/// <summary>
/// The XrStructureType of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// Next is NULL or a pointer to the next structure in a structure chain, such as XrPassthroughMeshTransformInfoHTC.
/// </summary>
public IntPtr next;
/// <summary>
/// A bitmask of XrCompositionLayerFlagBits describing flags to apply to the layer.
/// </summary>
public XrCompositionLayerFlags layerFlags;
/// <summary>
/// The XrSpace in which the layer will be kept stable over time.
/// </summary>
public XrSpace space;
};
/// <summary>
/// The application can specify the XrPassthroughColorHTC to adjust the alpha value of the passthrough. The range is between 0.0f and 1.0f, 1.0f means opaque.
/// </summary>
[StructLayout(LayoutKind.Sequential)]

View File

@@ -18,7 +18,7 @@ using UnityEditor.XR.OpenXR.Features;
namespace VIVE.OpenXR.CompositionLayer.Passthrough
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Composition Layer (Passthrough)",
[OpenXRFeature(UiName = "VIVE XR Composition Layer (Passthrough) (Deprecated)",
Desc = "Enable this feature to use the HTC Passthrough feature.",
Company = "HTC",
DocumentationLink = "..\\Documentation",
@@ -28,6 +28,7 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
FeatureId = featureId
)]
#endif
[Obsolete("This class is deprecated. Please use VivePassthrough instead.")]
public class ViveCompositionLayerPassthrough : OpenXRFeature
{
const string LOG_TAG = "VIVE.OpenXR.ViveCompositionLayerPassthrough";

View File

@@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
namespace VIVE.OpenXR.CompositionLayer.Passthrough
{
[Obsolete("This enumeration is deprecated. Please use XrStructureType instead.")]
//[StructLayout(LayoutKind.Sequential)]
public enum XrStructureTypeHTC
{
@@ -16,6 +17,7 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC = 1000317004,
}
[Obsolete("This enumeration is deprecated. Please use VIVE.OpenXR.Passthrough.PassthroughLayerForm instead.")]
public enum PassthroughLayerForm
{
///<summary> Fullscreen Passthrough Form</summary>
@@ -24,6 +26,7 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
Projected = 1
}
[Obsolete("This enumeration is deprecated. Please use VIVE.OpenXR.Passthrough.ProjectedPassthroughSpaceType instead.")]
public enum ProjectedPassthroughSpaceType
{
///<summary>

View File

@@ -49,7 +49,14 @@ namespace VIVE.OpenXR.DisplayRefreshRate
public const string featureId = "vive.openxr.feature.displayrefreshrate";
#region OpenXR Life Cycle
private XrInstance m_XrInstance = 0;
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
ViveInterceptors.Instance.AddRequiredFunction("xrPollEvent");
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
}
private XrInstance m_XrInstance = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>

View File

@@ -0,0 +1,111 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
namespace VIVE.OpenXR.DisplayRefreshRate
{
// -------------------- 12.52. XR_FB_display_refresh_rate --------------------
#region New Structures
/// <summary>
/// On platforms which support dynamically adjusting the display refresh rate, application developers may request a specific display refresh rate in order to improve the overall user experience.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrEventDataDisplayRefreshRateChangedFB
{
/// <summary>
/// The <see cref="XrStructureType"/> of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// fromDisplayRefreshRate is the previous display refresh rate.
/// </summary>
public float fromDisplayRefreshRate;
/// <summary>
/// toDisplayRefreshRate is the new display refresh rate.
/// </summary>
public float toDisplayRefreshRate;
/// <summary>
/// The XR_FB_display_refresh_rate extension must be enabled prior to using XrEventDataDisplayRefreshRateChangedFB.
/// </summary>
public XrEventDataDisplayRefreshRateChangedFB(XrStructureType in_type, IntPtr in_next, float in_fromDisplayRefreshRate, float in_toDisplayRefreshRate)
{
type = in_type;
next = in_next;
fromDisplayRefreshRate = in_fromDisplayRefreshRate;
toDisplayRefreshRate = in_toDisplayRefreshRate;
}
/// <summary>
/// Retrieves the identity value of XrEventDataDisplayRefreshRateChangedFB.
/// </summary>
public static XrEventDataDisplayRefreshRateChangedFB identity
{
get
{
return new XrEventDataDisplayRefreshRateChangedFB(XrStructureType.XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB, IntPtr.Zero, 0.0f, 0.0f); // user is default present
}
}
public static bool Get(XrEventDataBuffer eventDataBuffer, out XrEventDataDisplayRefreshRateChangedFB eventDataDisplayRefreshRateChangedFB)
{
eventDataDisplayRefreshRateChangedFB = identity;
if (eventDataBuffer.type == XrStructureType.XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB)
{
eventDataDisplayRefreshRateChangedFB.next = eventDataBuffer.next;
eventDataDisplayRefreshRateChangedFB.fromDisplayRefreshRate = BitConverter.ToSingle(eventDataBuffer.varying, 0);
eventDataDisplayRefreshRateChangedFB.toDisplayRefreshRate = BitConverter.ToSingle(eventDataBuffer.varying, 4);
return true;
}
return false;
}
}
public static class ViveDisplayRefreshRateChanged
{
public delegate void OnDisplayRefreshRateChanged(float fromDisplayRefreshRate, float toDisplayRefreshRate);
public static void Listen(OnDisplayRefreshRateChanged callback)
{
if (!allEventListeners.Contains(callback))
allEventListeners.Add(callback);
}
public static void Remove(OnDisplayRefreshRateChanged callback)
{
if (allEventListeners.Contains(callback))
allEventListeners.Remove(callback);
}
public static void Send(float fromDisplayRefreshRate, float toDisplayRefreshRate)
{
int N = 0;
if (allEventListeners != null)
{
N = allEventListeners.Count;
for (int i = N - 1; i >= 0; i--)
{
OnDisplayRefreshRateChanged single = allEventListeners[i];
try
{
single(fromDisplayRefreshRate, toDisplayRefreshRate);
}
catch (Exception e)
{
Debug.Log("Event : " + e.ToString());
allEventListeners.Remove(single);
Debug.Log("Event : A listener is removed due to exception.");
}
}
}
}
private static List<OnDisplayRefreshRateChanged> allEventListeners = new List<OnDisplayRefreshRateChanged>();
}
#endregion
}

View File

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

View File

@@ -15,8 +15,8 @@ using UnityEditor.XR.OpenXR.Features;
namespace VIVE.OpenXR.EyeTracker
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Eye Tracker",
BuildTargetGroups = new[] { BuildTargetGroup.Standalone },
[OpenXRFeature(UiName = "VIVE XR Eye Tracker (Beta)",
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "HTC",
Desc = "Support the eye tracker extension.",
DocumentationLink = "..\\Documentation",

View File

@@ -51,10 +51,10 @@ Through feeding the blend shape values of lip expression to an avatar, its facia
XR_LIP_EXPRESSION_MOUTH_UPPER_OVERTURN_HTC = 9,
XR_LIP_EXPRESSION_MOUTH_LOWER_OVERTURN_HTC = 10,
XR_LIP_EXPRESSION_MOUTH_POUT_HTC = 11,
XR_LIP_EXPRESSION_MOUTH_SMILE_RIGHT_HTC = 12,
XR_LIP_EXPRESSION_MOUTH_SMILE_LEFT_HTC = 13,
XR_LIP_EXPRESSION_MOUTH_SAD_RIGHT_HTC = 14,
XR_LIP_EXPRESSION_MOUTH_SAD_LEFT_HTC = 15,
XR_LIP_EXPRESSION_MOUTH_RAISER_RIGHT_HTC = 12,
XR_LIP_EXPRESSION_MOUTH_RAISER_LEFT_HTC = 13,
XR_LIP_EXPRESSION_MOUTH_STRETCHER_RIGHT_HTC = 14,
XR_LIP_EXPRESSION_MOUTH_STRETCHER_LEFT_HTC = 15,
XR_LIP_EXPRESSION_CHEEK_PUFF_RIGHT_HTC = 16,
XR_LIP_EXPRESSION_CHEEK_PUFF_LEFT_HTC = 17,
XR_LIP_EXPRESSION_CHEEK_SUCK_HTC = 18,

View File

@@ -68,8 +68,11 @@ namespace VIVE.OpenXR.FacialTracking
/// <param name="xrInstance">The instance to destroy.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
if (m_XrInstance == xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
}
DEBUG("OnInstanceDestroy() " + xrInstance);
}

View File

@@ -201,19 +201,19 @@ namespace VIVE.OpenXR.FacialTracking
/// <summary>
/// This blend shape raises the right side of the mouth further with a higher value.
/// </summary>
XR_LIP_EXPRESSION_MOUTH_SMILE_RIGHT_HTC = 12,
XR_LIP_EXPRESSION_MOUTH_RAISER_RIGHT_HTC = 12,
/// <summary>
/// This blend shape raises the left side of the mouth further with a higher value.
/// </summary>
XR_LIP_EXPRESSION_MOUTH_SMILE_LEFT_HTC = 13,
XR_LIP_EXPRESSION_MOUTH_RAISER_LEFT_HTC = 13,
/// <summary>
/// This blend shape lowers the right side of the mouth further with a higher value.
/// </summary>
XR_LIP_EXPRESSION_MOUTH_SAD_RIGHT_HTC = 14,
XR_LIP_EXPRESSION_MOUTH_STRETCHER_RIGHT_HTC = 14,
/// <summary>
/// This blend shape lowers the left side of the mouth further with a higher value.
/// </summary>
XR_LIP_EXPRESSION_MOUTH_SAD_LEFT_HTC = 15,
XR_LIP_EXPRESSION_MOUTH_STRETCHER_LEFT_HTC = 15,
/// <summary>
/// This blend shape puffs up the right side of the cheek further with a higher value.
/// </summary>
@@ -433,7 +433,7 @@ namespace VIVE.OpenXR.FacialTracking
/// </summary>
/// <param name="facialTracker">An <see cref="XrFacialTrackerHTC">XrFacialTrackerHTC</see> previously created by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateFacialTrackerHTC">xrCreateFacialTrackerHTC</see>.</param>
/// <param name="facialExpressions">A pointer to <see cref="XrFacialExpressionsHTC">XrFacialExpressionsHTC</see> receiving the returned facial expressions.</param>
/// <returns></returns>
/// <returns>XR_SUCCESS for success.</returns>
public delegate XrResult xrGetFacialExpressionsHTCDelegate(
XrFacialTrackerHTC facialTracker,
ref XrFacialExpressionsHTC facialExpressions);

View File

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

View File

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

View File

@@ -0,0 +1,185 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
using VIVE.OpenXR.SecondaryViewConfiguration;
namespace VIVE.OpenXR.FirstPersonObserver
{
/// <summary>
/// Name: FirstPersonObserver.cs
/// Role: OpenXR FirstPersonObserver Extension Class
/// Responsibility: The OpenXR extension implementation and its lifecycles logic in OpenXR
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(UiName = "XR MSFT First Person Observer",
BuildTargetGroups = new[] { BuildTargetGroup.Android },
Company = "HTC",
Desc = "Request the application to render an additional first-person view of the scene.",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = OPEN_XR_EXTENSION_STRING,
Version = "1.0.0",
FeatureId = FeatureId,
Hidden = true)]
#endif
public class ViveFirstPersonObserver : OpenXRFeature
{
private static ViveFirstPersonObserver _instance;
/// <summary>
/// ViveFirstPersonObserver static instance (Singleton).
/// </summary>
public static ViveFirstPersonObserver Instance
{
get
{
if (_instance == null)
{
_instance =
OpenXRSettings.Instance.GetFeature<ViveFirstPersonObserver>();
}
return _instance;
}
}
/// <summary>
/// The log identification.
/// </summary>
private const string LogTag = "VIVE.OpenXR.FirstPersonObserver";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string FeatureId = "vive.openxr.feature.firstpersonobserver";
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_MSFT_first_person_observer">12.114. XR_MSFT_first_person_observer</see>.
/// </summary>
public const string OPEN_XR_EXTENSION_STRING = "XR_MSFT_first_person_observer";
/// <summary>
/// The flag represents whether the OpenXR loader created an instance or not.
/// </summary>
private bool XrInstanceCreated { get; set; } = false;
/// <summary>
/// The instance created through xrCreateInstance.
/// </summary>
private XrInstance XrInstance { get; set; } = 0;
/// <summary>
/// The function delegate declaration of xrGetInstanceProcAddr.
/// </summary>
private OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr { get; set; }
#region OpenXR life-cycle events
/// <summary>
/// Called after xrCreateInstance.
/// </summary>
/// <param name="xrInstance">Handle of the xrInstance.</param>
/// <returns>Returns true if successful. Returns false otherwise.</returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!IsExtensionEnabled())
{
Warning($"OnInstanceCreate() {OPEN_XR_EXTENSION_STRING} or " +
$"{ViveSecondaryViewConfiguration.OPEN_XR_EXTENSION_STRING} is NOT enabled.");
return false;
}
XrInstanceCreated = true;
XrInstance = xrInstance;
Debug("OnInstanceCreate() " + XrInstance);
if (!GetXrFunctionDelegates(XrInstance))
{
Error("Get function pointer of OpenXRFunctionPointerAccessor failed.");
return false;
}
Debug("Get function pointer of OpenXRFunctionPointerAccessor succeed.");
return base.OnInstanceCreate(xrInstance);
}
#endregion
/// <summary>
/// Get the OpenXR function via XrInstance.
/// </summary>
/// <param name="xrInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <returns>Return true if get successfully. False otherwise.</returns>
private bool GetXrFunctionDelegates(XrInstance xrInstance)
{
if (xrGetInstanceProcAddr != IntPtr.Zero)
{
Debug("Get function pointer of openXRFunctionPointerAccessor.");
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(xrGetInstanceProcAddr,
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
if (XrGetInstanceProcAddr == null)
{
Error(
"Get function pointer of openXRFunctionPointerAccessor failed due to the XrGetInstanceProcAddr is null.");
return false;
}
}
else
{
Error(
"Get function pointer of openXRFunctionPointerAccessor failed due to the xrGetInstanceProcAddr is null.");
return false;
}
return true;
}
#region Utilities functions
/// <summary>
/// Check ViveFirstPersonObserver extension is enabled or not.
/// </summary>
/// <returns>Return true if enabled. False otherwise.</returns>
public static bool IsExtensionEnabled()
{
return OpenXRRuntime.IsExtensionEnabled(OPEN_XR_EXTENSION_STRING) &&
ViveSecondaryViewConfiguration.IsExtensionEnabled();
}
/// <summary>
/// Print log with tag "VIVE.OpenXR.SecondaryViewConfiguration".
/// </summary>
/// <param name="msg">The log you want to print.</param>
private static void Debug(string msg)
{
UnityEngine.Debug.Log(LogTag + " " + msg);
}
/// <summary>
/// Print warning message with tag "VIVE.OpenXR.SecondaryViewConfiguration".
/// </summary>
/// <param name="msg">The warning message you want to print.</param>
private static void Warning(string msg)
{
UnityEngine.Debug.LogWarning(LogTag + " " + msg);
}
/// <summary>
/// Print an error message with the tag "VIVE.OpenXR.SecondaryViewConfiguration."
/// </summary>
/// <param name="msg">The error message you want to print.</param>
private static void Error(string msg)
{
UnityEngine.Debug.LogError(LogTag + " " + msg);
}
#endregion
}
}

View File

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

View File

@@ -85,12 +85,12 @@ namespace VIVE.OpenXR
private static extern IntPtr intercept_xrGetInstanceProcAddr(IntPtr func);
[DllImport(ExtLib, EntryPoint = "applyFoveationHTC")]
private static extern XrResult applyFoveationHTC(XrFoveationModeHTC mode, UInt32 configCount, XrFoveationConfigurationHTC[] configs, UInt64 flags);
private static extern XrResult applyFoveationHTC(Foveation.XrFoveationModeHTC mode, UInt32 configCount, Foveation.XrFoveationConfigurationHTC[] configs, UInt64 flags);
/// <summary>
/// function to apply HTC Foveation
/// </summary>
public static XrResult ApplyFoveationHTC(XrFoveationModeHTC mode, UInt32 configCount, XrFoveationConfigurationHTC[] configs, UInt64 flags = 0)
public static XrResult ApplyFoveationHTC(Foveation.XrFoveationModeHTC mode, UInt32 configCount, Foveation.XrFoveationConfigurationHTC[] configs, UInt64 flags = 0)
{
//Debug.Log("Unity HTCFoveat:configCount " + configCount);
//if (configCount >=2) {

View File

@@ -0,0 +1,38 @@
// Copyright HTC Corporation All Rights Reserved.
namespace VIVE.OpenXR.Foveation
{
#region 12.86. XR_HTC_foveation
/// <summary>
/// The XrFoveationModeHTC identifies the different foveation modes.
/// </summary>
public enum XrFoveationModeHTC
{
XR_FOVEATION_MODE_DISABLE_HTC = 0,
XR_FOVEATION_MODE_FIXED_HTC = 1,
XR_FOVEATION_MODE_DYNAMIC_HTC = 2,
XR_FOVEATION_MODE_CUSTOM_HTC = 3,
XR_FOVEATION_MODE_MAX_ENUM_HTC = 0x7FFFFFFF
}
/// <summary>
/// The XrFoveationLevelHTC identifies the pixel density drop level of periphery area.
/// </summary>
public enum XrFoveationLevelHTC
{
XR_FOVEATION_LEVEL_NONE_HTC = 0,
XR_FOVEATION_LEVEL_LOW_HTC = 1,
XR_FOVEATION_LEVEL_MEDIUM_HTC = 2,
XR_FOVEATION_LEVEL_HIGH_HTC = 3,
XR_FOVEATION_LEVEL_MAX_ENUM_HTC = 0x7FFFFFFF
}
/// <summary>
/// The XrFoveationConfigurationHTC structure contains the custom foveation settings for the corresponding views.
/// </summary>
public struct XrFoveationConfigurationHTC
{
public XrFoveationLevelHTC level;
public float clearFovDegree;
public XrVector2f focalCenterOffset;
}
#endregion
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
# 12.1. XR_HTC_frame_synchronization
## Overview
Traditional, runtime will use the latest frame which will cost jitter. With Frame Synchronization, the render frame will not be discarded for smooth gameplay experience.
However, if the GPU cannot consistently finish rendering on time (rendering more than one vsync at a time), jitter will still occur. Therefore, reducing GPU load is key to smooth gameplay.
## Name String
XR_HTC_frame_synchronization
## Revision
1
## New Enum Constants
[XrStructureType](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrStructureType) enumeration is extended with:
- XR_TYPE_FRAME_SYNCHRONIZATION_SESSION_BEGIN_INFO_HTC
## New Enums
- XrFrameSynchronizationModeHTC
## New Structures
- XrFrameSynchronizationSessionBeginInfoHTC
## VIVE Plugin
Enable "VIVE XR Frame Synchronization" in "Project Settings > XR Plugin-in Management > OpenXR > Android Tab > OpenXR Feature Groups" to use the frame synchronization provided by VIVE OpenXR plugin.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 190a1897e332b7f45893a24c3f696567
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,165 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Text;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.FrameSynchronization
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Frame Synchronization (Beta)",
BuildTargetGroups = new[] { BuildTargetGroup.Android },
Company = "HTC",
Desc = "Support the Frame Synchronization extension.",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = kOpenxrExtensionString,
Version = "1.0.0",
FeatureId = featureId)]
#endif
public class ViveFrameSynchronization : OpenXRFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.FrameSynchronization.ViveFrameSynchronization";
StringBuilder m_sb = null;
StringBuilder sb {
get {
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
void WARNING(StringBuilder msg) { Debug.LogWarningFormat("{0} {1}", LOG_TAG, msg); }
void ERROR(StringBuilder msg) { Debug.LogErrorFormat("{0} {1}", LOG_TAG, msg); }
#endregion
/// <summary>
/// The extension name of 12.1. XR_HTC_frame_synchronization.
/// </summary>
public const string kOpenxrExtensionString = "XR_HTC_frame_synchronization";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.framesynchronization";
#region OpenXR Life Cycle
/// <inheritdoc />
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
sb.Clear().Append("HookGetInstanceProcAddr() xrBeginSession"); DEBUG(sb);
ViveInterceptors.Instance.AddRequiredFunction("xrBeginSession");
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
}
#pragma warning disable
private bool m_XrInstanceCreated = false;
#pragma warning enable
private XrInstance m_XrInstance = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The created instance.</param>
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
sb.Clear().Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
return false;
}
m_XrInstance = xrInstance;
m_XrInstanceCreated = true;
sb.Clear().Append("OnInstanceCreate() ").Append(m_XrInstance); DEBUG(sb);
ActivateFrameSynchronization(true);
return true;
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyInstance">xrDestroyInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The instance to destroy.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
sb.Clear().Append("OnInstanceDestroy() ").Append(xrInstance).Append(", current: ").Append(m_XrInstance); DEBUG(sb);
if (m_XrInstance == xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
}
}
#pragma warning disable
private bool m_XrSessionCreated = false;
#pragma warning enable
private XrSession m_XrSession = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateSession">xrCreateSession</see> is done.
/// </summary>
/// <param name="xrSession">The created session ID.</param>
protected override void OnSessionCreate(ulong xrSession)
{
m_XrSession = xrSession;
m_XrSessionCreated = true;
sb.Clear().Append("OnSessionCreate() ").Append(m_XrSession); DEBUG(sb);
}
protected override void OnSessionEnd(ulong xrSession)
{
sb.Clear().Append("OnSessionEnd() ").Append(xrSession).Append(", current: ").Append(m_XrSession); DEBUG(sb);
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroySession">xrDestroySession</see> is done.
/// </summary>
/// <param name="xrSession">The session ID to destroy.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
sb.Clear().Append("OnSessionDestroy() ").Append(xrSession).Append(", current: ").Append(m_XrSession); DEBUG(sb);
if (m_XrSession == xrSession)
{
m_XrSessionCreated = false;
m_XrSession = 0;
ActivateFrameSynchronization(false);
}
}
private XrSystemId m_XrSystemId = 0;
/// <summary>
/// Called when the <see cref="XrSystemId">XrSystemId</see> retrieved by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystem">xrGetSystem</see> is changed.
/// </summary>
/// <param name="xrSystem">The system id.</param>
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
sb.Clear().Append("OnSystemChange() " + m_XrSystemId); DEBUG(sb);
}
#endregion
[SerializeField]
internal SynchronizationModeHTC m_SynchronizationMode = SynchronizationModeHTC.Stablized;
/// <summary>
/// Activate or deactivate the Frame Synchronization feature.
/// </summary>
/// <param name="active">True for activate</param>
/// <param name="mode">The <see cref="XrFrameSynchronizationModeHTC"/> used for Frame Synchronization.</param>
private void ActivateFrameSynchronization(bool active)
{
sb.Clear().Append("ActivateFrameSynchronization() ").Append(active ? "enable " : "disable ").Append(m_SynchronizationMode); DEBUG(sb);
ViveInterceptors.Instance.ActivateFrameSynchronization(active, (XrFrameSynchronizationModeHTC)m_SynchronizationMode);
}
/// <summary>
/// Retrieves current frame synchronization mode.
/// </summary>
/// <returns>The mode of <see cref="SynchronizationModeHTC"/>.</returns>
public SynchronizationModeHTC GetSynchronizationMode() { return m_SynchronizationMode; }
}
}

View File

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

View File

@@ -0,0 +1,67 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
namespace VIVE.OpenXR.FrameSynchronization
{
/// <summary>
/// The enum alias of <see cref="XrFrameSynchronizationModeHTC"/>.
/// </summary>
public enum SynchronizationModeHTC : UInt32
{
Stablized = XrFrameSynchronizationModeHTC.XR_FRAME_SYNCHRONIZATION_MODE_STABILIZED_HTC,
Prompt = XrFrameSynchronizationModeHTC.XR_FRAME_SYNCHRONIZATION_MODE_PROMPT_HTC,
//Adaptive = XrFrameSynchronizationModeHTC.XR_FRAME_SYNCHRONIZATION_MODE_ADAPTIVE_HTC,
}
// -------------------- 12.1. XR_HTC_frame_synchronization --------------------
#region New Enums
public enum XrFrameSynchronizationModeHTC : UInt32
{
XR_FRAME_SYNCHRONIZATION_MODE_STABILIZED_HTC = 1,
XR_FRAME_SYNCHRONIZATION_MODE_PROMPT_HTC = 2,
XR_FRAME_SYNCHRONIZATION_MODE_ADAPTIVE_HTC = 3,
XR_FRAME_SYNCHRONIZATION_MODE_MAX_ENUM_HTC = 0x7FFFFFFF
}
#endregion
#region New Structures
/// <summary>
/// Traditional, runtime will use the latest frame which will cost jitter. With Frame Synchronization, the render frame will not be discarded for smooth gameplay experience.
/// However, if the GPU cannot consistently finish rendering on time(rendering more than one vsync at a time), jitter will still occur.Therefore, reducing GPU load is key to smooth gameplay.
/// The application can use Frame Synchronization by passing XrFrameSynchronizationSessionBeginInfoHTC at next of <see cref="XrSessionBeginInfo"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrFrameSynchronizationSessionBeginInfoHTC
{
/// <summary>
/// The XrStructureType of this structure. It must be XR_TYPE_FRAME_SYNCHRONIZATION_SESSION_BEGIN_INFO_HTC.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// The frame synchronization mode to be used in this session.
/// </summary>
public XrFrameSynchronizationModeHTC mode;
public XrFrameSynchronizationSessionBeginInfoHTC(XrStructureType in_type, IntPtr in_next, XrFrameSynchronizationModeHTC in_mode)
{
type = in_type;
next = in_next;
mode = in_mode;
}
public static XrFrameSynchronizationSessionBeginInfoHTC identity {
get {
return new XrFrameSynchronizationSessionBeginInfoHTC(
XrStructureType.XR_TYPE_FRAME_SYNCHRONIZATION_SESSION_BEGIN_INFO_HTC,
IntPtr.Zero,
XrFrameSynchronizationModeHTC.XR_FRAME_SYNCHRONIZATION_MODE_STABILIZED_HTC);
}
}
}
#endregion
}

View File

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

View File

@@ -19,7 +19,7 @@ The application should use
## VIVE Plugin
After adding the "VIVE Focus3 Hand Interaction" to "Project Settings > XR Plugin-in Management > OpenXR > Android Tab > Interaction Profiles", you can use the following Input Action Pathes.
After adding the "VIVE XR Hand Interaction" to "Project Settings > XR Plugin-in Management > OpenXR > Android Tab > Interaction Profiles", you can use the following Input Action Pathes.
### Left Hand
- <ViveHandInteraction>{LeftHand}/selectValue: Presents the left hand pinch strength.
@@ -29,4 +29,69 @@ After adding the "VIVE Focus3 Hand Interaction" to "Project Settings > XR Plugin
- <ViveHandInteraction>{RightHand}/selectValue: Presents the right hand pinch strength.
- <ViveHandInteraction>{RightHand}/pointerPose: Presents the right hand pinch pose.
Refer to the <VIVE OpenXR sample path>/Plugin/Input/ActionMap/InputActions.inputActions about the "Input Action Path" usage and the sample <VIVE OpenXR sample path>/Plugin/Input/OpenXRInput.unity.
Refer to the <VIVE OpenXR sample path>/Samples/Commons/ActionMap/InputActions.inputActions about the "Input Action Path" usage in the sample <VIVE OpenXR sample path>/Samples/Input/OpenXRInput.unity.
--------------------
# 12.31. XR_EXT_hand_interaction
## Name String
XR_EXT_hand_interaction
## Revision
1
## Hand Interaction Profile
### Interaction profile path:
- /interaction_profiles/ext/hand_interaction_ext
### Valid for user paths:
- /user/hand/left
- /user/hand/right
### Supported input source
- <20>K/input/aim/pose
- <20>K/input/aim_activate_ext/value: a 1D analog input component indicating that the user activated the action on the target that the user is pointing at with the aim pose.
- <20>K/input/aim_activate_ext/ready_ext: a boolean input, where the value XR_TRUE indicates that the fingers to perform the "aim_activate" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing an "aim_activate" gesture.
- <20>K/input/grip/pose
- <20>K/input/grasp_ext/value: a 1D analog input component indicating that the user is making a fist.
- <20>K/input/grasp_ext/ready_ext: a boolean input, where the value XR_TRUE indicates that the hand performing the grasp action is properly tracked by the hand tracking device and it is observed to be ready to perform or is performing the grasp action.
- <20>K/input/pinch_ext/pose
- <20>K/input/pinch_ext/value: a 1D analog input component indicating the extent which the user is bringing their finger and thumb together to perform a "pinch" gesture.
- <20>K/input/pinch_ext/ready_ext: a boolean input, where the value XR_TRUE indicates that the fingers used to perform the "pinch" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing a "pinch" gesture.
- <20>K/input/poke_ext/pose
The <20>K/input/aim/pose is typically used for aiming at objects out of arm<72><6D>s reach. When using a hand interaction profile, it is typically paired with <20>K/input/aim_activate_ext/value to optimize aiming ray stability while performing the gesture. When using a controller interaction profile, the "aim" pose is typically paired with a trigger or a button for aim and fire operations.
The <20>K/input/grip/pose is typically used for holding a large object in the user<65><72>s hand. When using a hand interaction profile, it is typically paired with <20>K/input/grasp_ext/value for the user to directly manipulate an object held in a hand. When using a controller interaction profile, the "grip" pose is typically paired with a "squeeze" button or trigger that gives the user the sense of tightly holding an object.
The <20>K/input/pinch_ext/pose is typically used for directly manipulating a small object using the pinch gesture. When using a hand interaction profile, it is typically paired with the <20>K/input/pinch_ext/value gesture. When using a controller interaction profile, it is typically paired with a trigger manipulated with the index finger, which typically requires curling the index finger and applying pressure with the fingertip.
The <20>K/input/poke_ext/pose is typically used for contact-based interactions using the motion of the hand or fingertip. It typically does not pair with other hand gestures or buttons on the controller. The application typically uses a sphere collider with the "poke" pose to visualize the pose and detect touch with a virtual object.
## VIVE Plugin
After adding the "VIVE XR Hand Interaction Ext" to "Project Settings > XR Plugin-in Management > OpenXR > Android Tab > Interaction Profiles", you can use the following Input Action Pathes.
### Left Hand
- <ViveHandInteraction>{LeftHand}/pointerPose: Presents the left hand aim pose used for aiming at objects out of arm<72><6D>s reach.
- <ViveHandInteraction>{LeftHand}/pointerValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the aimed-at target is being fully interacted with left hand.
- <ViveHandInteraction>{LeftHand}/pointerReady: XR_TRUE indicates that the left fingers to perform the "aim_activate" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing an "aim_activate" gesture.
- <ViveHandInteraction>{LeftHand}/gripPose: Presents the left hand grip pose used for holding a large object in the user<65><72>s hand.
- <ViveHandInteraction>{LeftHand}/gripValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the left fist is tightly closed.
- <ViveHandInteraction>{LeftHand}/gripReady: XR_TRUE indicates that the left hand performing the grasp action is properly tracked by the hand tracking device and it is observed to be ready to perform or is performing the grasp action.
- <ViveHandInteraction>{LeftHand}/pinchPose: Presents the left hand pinch pose used for directly manipulating a small object using the pinch gesture.
- <ViveHandInteraction>{LeftHand}/pinchValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the left finger and thumb are touching each other.
- <ViveHandInteraction>{LeftHand}/pinchReady: XR_TRUE indicates that the left fingers used to perform the "pinch" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing a "pinch" gesture.
- <ViveHandInteraction>{LeftHand}/pokePose: Presents the left hand poke pose used for contact-based interactions using the motion of the hand or fingertip.
### Right Hand
- <ViveHandInteraction>{RightHand}/pointerPose: Presents the right hand aim pose used for aiming at objects out of arm<72><6D>s reach.
- <ViveHandInteraction>{RightHand}/pointerValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the aimed-at target is being fully interacted with right hand.
- <ViveHandInteraction>{RightHand}/pointerReady: XR_TRUE indicates that the right fingers to perform the "aim_activate" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing an "aim_activate" gesture.
- <ViveHandInteraction>{RightHand}/gripPose: Presents the right hand grip pose used for holding a large object in the user<65><72>s hand.
- <ViveHandInteraction>{RightHand}/gripValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the right fist is tightly closed.
- <ViveHandInteraction>{RightHand}/gripReady: XR_TRUE indicates that the right hand performing the grasp action is properly tracked by the hand tracking device and it is observed to be ready to perform or is performing the grasp action.
- <ViveHandInteraction>{RightHand}/pinchPose: Presents the right hand pinch pose used for directly manipulating a small object using the pinch gesture.
- <ViveHandInteraction>{RightHand}/pinchValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the right finger and thumb are touching each other.
- <ViveHandInteraction>{RightHand}/pinchReady: XR_TRUE indicates that the right fingers used to perform the "pinch" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing a "pinch" gesture.
- <ViveHandInteraction>{RightHand}/pokePose: Presents the right hand poke pose used for contact-based interactions using the motion of the hand or fingertip.
Refer to the <VIVE OpenXR sample path>/Samples/HandInteractionExt/HandInteractionExt.inputActions about the "Input Action Path" usage in the sample <VIVE OpenXR sample path>/Samples/HandInteractionExt/HandInteractionExt.unity.

View File

@@ -31,10 +31,11 @@ namespace VIVE.OpenXR.Hand
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Hand Interaction",
BuildTargetGroups = new[] { BuildTargetGroup.Android , BuildTargetGroup.Standalone},
Hidden = true,
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "HTC",
Desc = "Support for enabling the hand interaction profile. Will register the controller map for hand interaction if enabled.",
DocumentationLink = "..\\Documentation",
Desc = "Support for enabling the VIVE hand interaction profile. Will register the controller map for hand interaction if enabled.",
DocumentationLink = "https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_HTC_hand_interaction",
Version = "1.0.0",
OpenxrExtensionStrings = kOpenxrExtensionString,
Category = FeatureCategory.Interaction,
@@ -42,6 +43,7 @@ namespace VIVE.OpenXR.Hand
#endif
public class ViveHandInteraction : OpenXRInteractionFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandInteraction ";
StringBuilder m_sb = null;
StringBuilder sb {
@@ -50,8 +52,9 @@ namespace VIVE.OpenXR.Hand
return m_sb;
}
}
void DEBUG(StringBuilder msg) { Debug.Log(msg); }
void WARNING(StringBuilder msg) { Debug.LogWarning(msg); }
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
void WARNING(StringBuilder msg) { Debug.LogWarningFormat("{0} {1}", LOG_TAG, msg); }
#endregion
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_hand_interaction">12.69. XR_HTC_hand_interaction</see>.
@@ -68,6 +71,7 @@ namespace VIVE.OpenXR.Hand
/// </summary>
private const string profile = "/interaction_profiles/htc/hand_interaction";
#region Supported component paths
private const string leftHand = "/user/hand_htc/left";
private const string rightHand = "/user/hand_htc/right";
@@ -85,21 +89,22 @@ namespace VIVE.OpenXR.Hand
/// <summary>
/// Constant for a pose interaction binding '.../input/aim/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs.
/// </summary>
private const string pointerPose = "/input/aim/pose";
public const string pointerPose = "/input/aim/pose";
/// <summary>
/// Constant for a pose interaction binding '.../input/grip/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs.
/// </summary>
public const string devicePose = "/input/grip/pose";
#endregion
[Preserve, InputControlLayout(displayName = "VIVE Hand Interaction (OpenXR)", commonUsages = new[] { "LeftHand", "RightHand" }, isGenericTypeOfDevice = true)]
public class HandInteractionDevice : OpenXRDevice
{
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandInteraction.HandInteractionDevice";
void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
void DEBUG(string msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
/// <summary>
/// A [AxisControl](xref:UnityEngine.InputSystem.Controls.AxisControl) that represents the <see cref="ViveHandInteraction.selectValue"/> OpenXR binding.
/// A <see cref="AxisControl"/> representing the <see cref="ViveHandInteraction.selectValue"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(aliases = new[] { "selectAxis, pinchStrength" }, usage = "Select")]
public AxisControl selectValue { get; private set; }
@@ -122,29 +127,29 @@ namespace VIVE.OpenXR.Hand
[Preserve, InputControl(offset = 0, alias = "aimPose", usage = "Pointer")]
public PoseControl pointerPose { get; private set; }
/// <summary>
/// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) required for backwards compatibility with the XRSDK layouts. This represents the overall tracking state of the device. This value is equivalent to mapping devicePose/isTracked.
/// </summary>
[Preserve, InputControl(offset = 8, usage = "IsTracked")]
public ButtonControl isTracked { get; private set; }
/// <summary>
/// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) required for backwards compatibility with the XRSDK layouts. This represents the overall tracking state of the device. This value is equivalent to mapping devicePose/isTracked.
/// </summary>
[Preserve, InputControl(offset = 8)]
public ButtonControl isTracked { get; private set; }
/// <summary>
/// A [IntegerControl](xref:UnityEngine.InputSystem.Controls.IntegerControl) required for backwards compatibility with the XRSDK layouts. This represents the bit flag set to indicate what data is valid. This value is equivalent to mapping devicePose/trackingState.
/// </summary>
[Preserve, InputControl(offset = 12, usage = "TrackingState")]
public IntegerControl trackingState { get; private set; }
/// <summary>
/// A [IntegerControl](xref:UnityEngine.InputSystem.Controls.IntegerControl) required for backwards compatibility with the XRSDK layouts. This represents the bit flag set to indicate what data is valid. This value is equivalent to mapping devicePose/trackingState.
/// </summary>
[Preserve, InputControl(offset = 12)]
public IntegerControl trackingState { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the device position. For the VIVE Focus 3 device, this is both the device and the pointer position. This value is equivalent to mapping devicePose/position.
/// </summary>
[Preserve, InputControl(offset = 16, alias = "gripPosition")]
public Vector3Control devicePosition { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the device position. This value is equivalent to mapping devicePose/position.
/// </summary>
[Preserve, InputControl(offset = 16, alias = "gripPosition")]
public Vector3Control devicePosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the device orientation. For the VIVE Focus 3 device, this is both the device and the pointer rotation. This value is equivalent to mapping devicePose/rotation.
/// </summary>
[Preserve, InputControl(offset = 28, alias = "gripOrientation")]
public QuaternionControl deviceRotation { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the device orientation. This value is equivalent to mapping devicePose/rotation.
/// </summary>
[Preserve, InputControl(offset = 28, alias = "gripOrientation")]
public QuaternionControl deviceRotation { get; private set; }
/// <summary>
@@ -184,16 +189,15 @@ namespace VIVE.OpenXR.Hand
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
// Requires the eye tracking extension
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
sb.Clear().Append(LOG_TAG).Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
sb.Clear().Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
return false;
}
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
sb.Clear().Append(LOG_TAG).Append("OnInstanceCreate() " + m_XrInstance); DEBUG(sb);
sb.Clear().Append("OnInstanceCreate() " + m_XrInstance); DEBUG(sb);
return base.OnInstanceCreate(xrInstance);
}
@@ -205,14 +209,12 @@ namespace VIVE.OpenXR.Hand
/// </summary>
protected override void RegisterDeviceLayout()
{
sb.Clear().Append(LOG_TAG).Append("RegisterDeviceLayout() Layout: ").Append(kLayoutName)
.Append(", Product: ").Append(kDeviceLocalizedName);
DEBUG(sb);
sb.Clear().Append("RegisterDeviceLayout() ").Append(kLayoutName).Append(", product: ").Append(kDeviceLocalizedName); DEBUG(sb);
InputSystem.RegisterLayout(typeof(HandInteractionDevice),
kLayoutName,
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
kLayoutName,
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
}
/// <summary>
@@ -220,16 +222,36 @@ namespace VIVE.OpenXR.Hand
/// </summary>
protected override void UnregisterDeviceLayout()
{
sb.Clear().Append(LOG_TAG).Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb);
sb.Clear().Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb);
InputSystem.RemoveLayout(kLayoutName);
}
#if UNITY_XR_OPENXR_1_9_1
/// <summary>
/// Return interaction profile type. HandInteractionDevice profile is Device type.
/// </summary>
/// <returns>Interaction profile type.</returns>
protected override InteractionProfileType GetInteractionProfileType()
{
return typeof(HandInteractionDevice).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device;
}
/// <summary>
/// Return device layer out string used for registering device HandInteractionDevice in InputSystem.
/// </summary>
/// <returns>Device layout string.</returns>
protected override string GetDeviceLayoutName()
{
return kLayoutName;
}
#endif
/// <summary>
/// Registers action maps to Unity XR.
/// </summary>
protected override void RegisterActionMapsWithRuntime()
{
sb.Clear().Append(LOG_TAG).Append("RegisterActionMapsWithRuntime() Action map vivehandinteraction")
sb.Clear().Append("RegisterActionMapsWithRuntime() Action map vivehandinteraction")
.Append(", localizedName: ").Append(kDeviceLocalizedName)
.Append(", desiredInteractionProfile").Append(profile);
DEBUG(sb);

View File

@@ -0,0 +1,585 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine.Scripting;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.XR;
using UnityEngine.InputSystem.Controls;
using UnityEngine.XR.OpenXR;
using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections.Generic;
using UnityEngine.XR;
using UnityEngine.XR.OpenXR.Input;
using System.Text;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
using PoseControl = UnityEngine.InputSystem.XR.PoseControl;
#else
using PoseControl = UnityEngine.XR.OpenXR.Input.PoseControl;
#endif
namespace VIVE.OpenXR.Hand
{
/// <summary>
/// This <see cref="OpenXRInteractionFeature"/> enables the use of hand interaction profiles in OpenXR. It enables <see cref="ViveHandInteractionExt.kOpenxrExtensionString">XR_EXT_hand_interaction</see> in the underyling runtime.
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Hand Interaction Ext",
Hidden = true,
BuildTargetGroups = new[] { BuildTargetGroup.Android },
Company = "HTC",
Desc = "Support for enabling the KHR hand interaction profile. Will register the controller map for hand interaction if enabled.",
DocumentationLink = "https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_EXT_hand_interaction",
Version = "1.0.0",
OpenxrExtensionStrings = kOpenxrExtensionString,
Category = FeatureCategory.Interaction,
FeatureId = featureId)]
#endif
public class ViveHandInteractionExt : OpenXRInteractionFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandInteractionExt";
StringBuilder m_sb = null;
StringBuilder sb {
get {
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
void WARNING(StringBuilder msg) { Debug.LogWarningFormat("{0} {1}", LOG_TAG, msg); }
#endregion
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_hand_interaction">12.69. XR_HTC_hand_interaction</see>.
/// </summary>
public const string kOpenxrExtensionString = "XR_EXT_hand_interaction";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.hand.interaction.ext";
[Preserve, InputControlLayout(displayName = "VIVE Hand Interaction Ext (OpenXR)", commonUsages = new[] { "LeftHand", "RightHand" })]
public class HandInteractionExtDevice : XRController
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandInteractionExt.HandInteractionExtDevice";
void DEBUG(string msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#endregion
#region Action Path
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteractionExt.grip"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, aliases = new[] { "device", "gripPose" }, usage = "Device")]
public PoseControl devicePose { get; private set; }
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteractionExt.aim"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, alias = "aimPose", usage = "Pointer")]
public PoseControl pointer { get; private set; }
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteractionExt.pinchPose"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, usage = "Pinch")]
public PoseControl pinchPose { get; private set; }
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteractionExt.poke"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, alias = "indexTip", usage = "Poke")]
public PoseControl pokePose { get; private set; }
/// <summary>
/// A <see cref="AxisControl"/> representing information from the <see cref="ViveHandInteractionExt.graspValue"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(aliases = new[] { "gripValue" }, usage = "GraspValue")]
public AxisControl graspValue { get; private set; }
/// <summary>
/// A <see cref="ButtonControl"/> representing the <see cref="ViveHandInteractionExt.graspReady"/> OpenXR bindings, depending on handedness.
/// </summary>
[Preserve, InputControl(aliases = new[] { "isGrasped", "isGripped" }, usage = "GraspReady")]
public ButtonControl graspReady { get; private set; }
/// <summary>
/// A <see cref="AxisControl"/> representing information from the <see cref="ViveHandInteractionExt.pointerActivateValue"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(aliases = new[] { "pointerValue" }, usage = "PointerActivateValue")]
public AxisControl pointerActivateValue { get; private set; }
/// <summary>
/// A <see cref="ButtonControl"/> representing the <see cref="ViveHandInteractionExt.pointerActivateReady"/> OpenXR bindings, depending on handedness.
/// </summary>
[Preserve, InputControl(aliases = new[] { "isPointed", "pointerReady" }, usage = "PointerActivateReady")]
public ButtonControl pointerActivateReady { get; private set; }
/// <summary>
/// A <see cref="AxisControl"/> representing information from the <see cref="ViveHandInteractionExt.pinchValue"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(usage = "PinchValue")]
public AxisControl pinchValue { get; private set; }
/// <summary>
/// A <see cref="ButtonControl"/> representing the <see cref="ViveHandInteractionExt.pinchReady"/> OpenXR bindings, depending on handedness.
/// </summary>
[Preserve, InputControl(aliases = new[] { "isPinched" }, usage = "PinchReady")]
public ButtonControl pinchReady { get; private set; }
/// <summary>
/// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) required for backwards compatibility with the XRSDK layouts. This represents the overall tracking state of the device. This value is equivalent to mapping devicePose/isTracked.
/// </summary>
[Preserve, InputControl(offset = 2)]
new public ButtonControl isTracked { get; private set; }
/// <summary>
/// A [IntegerControl](xref:UnityEngine.InputSystem.Controls.IntegerControl) required for backwards compatibility with the XRSDK layouts. This represents the bit flag set to indicate what data is valid. This value is equivalent to mapping devicePose/trackingState.
/// </summary>
[Preserve, InputControl(offset = 4)]
new public IntegerControl trackingState { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the device position. This value is equivalent to mapping devicePose/position.
/// </summary>
[Preserve, InputControl(offset = 8, noisy = true, alias = "gripPosition")]
new public Vector3Control devicePosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the device orientation. This value is equivalent to mapping devicePose/rotation.
/// </summary>
[Preserve, InputControl(offset = 20, noisy = true, alias = "gripRotation")]
new public QuaternionControl deviceRotation { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the aim position. This value is equivalent to mapping pointer/position.
/// </summary>
[Preserve, InputControl(offset = 72, noisy = true)]
public Vector3Control pointerPosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the aim orientation. This value is equivalent to mapping pointer/rotation.
/// </summary>
[Preserve, InputControl(offset = 84, noisy = true)]
public QuaternionControl pointerRotation { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the pinch position. This value is equivalent to mapping pinchPose/position.
/// </summary>
[Preserve, InputControl(offset = 136, noisy = true)]
public Vector3Control pinchPosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the pinch orientation. This value is equivalent to mapping pinchPose/rotation.
/// </summary>
[Preserve, InputControl(offset = 148, noisy = true)]
public QuaternionControl pinchRotation { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the poke position. This value is equivalent to mapping pokePose/position.
/// </summary>
[Preserve, InputControl(offset = 200, noisy = true)]
public Vector3Control pokePosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the poke orientation. This value is equivalent to mapping pokePose/rotation.
/// </summary>
[Preserve, InputControl(offset = 212, noisy = true)]
public QuaternionControl pokeRotation { get; private set; }
#endregion
/// <summary>
/// Internal call used to assign controls to the the correct element.
/// </summary>
protected override void FinishSetup()
{
DEBUG("FinishSetup() interfaceName: " + description.interfaceName
+ ", deviceClass: " + description.deviceClass
+ ", product: " + description.product
+ ", serial: " + description.serial
+ ", version: " + description.version);
base.FinishSetup();
pointer = GetChildControl<PoseControl>("pointer");
pointerActivateValue = GetChildControl<AxisControl>("pointerActivateValue");
pointerActivateReady = GetChildControl<ButtonControl>("pointerActivateReady");
devicePose = GetChildControl<PoseControl>("devicePose");
graspValue = GetChildControl<AxisControl>("graspValue");
graspReady = GetChildControl<ButtonControl>("graspReady");
pinchPose = GetChildControl<PoseControl>("pinchPose");
pinchValue = GetChildControl<AxisControl>("pinchValue");
pinchReady = GetChildControl<ButtonControl>("pinchReady");
pokePose = GetChildControl<PoseControl>("pokePose");
}
}
/// <summary>
/// The interaction profile string used to reference the hand interaction input device.
/// </summary>
public const string profile = "/interaction_profiles/ext/hand_interaction_ext";
#region Supported component paths
/// <summary>
/// Constant for a pose interaction binding '.../input/aim/pose' OpenXR Input Binding.<br></br>
/// Typically used for aiming at objects out of arm¡¦s reach. When using a hand interaction profile, it is typically paired with <see cref="pointerActivateValue"/> to optimize aiming ray stability while performing the gesture.<br></br>
/// When using a controller interaction profile, the "aim" pose is typically paired with a trigger or a button for aim and fire operations.
/// </summary>
public const string aim = "/input/aim/pose";
/// <summary>
/// Constant for a float interaction binding '.../input/aim_activate_ext/value' OpenXR Input Binding.<br></br>
/// A 1D analog input component indicating that the user activated the action on the target that the user is pointing at with the aim pose.
/// </summary>
public const string pointerActivateValue = "/input/aim_activate_ext/value";
/// <summary>
/// Constant for a boolean interaction binding '.../input/aim_activate_ext/ready_ext' OpenXR Input Binding.<br></br>
/// A boolean input, where the value XR_TRUE indicates that the fingers to perform the "aim_activate" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing an "aim_activate" gesture.
/// </summary>
public const string pointerActivateReady = "/input/aim_activate_ext/ready_ext";
/// <summary>
/// Constant for a pose interaction binding '.../input/grip/pose' OpenXR Input Binding.<br></br>
/// Typically used for holding a large object in the user¡¦s hand. When using a hand interaction profile, it is typically paired with <see cref="graspValue"/> for the user to directly manipulate an object held in a hand.<br></br>
/// When using a controller interaction profile, the "grip" pose is typically paired with a "squeeze" button or trigger that gives the user the sense of tightly holding an object.
/// </summary>
public const string grip = "/input/grip/pose";
/// <summary>
/// Constant for a float interaction binding '.../input/grasp_ext/value' OpenXR Input Binding.<br></br>
/// A 1D analog input component indicating that the user is making a fist.
/// </summary>
public const string graspValue = "/input/grasp_ext/value";
/// <summary>
/// Constant for a boolean interaction binding '.../input/grasp_ext/ready_ext' OpenXR Input Binding.<br></br>
/// A boolean input, where the value XR_TRUE indicates that the hand performing the grasp action is properly tracked by the hand tracking device and it is observed to be ready to perform or is performing the grasp action.
/// </summary>
public const string graspReady = "/input/grasp_ext/ready_ext";
/// <summary>
/// Constant for a pose interaction binding '.../input/pinch_ext/pose' OpenXR Input Binding.<br></br>
/// Typically used for directly manipulating a small object using the pinch gesture. When using a hand interaction profile, it is typically paired with the <see cref="pinchValue"/>.<br></br>
/// When using a controller interaction profile, it is typically paired with a trigger manipulated with the index finger, which typically requires curling the index finger and applying pressure with the fingertip.
/// </summary>
public const string pinchPose = "/input/pinch_ext/pose";
/// <summary>
/// Constant for a float interaction binding '.../input/pinch_ext/value' OpenXR Input Binding.<br></br>
/// A 1D analog input component indicating the extent which the user is bringing their finger and thumb together to perform a "pinch" gesture.
/// </summary>
public const string pinchValue = "/input/pinch_ext/value";
/// <summary>
/// Constant for a boolean interaction binding '.../input/pinch_ext/ready_ext' OpenXR Input Binding.<br></br>
/// A boolean input, where the value XR_TRUE indicates that the fingers used to perform the "pinch" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing a "pinch" gesture.
/// </summary>
public const string pinchReady = "/input/pinch_ext/ready_ext";
/// <summary>
/// Constant for a pose interaction binding '.../input/poke_ext/pose' OpenXR Input Binding.<br></br>
/// Typically used for contact-based interactions using the motion of the hand or fingertip. It typically does not pair with other hand gestures or buttons on the controller. The application typically uses a sphere collider with the "poke" pose to visualize the pose and detect touch with a virtual object.
/// </summary>
public const string poke = "/input/poke_ext/pose";
#endregion
#pragma warning disable
private bool m_XrInstanceCreated = false;
#pragma warning restore
private XrInstance m_XrInstance = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The created instance.</param>
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
sb.Clear().Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
return false;
}
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
sb.Clear().Append("OnInstanceCreate() " + m_XrInstance); DEBUG(sb);
return base.OnInstanceCreate(xrInstance);
}
private const string kLayoutName = "ViveHandInteractionExt";
private const string kDeviceLocalizedName = "Vive Hand Interaction Ext OpenXR";
/// <summary>
/// Registers the <see cref="HandInteractionExtDevice"/> layout with the Input System.
/// </summary>
protected override void RegisterDeviceLayout()
{
sb.Clear().Append("RegisterDeviceLayout() ").Append(kLayoutName).Append(", product: ").Append(kDeviceLocalizedName); DEBUG(sb);
InputSystem.RegisterLayout(typeof(HandInteractionExtDevice),
kLayoutName,
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
}
/// <summary>
/// Removes the <see cref="HandInteractionExtDevice"/> layout from the Input System.
/// </summary>
protected override void UnregisterDeviceLayout()
{
sb.Clear().Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb);
InputSystem.RemoveLayout(kLayoutName);
}
#if UNITY_XR_OPENXR_1_9_1
/// <summary>
/// Return interaction profile type. HandInteractionExtDevice profile is Device type.
/// </summary>
/// <returns>Interaction profile type.</returns>
protected override InteractionProfileType GetInteractionProfileType()
{
return typeof(HandInteractionExtDevice).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device;
}
/// <summary>
/// Return device layer out string used for registering device HandInteractionExtDevice in InputSystem.
/// </summary>
/// <returns>Device layout string.</returns>
protected override string GetDeviceLayoutName()
{
return kLayoutName;
}
#endif
/// <summary>
/// Registers action maps to Unity XR.
/// </summary>
protected override void RegisterActionMapsWithRuntime()
{
sb.Clear().Append("RegisterActionMapsWithRuntime() Action map vivehandinteractionext")
.Append(", localizedName: ").Append(kDeviceLocalizedName)
.Append(", desiredInteractionProfile").Append(profile);
DEBUG(sb);
ActionMapConfig actionMap = new ActionMapConfig()
{
name = "vivehandinteractionext",
localizedName = kDeviceLocalizedName,
desiredInteractionProfile = profile,
manufacturer = "HTC",
serialNumber = "",
deviceInfos = new List<DeviceConfig>()
{
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics)(InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.Left),
userPath = UserPaths.leftHand
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics)(InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.Right),
userPath = UserPaths.rightHand
}
},
actions = new List<ActionConfig>()
{
// Grip Pose
new ActionConfig()
{
name = "devicePose",
localizedName = "Grasp Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Device"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = grip,
interactionProfileName = profile,
}
}
},
// Grip Value
new ActionConfig()
{
name = "graspValue",
localizedName = "Grip Axis",
type = ActionType.Axis1D,
usages = new List<string>()
{
"GraspValue"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = graspValue,
interactionProfileName = profile,
}
}
},
// Grip Ready
new ActionConfig()
{
name = "graspReady",
localizedName = "Is Grasped",
type = ActionType.Binary,
usages = new List<string>()
{
"GraspReady"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = graspReady,
interactionProfileName = profile,
},
}
},
// Aim Pose
new ActionConfig()
{
name = "pointer",
localizedName = "Aim Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Pointer"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = aim,
interactionProfileName = profile,
}
}
},
// Aim Value
new ActionConfig()
{
name = "pointerActivateValue",
localizedName = "Pointer Axis",
type = ActionType.Axis1D,
usages = new List<string>()
{
"PointerActivateValue"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pointerActivateValue,
interactionProfileName = profile,
}
}
},
// Aim Ready
new ActionConfig()
{
name = "pointerActivateReady",
localizedName = "Is Pointed",
type = ActionType.Binary,
usages = new List<string>()
{
"PointerActivateReady"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pointerActivateReady,
interactionProfileName = profile,
},
}
},
// Pinch Pose
new ActionConfig()
{
name = "pinchPose",
localizedName = "Pinch Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Pinch"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pinchPose,
interactionProfileName = profile,
}
}
},
// Pinch Value
new ActionConfig()
{
name = "pinchValue",
localizedName = "Pinch Axis",
type = ActionType.Axis1D,
usages = new List<string>()
{
"PinchValue"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pinchValue,
interactionProfileName = profile,
}
}
},
// Pinch Ready
new ActionConfig()
{
name = "pinchReady",
localizedName = "Is Pinched",
type = ActionType.Binary,
usages = new List<string>()
{
"PinchReady"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pinchReady,
interactionProfileName = profile,
},
}
},
// Poke Pose
new ActionConfig()
{
name = "pokePose",
localizedName = "Index Tip",
type = ActionType.Pose,
usages = new List<string>()
{
"Poke"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = poke,
interactionProfileName = profile,
}
}
},
}
};
AddActionMap(actionMap);
}
}
}

View File

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

View File

@@ -1,14 +1,18 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine;
using System.Runtime.InteropServices;
using System;
using System.Linq;
using UnityEngine.XR;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using AOT;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
@@ -18,7 +22,7 @@ namespace VIVE.OpenXR.Hand
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Hand Tracking",
BuildTargetGroups = new[] { BuildTargetGroup.Android , BuildTargetGroup.Standalone },
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "HTC",
Desc = "Support the Hand Tracking extension.",
DocumentationLink = "..\\Documentation",
@@ -28,10 +32,23 @@ namespace VIVE.OpenXR.Hand
#endif
public class ViveHandTracking : OpenXRFeature
{
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandTracking";
void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); }
void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
#region Log
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandTracking ";
StringBuilder m_sb = null;
StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
void DEBUG(String msg) { Debug.Log(LOG_TAG + msg); }
void DEBUG(StringBuilder msg) { Debug.Log(msg); }
void WARNING(StringBuilder msg) { Debug.LogWarning(msg); }
void ERROR(String msg) { Debug.LogError(LOG_TAG + msg); }
void ERROR(StringBuilder msg) { Debug.LogError(msg); }
#endregion
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_hand_tracking">12.29 XR_EXT_hand_tracking</see>.
@@ -99,13 +116,14 @@ namespace VIVE.OpenXR.Hand
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
WARNING("OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled.");
sb.Clear().Append(LOG_TAG).Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
return false;
}
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
DEBUG("OnInstanceCreate() " + m_XrInstance);
InputSystem.onAfterUpdate += UpdateCallback;
sb.Clear().Append(LOG_TAG).Append("OnInstanceCreate() ").Append(m_XrInstance); DEBUG(sb);
return GetXrFunctionDelegates(m_XrInstance);
}
@@ -115,9 +133,13 @@ namespace VIVE.OpenXR.Hand
/// <param name="xrInstance">The instance to destroy.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
DEBUG("OnInstanceDestroy() " + xrInstance);
if (m_XrInstance == xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
InputSystem.onAfterUpdate -= UpdateCallback;
}
sb.Clear().Append(LOG_TAG).Append("OnInstanceDestroy() ").Append(xrInstance); DEBUG(sb);
}
private XrSystemId m_XrSystemId = 0;
@@ -128,7 +150,7 @@ namespace VIVE.OpenXR.Hand
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
DEBUG("OnSystemChange() " + m_XrSystemId);
sb.Clear().Append(LOG_TAG).Append("OnSystemChange() ").Append(m_XrSystemId); DEBUG(sb);
}
private bool m_XrSessionCreated = false;
@@ -146,7 +168,7 @@ namespace VIVE.OpenXR.Hand
{
m_XrSession = xrSession;
m_XrSessionCreated = true;
DEBUG("OnSessionCreate() " + m_XrSession);
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() ").Append(m_XrSession); DEBUG(sb);
// Enumerate supported reference space types and create the XrSpace.
XrReferenceSpaceType[] spaces = new XrReferenceSpaceType[Enum.GetNames(typeof(XrReferenceSpaceType)).Count()];
@@ -158,7 +180,7 @@ namespace VIVE.OpenXR.Hand
spaces: out spaces[0]) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
DEBUG("OnSessionCreate() spaceCountOutput: " + spaceCountOutput);
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() spaceCountOutput: ").Append(spaceCountOutput); DEBUG(sb);
Array.Resize(ref spaces, (int)spaceCountOutput);
#pragma warning disable 0618
@@ -186,7 +208,7 @@ namespace VIVE.OpenXR.Hand
#pragma warning restore 0618
{
hasReferenceSpaceLocal = true;
DEBUG("OnSessionCreate() CreateReferenceSpace LOCAL: " + m_ReferenceSpaceLocal);
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() CreateReferenceSpace LOCAL: ").Append(m_ReferenceSpaceLocal); DEBUG(sb);
}
else
{
@@ -210,7 +232,7 @@ namespace VIVE.OpenXR.Hand
#pragma warning restore 0618
{
hasReferenceSpaceStage = true;
DEBUG("OnSessionCreate() CreateReferenceSpace STAGE: " + m_ReferenceSpaceStage);
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() CreateReferenceSpace STAGE: ").Append(m_ReferenceSpaceStage); DEBUG(sb);
}
else
{
@@ -220,7 +242,7 @@ namespace VIVE.OpenXR.Hand
}
else
{
ERROR("OnSessionCreate() EnumerateReferenceSpaces(" + spaceCountOutput + ") failed.");
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() EnumerateReferenceSpaces(").Append(spaceCountOutput).Append(") failed."); ERROR(sb);
}
}
else
@@ -233,7 +255,7 @@ namespace VIVE.OpenXR.Hand
{
hasLeftHandTracker = true;
leftHandTracker = value;
DEBUG("OnSessionCreate() leftHandTracker " + leftHandTracker);
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() leftHandTracker ").Append(leftHandTracker); DEBUG(sb);
}
}
{ // right hand tracker
@@ -241,7 +263,7 @@ namespace VIVE.OpenXR.Hand
{
hasRightHandTracker = true;
rightHandTracker = value;
DEBUG("OnSessionCreate() rightHandTracker " + rightHandTracker);
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() rightHandTracker ").Append(rightHandTracker); DEBUG(sb);
}
}
}
@@ -252,7 +274,7 @@ namespace VIVE.OpenXR.Hand
/// <param name="xrSession">The session ID to destroy.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
DEBUG("OnSessionDestroy() " + xrSession);
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() ").Append(xrSession); DEBUG(sb);
// Reference Space is binding with xrSession so we destroy the xrSpace when xrSession is destroyed.
if (hasReferenceSpaceLocal)
@@ -261,12 +283,12 @@ namespace VIVE.OpenXR.Hand
if (DestroySpace(m_ReferenceSpaceLocal) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
DEBUG("OnSessionDestroy() DestroySpace LOCAL " + m_ReferenceSpaceLocal);
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() DestroySpace LOCAL ").Append(m_ReferenceSpaceLocal); DEBUG(sb);
m_ReferenceSpaceLocal = 0;
}
else
{
ERROR("OnSessionDestroy() DestroySpace LOCAL " + m_ReferenceSpaceLocal + " failed.");
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() DestroySpace LOCAL ").Append(m_ReferenceSpaceLocal).Append(" failed."); ERROR(sb);
}
hasReferenceSpaceLocal = false;
}
@@ -276,12 +298,12 @@ namespace VIVE.OpenXR.Hand
if (DestroySpace(m_ReferenceSpaceStage) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
DEBUG("OnSessionDestroy() DestroySpace STAGE " + m_ReferenceSpaceStage);
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() DestroySpace STAGE ").Append(m_ReferenceSpaceStage); DEBUG(sb);
m_ReferenceSpaceStage = 0;
}
else
{
ERROR("OnSessionDestroy() DestroySpace STAGE " + m_ReferenceSpaceStage + " failed.");
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() DestroySpace STAGE ").Append(m_ReferenceSpaceStage).Append(" failed."); ERROR(sb);
}
hasReferenceSpaceStage = false;
}
@@ -291,11 +313,11 @@ namespace VIVE.OpenXR.Hand
{
if (DestroyHandTrackerEXT(leftHandTracker) == XrResult.XR_SUCCESS)
{
DEBUG("OnSessionDestroy() Left DestroyHandTrackerEXT " + leftHandTracker);
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() Left DestroyHandTrackerEXT ").Append(leftHandTracker); DEBUG(sb);
}
else
{
ERROR("OnSessionDestroy() Left DestroyHandTrackerEXT " + leftHandTracker + " failed.");
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() Left DestroyHandTrackerEXT ").Append(leftHandTracker).Append(" failed."); ERROR(sb);
}
hasLeftHandTracker = false;
}
@@ -303,11 +325,11 @@ namespace VIVE.OpenXR.Hand
{
if (DestroyHandTrackerEXT(rightHandTracker) == XrResult.XR_SUCCESS)
{
DEBUG("OnSessionDestroy() Right DestroyHandTrackerEXT " + rightHandTracker);
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() Right DestroyHandTrackerEXT ").Append(rightHandTracker); DEBUG(sb);
}
else
{
ERROR("OnSessionDestroy() Right DestroyHandTrackerEXT " + rightHandTracker + " failed.");
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() Right DestroyHandTrackerEXT ").Append(rightHandTracker).Append(" failed."); ERROR(sb);
}
hasRightHandTracker = false;
}
@@ -453,13 +475,13 @@ namespace VIVE.OpenXR.Hand
if (createInfo.hand == XrHandEXT.XR_HAND_LEFT_EXT && hasLeftHandTracker)
{
DEBUG("CreateHandTrackerEXT() Left tracker " + leftHandTracker + " already created.");
sb.Clear().Append(LOG_TAG).Append("CreateHandTrackerEXT() Left tracker ").Append(leftHandTracker).Append(" already created."); DEBUG(sb);
handTracker = leftHandTracker;
return XrResult.XR_SUCCESS;
}
if (createInfo.hand == XrHandEXT.XR_HAND_RIGHT_EXT && hasRightHandTracker)
{
DEBUG("CreateHandTrackerEXT() Right tracker " + rightHandTracker + " already created.");
sb.Clear().Append(LOG_TAG).Append("CreateHandTrackerEXT() Right tracker ").Append(rightHandTracker).Append(" already created."); DEBUG(sb);
handTracker = rightHandTracker;
return XrResult.XR_SUCCESS;
}
@@ -677,7 +699,7 @@ namespace VIVE.OpenXR.Hand
bool support = false;
for (int i = 0; i < spaceCountOutput; i++)
{
DEBUG("IsReferenceSpaceTypeSupported() supported space[" + i + "]: " + spaces[i]);
sb.Clear().Append(LOG_TAG).Append("IsReferenceSpaceTypeSupported() supported space[").Append(i).Append("]: ").Append(spaces[i]); DEBUG(sb);
if (spaces[i] == space) { support = true; }
}
@@ -720,7 +742,7 @@ namespace VIVE.OpenXR.Hand
sys_hand_tracking_prop_ptr = new IntPtr(offset);
handTrackingSystemProperties = (XrSystemHandTrackingPropertiesEXT)Marshal.PtrToStructure(sys_hand_tracking_prop_ptr, typeof(XrSystemHandTrackingPropertiesEXT));
DEBUG("IsHandTrackingSupported() XrSystemHandTrackingPropertiesEXT.supportsHandTracking: " + handTrackingSystemProperties.supportsHandTracking);
sb.Clear().Append(LOG_TAG).Append("IsHandTrackingSupported() XrSystemHandTrackingPropertiesEXT.supportsHandTracking: ").Append((UInt32)handTrackingSystemProperties.supportsHandTracking); DEBUG(sb);
ret = handTrackingSystemProperties.supportsHandTracking > 0;
}
else
@@ -736,7 +758,7 @@ namespace VIVE.OpenXR.Hand
{
if (!IsHandTrackingSupported())
{
ERROR("CreateHandTrackers() " + (isLeft ? "Left" : "Right") + " hand tracking is NOT supported.");
sb.Clear().Append(LOG_TAG).Append("CreateHandTrackers() ").Append((isLeft ? "Left" : "Right")).Append(" hand tracking is NOT supported."); ERROR(sb);
handTracker = 0;
return false;
}
@@ -748,7 +770,7 @@ namespace VIVE.OpenXR.Hand
createInfo.handJointSet = XrHandJointSetEXT.XR_HAND_JOINT_SET_DEFAULT_EXT;
var ret = CreateHandTrackerEXT(ref createInfo, out handTracker);
DEBUG("CreateHandTrackers() " + (isLeft ? "Left" : "Right") + " CreateHandTrackerEXT = " + ret);
sb.Clear().Append(LOG_TAG).Append("CreateHandTrackers() ").Append((isLeft ? "Left" : "Right")).Append(" CreateHandTrackerEXT = ").Append(ret); DEBUG(sb);
return ret == XrResult.XR_SUCCESS;
}
@@ -773,20 +795,52 @@ namespace VIVE.OpenXR.Hand
return true;
}
private int lastUpdateFrameL = -1, lastUpdateFrameR = -1;
private void UpdateCallback()
{
// Only allow updating poses once at BeforeRender & Dynamic per frame.
if (InputState.currentUpdateType == InputUpdateType.BeforeRender ||
InputState.currentUpdateType == InputUpdateType.Dynamic)
{
lastUpdateFrameL = -1;
lastUpdateFrameR = -1;
}
}
private bool AllowUpdate(bool isLeft)
{
bool allow;
if (isLeft)
{
allow = (lastUpdateFrameL != Time.frameCount);
lastUpdateFrameL = Time.frameCount;
}
else
{
allow = (lastUpdateFrameR != Time.frameCount);
lastUpdateFrameR = Time.frameCount;
}
return allow;
}
/// <summary>
/// Retrieves the <see cref="XrHandJointLocationEXT"> XrHandJointLocationEXT </see> data.
/// </summary>
/// <param name="isLeft">Left or right hand.</param>
/// <param name="handJointLocation">Output parameter to retrieve <see cref="XrHandJointLocationEXT"> XrHandJointLocationEXT </see> data.</param>
/// <param name="timestamp">The hand tracking data timestamp.</param>
/// <returns>True for valid data.</returns>
public bool GetJointLocations(bool isLeft, out XrHandJointLocationEXT[] handJointLocation)
public bool GetJointLocations(bool isLeft, out XrHandJointLocationEXT[] handJointLocation, out XrTime timestamp)
{
bool ret = false;
handJointLocation = isLeft ? jointLocationsL : jointLocationsR;
timestamp = m_frameState.predictedDisplayTime;
if (!AllowUpdate(isLeft)) { return true; }
bool ret = false;
if (isLeft && !hasLeftHandTracker) { return ret; }
if (!isLeft && !hasRightHandTracker) { return ret; }
OpenXRHelper.Trace.Begin("GetJointLocations");
TrackingOriginModeFlags origin = GetTrackingOriginMode();
if (origin == TrackingOriginModeFlags.Unknown || origin == TrackingOriginModeFlags.Unbounded) { return ret; }
XrSpace baseSpace = (origin == TrackingOriginModeFlags.Device ? m_ReferenceSpaceLocal : m_ReferenceSpaceStage);
@@ -831,6 +885,8 @@ namespace VIVE.OpenXR.Hand
locateInfo: locateInfo,
locations: ref locations) == XrResult.XR_SUCCESS)
{
timestamp = locateInfo.time;
if (locations.isActive)
{
if (IntPtr.Size == 4)
@@ -858,7 +914,19 @@ namespace VIVE.OpenXR.Hand
}
Marshal.FreeHGlobal(locations.jointLocations);
OpenXRHelper.Trace.End();
return ret;
}
/// <summary>
/// Retrieves the <see cref="XrHandJointLocationEXT"> XrHandJointLocationEXT </see> data.
/// </summary>
/// <param name="isLeft">Left or right hand.</param>
/// <param name="handJointLocation">Output parameter to retrieve <see cref="XrHandJointLocationEXT"> XrHandJointLocationEXT </see> data.</param>
/// <returns>True for valid data.</returns>
public bool GetJointLocations(bool isLeft, out XrHandJointLocationEXT[] handJointLocation)
{
return GetJointLocations(isLeft, out handJointLocation, out XrTime timestamp);
}
}
}

View File

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

View File

@@ -0,0 +1,154 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEditor;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine;
using System.Text;
#if UNITY_EDITOR
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.Interaction
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR - Interaction Group",
Category = "Interactions",
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "HTC",
Desc = "VIVE interaction profiles management.",
OpenxrExtensionStrings = kOpenxrExtensionString,
Version = "2.5.0",
FeatureId = featureId)]
#endif
public class ViveInteractions : OpenXRFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Interaction.ViveInteractions ";
static StringBuilder m_sb = null;
static StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
static void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
static void WARNING(StringBuilder msg) { Debug.LogWarningFormat("{0} {1}", LOG_TAG, msg); }
static void ERROR(StringBuilder msg) { Debug.LogErrorFormat("{0} {1}", LOG_TAG, msg); }
#endregion
public const string kOpenxrExtensionString = "";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.interactions";
#region OpenXR Life Cycle
#pragma warning disable
private bool m_XrInstanceCreated = false;
#pragma warning enable
private XrInstance m_XrInstance = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The created instance.</param>
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
m_XrInstance = xrInstance;
m_XrInstanceCreated = true;
sb.Clear().Append("OnInstanceCreate() ").Append(m_XrInstance); DEBUG(sb);
return true;
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyInstance">xrDestroyInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The instance to destroy.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
sb.Clear().Append("OnInstanceDestroy() ").Append(xrInstance).Append(", current: ").Append(m_XrInstance); DEBUG(sb);
if (m_XrInstance == xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
}
}
#pragma warning disable
private bool m_XrSessionCreated = false;
#pragma warning enable
private XrSession m_XrSession = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateSession">xrCreateSession</see> is done.
/// </summary>
/// <param name="xrSession">The created session ID.</param>
protected override void OnSessionCreate(ulong xrSession)
{
m_XrSession = xrSession;
m_XrSessionCreated = true;
sb.Clear().Append("OnSessionCreate() ").Append(m_XrSession); DEBUG(sb);
}
protected override void OnSessionEnd(ulong xrSession)
{
sb.Clear().Append("OnSessionEnd() ").Append(xrSession).Append(", current: ").Append(m_XrSession); DEBUG(sb);
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroySession">xrDestroySession</see> is done.
/// </summary>
/// <param name="xrSession">The session ID to destroy.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
sb.Clear().Append("OnSessionDestroy() ").Append(xrSession).Append(", current: ").Append(m_XrSession); DEBUG(sb);
if (m_XrSession == xrSession)
{
m_XrSessionCreated = false;
m_XrSession = 0;
}
}
private XrSystemId m_XrSystemId = 0;
/// <summary>
/// Called when the <see cref="XrSystemId">XrSystemId</see> retrieved by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystem">xrGetSystem</see> is changed.
/// </summary>
/// <param name="xrSystem">The system id.</param>
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
sb.Clear().Append("OnSystemChange() " + m_XrSystemId); DEBUG(sb);
}
#endregion
[SerializeField]
internal bool m_ViveHandInteraction = false;
/// <summary>
/// Checks if using <see href="https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_HTC_hand_interaction">XR_HTC_hand_interaction</see> or not.
/// </summary>
/// <returns>True for using.</returns>
public bool UseViveHandInteraction() { return m_ViveHandInteraction; }
[SerializeField]
internal bool m_ViveWristTracker = false;
/// <summary>
/// Checks if using <see href="https://business.vive.com/eu/product/vive-wrist-tracker/">VIVE Wrist Tracker</see> or not.
/// </summary>
/// <returns>True for using.</returns>
public bool UseViveWristTracker() { return m_ViveWristTracker; }
[SerializeField]
internal bool m_ViveXRTracker = false;
/// <summary>
/// Checks if using <see href="https://business.vive.com/eu/product/vive-ultimate-tracker/">VIVE Ultimate Tracker</see> or not.
/// </summary>
/// <returns>True for using.</returns>
public bool UseViveXrTracker() { return m_ViveXRTracker; }
[SerializeField]
internal bool m_KHRHandInteraction = false;
/// <summary>
/// Checks if using <see href="https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_EXT_hand_interaction">XR_EXT_hand_interaction</see> or not.
/// </summary>
/// <returns>True for using.</returns>
public bool UseKhrHandInteraction() { return m_KHRHandInteraction; }
}
}

View File

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

View File

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

View File

@@ -0,0 +1,76 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.XR.OpenXR.Features;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.Feature
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR MockRuntime",
Desc = "VIVE's mock runtime. Used with OpenXR MockRuntime to test features on Editor.",
Company = "HTC",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = kOpenxrExtensionString,
Version = "1.0.0",
BuildTargetGroups = new[] { BuildTargetGroup.Standalone },
FeatureId = featureId
)]
#endif
public class ViveMockRuntime : OpenXRFeature
{
public const string kOpenxrExtensionString = "";
[DllImport("ViveMockRuntime", EntryPoint = "HookGetInstanceProcAddr")]
public static extern IntPtr HookGetInstanceProcAddrFake(IntPtr func);
//AddRequiredFeature
[DllImport("ViveMockRuntime", EntryPoint = "AddRequiredFeature")]
public static extern void AddRequiredFeature(string featureName);
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.mockruntime";
public bool enableFuture = false;
public bool enableAnchor = false;
#region override functions
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
IntPtr nextProcAddr = func;
if (Application.isEditor)
{
Debug.Log("ViveMockRuntime: HookGetInstanceProcAddr");
try
{
AddRequiredFeature("Future");
AddRequiredFeature("Anchor");
nextProcAddr = HookGetInstanceProcAddrFake(nextProcAddr);
}
catch (DllNotFoundException ex)
{
Debug.LogError("ViveMockRuntime: DLL not found: " + ex.Message);
}
catch (EntryPointNotFoundException ex)
{
Debug.LogError("ViveMockRuntime: Function not found in DLL: " + ex.Message);
}
catch (Exception ex)
{
Debug.LogError("ViveMockRuntime: Unexpected error: " + ex.Message);
}
}
return nextProcAddr;
}
#endregion override functions
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
# 12.89. XR_HTC_passthrough
## Name String
XR_HTC_passthrough
## Revision
1
## New Object Types
- [XrPassthroughHTC](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrPassthroughHTC)
## New Enum Constants
[XrObjectType](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrObjectType) enumeration is extended with:
- XR_OBJECT_TYPE_PASSTHROUGH_HTC
[XrStructureType](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrStructureType) enumeration is extended with:
- XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC
- XR_TYPE_PASSTHROUGH_COLOR_HTC
- XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC
- XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC
## New Enums
- [XrPassthroughFormHTC](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrPassthroughFormHTC)
## New Structures
- [XrPassthroughCreateInfoHTC](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrPassthroughCreateInfoHTC)
- [XrPassthroughColorHTC](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrPassthroughColorHTC)
- [XrPassthroughMeshTransformInfoHTC](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrPassthroughMeshTransformInfoHTC)
- [XrCompositionLayerPassthroughHTC](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrCompositionLayerPassthroughHTC)
## New Functions
- [xrCreatePassthroughHTC](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#xrCreatePassthroughHTC)
- [xrDestroyPassthroughHTC](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#xrDestroyPassthroughHTC)
## VIVE Plugin
Enable "VIVE XR Passthrough" in "Project Settings > XR Plugin-in Management > OpenXR > Android Tab > OpenXR Feature Groups" to use the Passthrough feature provided by VIVE OpenXR plugin.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0fddf83b59e7c194493074db7cc7aebb
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,534 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
namespace VIVE.OpenXR.Passthrough
{
/// <summary>
/// The forms of passthrough layer.
/// </summary>
public enum PassthroughLayerForm
{
///<summary> Fullscreen Passthrough Form</summary>
Planar = 0,
///<summary> Projected Passthrough Form</summary>
Projected = 1
}
/// <summary>
/// The types of passthrough space.
/// </summary>
public enum ProjectedPassthroughSpaceType
{
///<summary>
/// XR_REFERENCE_SPACE_TYPE_VIEW at (0,0,0) with orientation (0,0,0,1)
///</summary>
Headlock = 0,
///<summary>
/// When TrackingOriginMode is TrackingOriginModeFlags.Floor:
/// XR_REFERENCE_SPACE_TYPE_STAGE at (0,0,0) with orientation (0,0,0,1)
///
/// When TrackingOriginMode is TrackingOriginModeFlags.Device:
/// XR_REFERENCE_SPACE_TYPE_LOCAL at (0,0,0) with orientation (0,0,0,1)
///
///</summary>
Worldlock = 1
}
// -------------------- 12.88. XR_HTC_passthrough --------------------
#region New Object Types
/// <summary>
/// An application can create an <see href="https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrPassthroughHTC">XrPassthroughHTC</see> handle by calling <see href="https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#xrCreatePassthroughHTC">xrCreatePassthroughHTC</see>. The returned passthrough handle can be subsequently used in API calls.
/// </summary>
public struct XrPassthroughHTC : IEquatable<UInt64>
{
private readonly UInt64 value;
public XrPassthroughHTC(UInt64 u)
{
value = u;
}
public static implicit operator UInt64(XrPassthroughHTC equatable)
{
return equatable.value;
}
public static implicit operator XrPassthroughHTC(UInt64 u)
{
return new XrPassthroughHTC(u);
}
public bool Equals(XrPassthroughHTC other)
{
return value == other.value;
}
public bool Equals(UInt64 other)
{
return value == other;
}
public override bool Equals(object obj)
{
return obj is XrPassthroughHTC && Equals((XrPassthroughHTC)obj);
}
public override int GetHashCode()
{
return value.GetHashCode();
}
public override string ToString()
{
return value.ToString();
}
public static bool operator ==(XrPassthroughHTC a, XrPassthroughHTC b) { return a.Equals(b); }
public static bool operator !=(XrPassthroughHTC a, XrPassthroughHTC b) { return !a.Equals(b); }
public static bool operator >=(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value >= b.value; }
public static bool operator <=(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value <= b.value; }
public static bool operator >(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value > b.value; }
public static bool operator <(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value < b.value; }
public static XrPassthroughHTC operator +(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value + b.value; }
public static XrPassthroughHTC operator -(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value - b.value; }
public static XrPassthroughHTC operator *(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value * b.value; }
public static XrPassthroughHTC operator /(XrPassthroughHTC a, XrPassthroughHTC b)
{
if (b.value == 0)
{
throw new DivideByZeroException();
}
return a.value / b.value;
}
}
#endregion
#region New Enums
/// <summary>
/// The XrPassthroughFormHTC enumeration identifies the form of the passthrough, presenting the passthrough fill the full screen or project onto a specified mesh.
/// </summary>
public enum XrPassthroughFormHTC
{
/// <summary>
/// Presents the passthrough with full of the entire screen..
/// </summary>
XR_PASSTHROUGH_FORM_PLANAR_HTC = 0,
/// <summary>
/// Presents the passthrough projecting onto a custom mesh.
/// </summary>
XR_PASSTHROUGH_FORM_PROJECTED_HTC = 1,
};
#endregion
#region New Structures
/// <summary>
/// The XrPassthroughCreateInfoHTC structure describes the information to create an <see cref="XrPassthroughCreateInfoHTC">XrPassthroughCreateInfoHTC</see> handle.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrPassthroughCreateInfoHTC
{
/// <summary>
/// The <see cref="XrStructureType">XrStructureType</see> of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// The form specifies the form of passthrough.
/// </summary>
public XrPassthroughFormHTC form;
/// <param name="in_type">The <see cref="XrStructureType">XrStructureType</see> of this structure.</param>
/// <param name="in_next">NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.</param>
/// <param name="in_facialTrackingType">An XrFacialTrackingTypeHTC which describes which type of facial tracking should be used for this handle.</param>
public XrPassthroughCreateInfoHTC(XrStructureType in_type, IntPtr in_next, XrPassthroughFormHTC in_form)
{
type = in_type;
next = in_next;
form = in_form;
}
};
/// <summary>
/// The application can specify the XrPassthroughColorHTC to adjust the alpha value of the passthrough. The range is between 0.0f and 1.0f, 1.0f means opaque.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrPassthroughColorHTC
{
/// <summary>
/// The XrStructureType of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// Next is NULL or a pointer to the next structure in a structure chain, such as XrPassthroughMeshTransformInfoHTC.
/// </summary>
public IntPtr next;
/// <summary>
/// The alpha value of the passthrough in the range [0, 1].
/// </summary>
public float alpha;
public XrPassthroughColorHTC(XrStructureType in_type, IntPtr in_next, float in_alpha)
{
type = in_type;
next = in_next;
alpha = in_alpha;
}
};
/// <summary>
/// The XrPassthroughMeshTransformInfoHTC structure describes the mesh and transformation.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrPassthroughMeshTransformInfoHTC
{
/// <summary>
/// The XrStructureType of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// Next is NULL or a pointer to the next structure in a structure chain.
/// </summary>
public IntPtr next;
/// <summary>
/// The count of vertices array in the mesh.
/// </summary>
public UInt32 vertexCount;
/// <summary>
/// An array of XrVector3f. The size of the array must be equal to vertexCount.
/// </summary>
public XrVector3f[] vertices;
/// <summary>
/// The count of indices array in the mesh.
/// </summary>
public UInt32 indexCount;
/// <summary>
/// An array of triangle indices. The size of the array must be equal to indexCount.
/// </summary>
public UInt32[] indices;
/// <summary>
/// The XrSpace that defines the projected passthrough's base space for transformations.
/// </summary>
public XrSpace baseSpace;
/// <summary>
/// The XrTime that defines the time at which the transform is applied.
/// </summary>
public XrTime time;
/// <summary>
/// The XrPosef that defines the pose of the mesh
/// </summary>
public XrPosef pose;
/// <summary>
/// The XrVector3f that defines the scale of the mesh
/// </summary>
public XrVector3f scale;
public XrPassthroughMeshTransformInfoHTC(XrStructureType in_type, IntPtr in_next, UInt32 in_vertexCount,
XrVector3f[] in_vertices, UInt32 in_indexCount, UInt32[] in_indices, XrSpace in_baseSpace, XrTime in_time,
XrPosef in_pose, XrVector3f in_scale)
{
type = in_type;
next = in_next;
vertexCount = in_vertexCount;
vertices = in_vertices;
indexCount = in_indexCount;
indices = in_indices;
baseSpace = in_baseSpace;
time = in_time;
pose = in_pose;
scale = in_scale;
}
};
/// <summary>
/// A pointer to XrCompositionLayerPassthroughHTC may be submitted in xrEndFrame as a pointer to the base structure XrCompositionLayerBaseHeader, in the desired layer order, to request the runtime to composite a passthrough layer into the final frame output.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrCompositionLayerPassthroughHTC
{
/// <summary>
/// The XrStructureType of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// Next is NULL or a pointer to the next structure in a structure chain, such as XrPassthroughMeshTransformInfoHTC.
/// </summary>
public IntPtr next;
/// <summary>
/// A bitmask of XrCompositionLayerFlagBits describing flags to apply to the layer.
/// </summary>
public XrCompositionLayerFlags layerFlags;
/// <summary>
/// The XrSpace that specifies the layer¡¦s space - must be XR_NULL_HANDLE.
/// </summary>
public XrSpace space;
/// <summary>
/// The XrPassthroughHTC previously created by xrCreatePassthroughHTC.
/// </summary>
public XrPassthroughHTC passthrough;
/// <summary>
/// The XrPassthroughColorHTC describing the color information with the alpha value of the passthrough layer.
/// </summary>
public XrPassthroughColorHTC color;
public XrCompositionLayerPassthroughHTC(XrStructureType in_type, IntPtr in_next, XrCompositionLayerFlags in_layerFlags,
XrSpace in_space, XrPassthroughHTC in_passthrough, XrPassthroughColorHTC in_color)
{
type = in_type;
next = in_next;
layerFlags = in_layerFlags;
space = in_space;
passthrough = in_passthrough;
color = in_color;
}
};
[StructLayout(LayoutKind.Sequential)]
public struct XrPassthroughConfigurationBaseHeaderHTC
{
public XrStructureType type;
public IntPtr next;
};
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct XrPassthroughConfigurationImageRateHTC
{
public XrStructureType type;
public IntPtr next;
public float srcImageRate;
public float dstImageRate;
};
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct XrPassthroughConfigurationImageQualityHTC
{
public XrStructureType type;
public IntPtr next;
public float scale;
};
[StructLayout(LayoutKind.Sequential)]
public struct XrEventDataPassthroughConfigurationImageRateChangedHTC
{
public XrStructureType type;
public IntPtr next;
public XrPassthroughConfigurationImageRateHTC fromImageRate;
public XrPassthroughConfigurationImageRateHTC toImageRate;
public XrEventDataPassthroughConfigurationImageRateChangedHTC(XrStructureType in_type, IntPtr in_next, XrPassthroughConfigurationImageRateHTC in_fromImageRate, XrPassthroughConfigurationImageRateHTC in_toImageRate)
{
type = in_type;
next = in_next;
fromImageRate = in_fromImageRate;
toImageRate = in_toImageRate;
}
public static XrEventDataPassthroughConfigurationImageRateChangedHTC identity
{
get
{
return new XrEventDataPassthroughConfigurationImageRateChangedHTC(
XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_CHANGED_HTC,
IntPtr.Zero,
new XrPassthroughConfigurationImageRateHTC { type = XrStructureType.XR_TYPE_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_HTC, next = IntPtr.Zero },
new XrPassthroughConfigurationImageRateHTC { type = XrStructureType.XR_TYPE_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_HTC, next = IntPtr.Zero }); // user is default present
}
}
public static bool Get(XrEventDataBuffer eventDataBuffer, out XrEventDataPassthroughConfigurationImageRateChangedHTC eventDataPassthroughConfigurationImageRate)
{
eventDataPassthroughConfigurationImageRate = identity;
if (eventDataBuffer.type == XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_CHANGED_HTC)
{
eventDataPassthroughConfigurationImageRate.next = eventDataBuffer.next;
eventDataPassthroughConfigurationImageRate.fromImageRate.type = (XrStructureType)BitConverter.ToUInt32(eventDataBuffer.varying, 0);
eventDataPassthroughConfigurationImageRate.fromImageRate.next = (IntPtr)BitConverter.ToInt64(eventDataBuffer.varying, 8);
eventDataPassthroughConfigurationImageRate.fromImageRate.srcImageRate = BitConverter.ToSingle(eventDataBuffer.varying, 16);
eventDataPassthroughConfigurationImageRate.fromImageRate.dstImageRate = BitConverter.ToSingle(eventDataBuffer.varying, 20);
eventDataPassthroughConfigurationImageRate.toImageRate.type = (XrStructureType)BitConverter.ToUInt32(eventDataBuffer.varying, 24);
eventDataPassthroughConfigurationImageRate.toImageRate.next = (IntPtr)BitConverter.ToInt64(eventDataBuffer.varying, 32);
eventDataPassthroughConfigurationImageRate.toImageRate.srcImageRate = BitConverter.ToSingle(eventDataBuffer.varying, 40);
eventDataPassthroughConfigurationImageRate.toImageRate.dstImageRate = BitConverter.ToSingle(eventDataBuffer.varying, 44);
return true;
}
return false;
}
};
[StructLayout(LayoutKind.Sequential)]
public struct XrEventDataPassthroughConfigurationImageQualityChangedHTC
{
public XrStructureType type;
public IntPtr next;
public XrPassthroughConfigurationImageQualityHTC fromImageQuality;
public XrPassthroughConfigurationImageQualityHTC toImageQuality;
public XrEventDataPassthroughConfigurationImageQualityChangedHTC(XrStructureType in_type, IntPtr in_next, XrPassthroughConfigurationImageQualityHTC in_fromImageQuality, XrPassthroughConfigurationImageQualityHTC in_toImageQuality)
{
type = in_type;
next = in_next;
fromImageQuality = in_fromImageQuality;
toImageQuality = in_toImageQuality;
}
public static XrEventDataPassthroughConfigurationImageQualityChangedHTC identity
{
get
{
return new XrEventDataPassthroughConfigurationImageQualityChangedHTC(
XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_QUALITY_CHANGED_HTC,
IntPtr.Zero,
new XrPassthroughConfigurationImageQualityHTC { type = XrStructureType.XR_TYPE_PASSTHROUGH_CONFIGURATION_IMAGE_QUALITY_HTC, next = IntPtr.Zero },
new XrPassthroughConfigurationImageQualityHTC { type = XrStructureType.XR_TYPE_PASSTHROUGH_CONFIGURATION_IMAGE_QUALITY_HTC, next = IntPtr.Zero }); // user is default present
}
}
public static bool Get(XrEventDataBuffer eventDataBuffer, out XrEventDataPassthroughConfigurationImageQualityChangedHTC ventDataPassthroughConfigurationImageQuality)
{
ventDataPassthroughConfigurationImageQuality = identity;
if (eventDataBuffer.type == XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_QUALITY_CHANGED_HTC)
{
ventDataPassthroughConfigurationImageQuality.next = eventDataBuffer.next;
ventDataPassthroughConfigurationImageQuality.fromImageQuality.type = (XrStructureType)BitConverter.ToUInt32(eventDataBuffer.varying, 0);
ventDataPassthroughConfigurationImageQuality.fromImageQuality.next = (IntPtr)BitConverter.ToInt64(eventDataBuffer.varying, 8);
ventDataPassthroughConfigurationImageQuality.fromImageQuality.scale = BitConverter.ToSingle(eventDataBuffer.varying, 16);
ventDataPassthroughConfigurationImageQuality.toImageQuality.type = (XrStructureType)BitConverter.ToUInt32(eventDataBuffer.varying, 24);
ventDataPassthroughConfigurationImageQuality.toImageQuality.next = (IntPtr)BitConverter.ToInt64(eventDataBuffer.varying, 32);
ventDataPassthroughConfigurationImageQuality.toImageQuality.scale = BitConverter.ToSingle(eventDataBuffer.varying, 40);
return true;
}
return false;
}
};
[StructLayout(LayoutKind.Sequential)]
public struct XrSystemPassthroughConfigurationPropertiesHTC
{
public XrStructureType type;
public IntPtr next;
public XrBool32 supportsImageRate;
public XrBool32 supportsImageQuality;
};
#endregion
#region New Functions
public static class VivePassthroughHelper
{
/// <summary>
/// The delegate function of <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreatePassthroughHTC">xrCreatePassthroughHTC</see>.
/// </summary>
/// <param name="session">An <see cref="XrSession">XrSession</see> in which the passthrough will be active.</param>
/// <param name="createInfo">createInfo is a pointer to an <see cref="XrPassthroughCreateInfoHTC">XrPassthroughCreateInfoHTC</see> structure containing information about how to create the passthrough.</param>
/// <param name="passthrough">passthrough is a pointer to a handle in which the created <see cref="XrPassthroughHTC">XrPassthroughHTC</see> is returned.</param>
/// <returns>XR_SUCCESS for success.</returns>
public delegate XrResult xrCreatePassthroughHTCDelegate(
XrSession session,
XrPassthroughCreateInfoHTC createInfo,
out XrPassthroughHTC passthrough);
/// <summary>
/// The delegate function of <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyPassthroughHTC">xrDestroyFacialTrackerHTC</see>.
/// </summary>
/// <param name="passthrough">passthrough is the <see cref="XrPassthroughHTC">XrPassthroughHTC</see> to be destroyed..</param>
/// <returns>XR_SUCCESS for success.</returns>
public delegate XrResult xrDestroyPassthroughHTCDelegate(
XrPassthroughHTC passthrough);
public delegate XrResult xrEnumeratePassthroughImageRatesHTCDelegate(
XrSession session,
[In] UInt32 imageRateCapacityInput,
ref UInt32 imageRateCountOutput,
[In, Out] XrPassthroughConfigurationImageRateHTC[] imageRates);
public delegate XrResult xrGetPassthroughConfigurationHTCDelegate(
XrSession session,
IntPtr/*ref XrPassthroughConfigurationBaseHeaderHTC*/ config);
public delegate XrResult xrSetPassthroughConfigurationHTCDelegate(
XrSession session,
IntPtr/*ref XrPassthroughConfigurationBaseHeaderHTC*/ config);
}
public static class VivePassthroughImageQualityChanged
{
public delegate void OnImageQualityChanged(float fromQuality, float toQuality);
public static void Listen(OnImageQualityChanged callback)
{
if (!allEventListeners.Contains(callback))
allEventListeners.Add(callback);
}
public static void Remove(OnImageQualityChanged callback)
{
if (allEventListeners.Contains(callback))
allEventListeners.Remove(callback);
}
public static void Send(float fromQuality, float toQuality)
{
int N = 0;
if (allEventListeners != null)
{
N = allEventListeners.Count;
for (int i = N - 1; i >= 0; i--)
{
OnImageQualityChanged single = allEventListeners[i];
try
{
single(fromQuality, toQuality);
}
catch (Exception e)
{
Debug.Log("Event : " + e.ToString());
allEventListeners.Remove(single);
Debug.Log("Event : A listener is removed due to exception.");
}
}
}
}
private static List<OnImageQualityChanged> allEventListeners = new List<OnImageQualityChanged>();
}
public static class VivePassthroughImageRateChanged
{
public delegate void OnImageRateChanged(float fromSrcImageRate, float fromDestImageRate, float toSrcImageRate, float toDestImageRate);
public static void Listen(OnImageRateChanged callback)
{
if (!allEventListeners.Contains(callback))
allEventListeners.Add(callback);
}
public static void Remove(OnImageRateChanged callback)
{
if (allEventListeners.Contains(callback))
allEventListeners.Remove(callback);
}
public static void Send(float fromSrcImageRate, float fromDestImageRate, float toSrcImageRate, float toDestImageRate)
{
int N = 0;
if (allEventListeners != null)
{
N = allEventListeners.Count;
for (int i = N - 1; i >= 0; i--)
{
OnImageRateChanged single = allEventListeners[i];
try
{
single(fromSrcImageRate, fromDestImageRate, toSrcImageRate, toDestImageRate);
}
catch (Exception e)
{
Debug.Log("Event : " + e.ToString());
allEventListeners.Remove(single);
Debug.Log("Event : A listener is removed due to exception.");
}
}
}
}
private static List<OnImageRateChanged> allEventListeners = new List<OnImageRateChanged>();
}
#endregion
}

View File

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

View File

@@ -16,7 +16,7 @@ using UnityEditor.XR.OpenXR.Features;
namespace VIVE.OpenXR
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Path Enumeration",
[OpenXRFeature(UiName = "VIVE XR Path Enumeration (Beta)",
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "HTC",
Desc = "The extension provides more flexibility for the user paths and input/output source paths related to an interaction profile. Developers can use this extension to obtain the path that the user has decided on.",

View File

@@ -0,0 +1,101 @@
# XR_EXT_plane_detection
## Name String
XR_EXT_plane_detection
## Revision
1
## Overview
The PlaneDetectionManager class provides functionalities for managing plane detection using the VIVE XR SDK. It includes methods to check feature support, create and destroy plane detectors, and helper functions for interacting with the plane detection extension.
## Plane Detection Workflow
1. Check Feature Support:
```csharp
bool isSupported = PlaneDetectionManager.IsSupported();
```
Ensure the plane detection feature is supported before attempting to create a plane detector.
1. Create Plane Detector:
```csharp
PlaneDetector planeDetector = PlaneDetectionManager.CreatePlaneDetector();
```
Create a plane detector instance to begin detecting planes.
1. Begin Plane Detection:
```csharp
XrResult result = planeDetector.BeginPlaneDetection();
```
Start the plane detection process.
1. Get Plane Detection State:
```csharp
XrPlaneDetectionStateEXT state = planeDetector.GetPlaneDetectionState();
```
Check the current state of the plane detection process.
1. Retrieve Plane Detections:
```csharp
List<PlaneDetectorLocation> locations;
XrResult result = planeDetector.GetPlaneDetections(out locations);
```
Retrieve the detected planes.
1. Get Plane Vertices:
```csharp
Plane plane = planeDetector.GetPlane(planeId);
```
Retrieve the vertices of a specific plane.
1. Destroy Plane Detector:
```csharp
PlaneDetectionManager.DestroyPlaneDetector(planeDetector);
```
Destroy the plane detector to release resources.
## Example Usage
Here's a basic example of how to use the PlaneDetectionManager to detect planes:
```csharp
if (PlaneDetectionManager.IsSupported())
{
var planeDetector = PlaneDetectionManager.CreatePlaneDetector();
if (planeDetector != null)
{
planeDetector.BeginPlaneDetection();
XrPlaneDetectionStateEXT state = planeDetector.GetPlaneDetectionState();
if (state == XrPlaneDetectionStateEXT.DONE_EXT)
{
List<PlaneDetectorLocation> locations;
if (planeDetector.GetPlaneDetections(out locations) == XrResult.XR_SUCCESS)
{
foreach (var location in locations)
{
// Process detected planes
}
}
}
PlaneDetectionManager.DestroyPlaneDetector(planeDetector);
}
}
```
This example checks if the plane detection feature is supported, creates a plane detector, begins the plane detection process, retrieves the detected planes, and finally destroys the plane detector to release resources.

View File

@@ -225,6 +225,7 @@ planeDetector);
#region override functions
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
ViveInterceptors.Instance.AddRequiredFunction("xrWaitFrame");
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
}
@@ -243,7 +244,7 @@ planeDetector);
//Debug.Log("OnInstanceCreate() " + m_XrInstance);
CommonWrapper.Instance.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr);
SpaceWrapper.Instance.OnInstanceCreate(xrInstance, CommonWrapper.Instance.GetInstanceProcAddr);
SpaceWrapper.Instance.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr);
return GetXrFunctionDelegates(m_XrInstance);
}
@@ -527,4 +528,4 @@ planeDetector);
}
#endregion
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,126 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Collections;
using UnityEngine;
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: AccessDebugTexture.cs
/// Role: General script
/// Responsibility: To assess the debug texture from SpectatorCameraBased.cs
/// </summary>
[RequireComponent(typeof(Renderer))]
public class AccessDebugTexture : MonoBehaviour
{
private static SpectatorCameraBased SpectatorCameraBased => SpectatorCameraBased.Instance;
// Some variables related to time definition for access SpectatorCameraBased class resources
private const float WaitSpectatorCameraBasedInitTime = 1.5f;
private const float WaitSpectatorCameraBasedPeriodTime = .5f;
private const float WaitSpectatorCameraBasedMaxTime = 10f;
private const int QuadrupleCheckIsRecordingCount = 4;
/// <summary>
/// The GameObject Renderer component
/// </summary>
private Renderer Renderer { get; set; }
/// <summary>
/// The default value of material in Renderer component
/// </summary>
private Material DefaultMaterial { get; set; }
/// <summary>
/// Set the Renderer material as debug material
/// </summary>
private void SetDebugMaterial()
{
Debug.Log("SetDebugMaterial");
if (SpectatorCameraBased)
{
if (SpectatorCameraBased.SpectatorCameraViewMaterial)
{
Renderer.material = SpectatorCameraBased.SpectatorCameraViewMaterial;
}
else
{
Debug.Log("No debug material set on SpectatorCameraBased.");
}
}
}
/// <summary>
/// Set the Renderer material as default material
/// </summary>
private void SetDefaultMaterial()
{
Debug.Log("SetDefaultMaterial");
Renderer.material = DefaultMaterial ? DefaultMaterial : null;
}
private IEnumerator Start()
{
float waitingTime = WaitSpectatorCameraBasedMaxTime;
bool getSpectatorCameraBased = false;
yield return new WaitForSeconds(WaitSpectatorCameraBasedInitTime);
do
{
if (!SpectatorCameraBased)
{
yield return new WaitForSeconds(WaitSpectatorCameraBasedPeriodTime);
waitingTime -= WaitSpectatorCameraBasedPeriodTime;
continue;
}
// Set -1 if accessed SpectatorCameraBased so we can break the while loop
waitingTime = -1;
getSpectatorCameraBased = true;
Renderer = GetComponent<Renderer>();
DefaultMaterial = Renderer.material;
SpectatorCameraBased.OnSpectatorStart += SetDebugMaterial;
SpectatorCameraBased.OnSpectatorStop += SetDefaultMaterial;
} while (waitingTime > 0);
if (!getSpectatorCameraBased)
{
Debug.Log($"Try to get SpectatorCameraBased " +
$"{WaitSpectatorCameraBasedMaxTime / WaitSpectatorCameraBasedPeriodTime} times but fail.");
Debug.Log("Destroy AccessDebugTexture now.");
Destroy(this);
yield break;
}
int quadrupleCheckCount = QuadrupleCheckIsRecordingCount;
while (quadrupleCheckCount > 0)
{
if (SpectatorCameraBased.IsRecording)
{
Debug.Log("Recording. Set debug material.");
SpectatorCameraBased.OnSpectatorStart?.Invoke();
break;
}
quadrupleCheckCount--;
yield return null;
Debug.Log("No recording. Keep default material.");
}
}
private void OnDestroy()
{
Renderer.material = DefaultMaterial ? DefaultMaterial : null;
if (SpectatorCameraBased)
{
SpectatorCameraBased.OnSpectatorStart -= SetDebugMaterial;
SpectatorCameraBased.OnSpectatorStop -= SetDefaultMaterial;
}
}
}
}

View File

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

View File

@@ -0,0 +1,146 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: SpectatorCameraBased.Editor.cs
/// Role: General script use in Unity Editor only
/// Responsibility: Display the SpectatorCameraBased.cs in Unity Inspector
/// </summary>
public partial class SpectatorCameraBased
{
#if UNITY_EDITOR
[SerializeField, Tooltip("State of debugging the spectator camera or not")]
private bool isDebugSpectatorCamera;
/// <summary>
/// State of debugging the spectator camera or not
/// </summary>
public bool IsDebugSpectatorCamera
{
get => isDebugSpectatorCamera;
set
{
isDebugSpectatorCamera = value;
if (!value)
{
IsRecording = false;
}
}
}
[CustomEditor(typeof(SpectatorCameraBased))]
public class SpectatorCameraBasedEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
// Just return if not "SpectatorCameraBased" class
if (!(target is SpectatorCameraBased))
{
return;
}
serializedObject.Update();
EditorGUI.BeginChangeCheck();
DrawGUI();
if (EditorGUI.EndChangeCheck())
{
Debug.Log("SpectatorCameraBased script is changed.");
EditorUtility.SetDirty(target);
}
serializedObject.ApplyModifiedProperties();
}
private void DrawGUI()
{
var script = (SpectatorCameraBased)target;
EditorGUI.BeginChangeCheck();
var currentSpectatorCameraViewMaterial = EditorGUILayout.ObjectField(
"Spectator Camera View Material",
script.SpectatorCameraViewMaterial,
typeof(Material),
false) as Material;
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, currentSpectatorCameraViewMaterial
? "Change Spectator Camera View Material"
: "Set Spectator Camera View Material as NULL");
script.SpectatorCameraViewMaterial = currentSpectatorCameraViewMaterial;
}
EditorGUI.BeginChangeCheck();
var currentIsDebugSpectatorCamera =
EditorGUILayout.Toggle("Active Spectator Camera Debugging", script.IsDebugSpectatorCamera);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Change IsDebugSpectatorCamera Value");
script.IsDebugSpectatorCamera = currentIsDebugSpectatorCamera;
}
if (script.IsDebugSpectatorCamera)
{
EditorGUI.BeginChangeCheck();
var currentIsRecording =
EditorGUILayout.Toggle("Active Spectator Camera Recording", script.IsRecording);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Change IsRecording Value");
script.IsRecording = currentIsRecording;
}
}
else
{
script.IsRecording = false;
}
if (script.IsDebugSpectatorCamera && GUILayout.Button("Load \"Simple_Demo_2\" scene for testing"))
{
if (DoesSceneExist("Simple_Demo_2"))
{
SceneManager.LoadScene("Simple_Demo_2");
}
else
{
Debug.LogWarning("Simple_Demo_2 scene not found. Please add it in build setting first.");
}
}
}
}
/// <summary>
/// Returns true if the scene 'name' exists and is in your Build settings, false otherwise.
/// </summary>
private static bool DoesSceneExist(string name)
{
if (string.IsNullOrEmpty(name))
{
return false;
}
for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
{
var scenePath = SceneUtility.GetScenePathByBuildIndex(i);
var lastSlash = scenePath.LastIndexOf("/", StringComparison.Ordinal);
var sceneName = scenePath.Substring(lastSlash + 1, scenePath.LastIndexOf(".", StringComparison.Ordinal) - lastSlash - 1);
if (string.Compare(name, sceneName, StringComparison.OrdinalIgnoreCase) == 0)
{
return true;
}
}
return false;
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,836 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
using VIVE.OpenXR.FirstPersonObserver;
using VIVE.OpenXR.SecondaryViewConfiguration;
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: SpectatorCameraBased.cs
/// Role: The base class cooperating with OpenXR SecondaryViewConfiguration Extension in Unity MonoBehaviour lifecycle (Singleton)
/// Responsibility: The handler responsible for the cooperation between the Unity MonoBehaviour lifecycle and OpenXR framework lifecycle
/// </summary>
public partial class SpectatorCameraBased : MonoBehaviour
{
private static SpectatorCameraBased _instance;
/// <summary>
/// SpectatorCameraBased static instance (Singleton)
/// </summary>
public static SpectatorCameraBased Instance => _instance;
#region Default value definition
/// <summary>
/// Camera texture width
/// </summary>
private const int TextureWidthDefault = 1920;
/// <summary>
/// Camera texture height
/// </summary>
private const int TextureHeightDefault = 1080;
/// <summary>
/// Camera GameObject Based Name
/// </summary>
private const string CameraGameObjectBasedName = "Spectator Camera Based Object";
/// <summary>
/// To define how long of the time (second) that the recording state is changed
/// </summary>
public const float RECORDING_STATE_CHANGE_THRESHOLD_IN_SECOND = 1f;
#endregion
#if !UNITY_EDITOR && UNITY_ANDROID
#region OpenXR Extension
/// <summary>
/// ViveFirstPersonObserver OpenXR extension
/// </summary>
private static ViveFirstPersonObserver FirstPersonObserver => ViveFirstPersonObserver.Instance;
/// <summary>
/// ViveSecondaryViewConfiguration OpenXR extension
/// </summary>
private static ViveSecondaryViewConfiguration SecondaryViewConfiguration => ViveSecondaryViewConfiguration.Instance;
#endregion
#region Locker and flag for multithread safety of texture updating
/// <summary>
/// Locker of NeedReInitTexture variables
/// </summary>
private readonly object _needReInitTextureLock = new object();
/// <summary>
/// State of whether re-init is needed for camera texture
/// </summary>
private bool NeedReInitTexture { get; set; }
/// <summary>
/// Locker of NeedUpdateTexture variables
/// </summary>
private readonly object _needUpdateTextureLock = new object();
/// <summary>
/// State of whether updated camera texture is needed
/// </summary>
private bool NeedUpdateTexture { get; set; }
#endregion
#endif
#region Spectator camera texture variables
/// <summary>
/// Camera texture size
/// </summary>
private Vector2 CameraTargetTextureSize { get; set; }
/// <summary>
/// Camera texture
/// </summary>
private RenderTexture CameraTargetTexture { get; set; }
#endregion
/// <summary>
/// GameObject of the spectator camera
/// </summary>
public GameObject SpectatorCameraGameObject { get; private set; }
/// <summary>
/// Camera component of the spectator camera
/// </summary>
public Camera SpectatorCamera { get; private set; }
public Camera MainCamera { get; private set; }
#region Debug Variables
[SerializeField] private Material spectatorCameraViewMaterial;
/// <summary>
/// Material that show the spectator camera view
/// </summary>
public Material SpectatorCameraViewMaterial
{
get => spectatorCameraViewMaterial;
set
{
spectatorCameraViewMaterial = value;
if (spectatorCameraViewMaterial && SpectatorCamera)
{
spectatorCameraViewMaterial.mainTexture = SpectatorCamera.targetTexture;
}
}
}
#endregion
private bool _followHmd;
/// <summary>
/// Is the spectator camera following the HMD or not
/// </summary>
public bool FollowHmd
{
get => _followHmd;
set
{
_followHmd = value;
if (SpectatorCamera.transform.parent != null &&
(SpectatorCamera.transform.localPosition != Vector3.zero ||
SpectatorCamera.transform.localRotation != Quaternion.identity))
{
Debug.Log("The local position or rotation should not be modified. Will reset the SpectatorCamera transform.");
SpectatorCamera.transform.localPosition = Vector3.zero;
SpectatorCamera.transform.localRotation = Quaternion.identity;
}
}
}
/// <summary>
/// State of allowing capture the 360 image or not
/// </summary>
public static bool IsAllowSpectatorCameraCapture360Image =>
#if !UNITY_EDITOR && UNITY_ANDROID
SecondaryViewConfiguration.IsAllowSpectatorCameraCapture360Image
#else
true
#endif
;
/// <summary>
/// SpectatorCameraBased init success or not
/// </summary>
private bool InitSuccess { get; set; }
/// <summary>
/// State of whether the app is not be focusing by the user
/// </summary>
private bool IsInBackground { get; set; }
[SerializeField, Tooltip("State of whether the spectator camera is recording currently")]
private bool isRecording;
/// <summary>
/// State of whether the spectator camera is recording currently
/// </summary>
public bool IsRecording
{
get => isRecording;
set
{
isRecording = value;
if (value)
{
if (IsPerformedStartRecordingCallback)
{
return;
}
IsPerformedStartRecordingCallback = true;
IsPerformedCloseRecordingCallback = false;
OnSpectatorStart?.Invoke();
}
else
{
if (IsPerformedCloseRecordingCallback ||
/* Because OpenXR periodically changes the spectator enabled flag, we need
to consider checking the state with a time delay so that we can make sure
it is changing for a long while or just periodically. */
Math.Abs(LastRecordingStateIsDisableTime - LastRecordingStateIsActiveTime) <
RECORDING_STATE_CHANGE_THRESHOLD_IN_SECOND)
{
return;
}
IsPerformedCloseRecordingCallback = true;
IsPerformedStartRecordingCallback = false;
OnSpectatorStop?.Invoke();
}
}
}
/// <summary>
/// The last time of the recording state that is active.
/// </summary>
public float LastRecordingStateIsActiveTime { get; private set; }
/// <summary>
/// The last time of the recording state that is disable.
/// </summary>
public float LastRecordingStateIsDisableTime { get; private set; }
/// <summary>
/// Flag denotes the callback is performed when the recording state changes to active
/// </summary>
private bool IsPerformedStartRecordingCallback { get; set; }
/// <summary>
/// Flag denotes the callback is performed when the recording state changes to disable
/// </summary>
private bool IsPerformedCloseRecordingCallback { get; set; }
#region Public variables for register the delegate callback functions
/// <summary>
/// Delegate type for spectator camera callbacks.
/// A delegate declaration that can encapsulate a method that takes no argument and returns void.
/// </summary>
public delegate void SpectatorCameraCallback();
/// <summary>
/// Delegate that custom code is executed when the spectator camera state changes to active.
/// </summary>
public SpectatorCameraCallback OnSpectatorStart;
/// <summary>
/// Delegate that custom code is executed when the spectator camera state changes to disable.
/// </summary>
public SpectatorCameraCallback OnSpectatorStop;
#endregion
#if !UNITY_EDITOR && UNITY_ANDROID
/// <summary>
/// Set the flag NeedReInitTexture as true
/// </summary>
/// <param name="size">The re-init texture size</param>
private void OnTextureSizeUpdated(Vector2 size)
{
lock (_needReInitTextureLock)
{
NeedReInitTexture = true;
CameraTargetTextureSize = size;
}
}
/// <summary>
/// Set the flag NeedUpdateTexture as true
/// </summary>
private void OnTextureUpdated()
{
lock (_needUpdateTextureLock)
{
NeedUpdateTexture = true;
}
}
/// <summary>
/// Init the projection matrix of spectator camera
/// </summary>
/// <param name="left">The position of the left vertical plane of the viewing frustum</param>
/// <param name="right">The position of the right vertical plane of the viewing frustum</param>
/// <param name="top">The position of the top horizontal plane of the viewing frustum</param>
/// <param name="bottom">The position of the bottom horizontal plane of the viewing frustum</param>
private void OnFovUpdated(float left, float right, float top, float bottom)
{
#region Modify the camera projection matrix (No need, just for reference)
/*
if (SpectatorCamera)
{
float far = SpectatorCamera.farClipPlane;
float near = SpectatorCamera.nearClipPlane;
SpectatorCamera.projectionMatrix = new Matrix4x4()
{
[0, 0] = 2f / (right - left),
[0, 1] = 0,
[0, 2] = (right + left) / (right - left),
[0, 3] = 0,
[1, 0] = 0,
[1, 1] = 2f / (top - bottom),
[1, 2] = (top + bottom) / (top - bottom),
[1, 3] = 0,
[2, 0] = 0,
[2, 1] = 0,
[2, 2] = -(far + near) / (far - near),
[2, 3] = -(2f * far * near) / (far - near),
[3, 0] = 0,
[3, 1] = 0,
[3, 2] = -1f,
[3, 3] = 0,
};
}
*/
#endregion
}
#endif
/// <summary>
/// Init the camera texture
/// </summary>
private void InitCameraTargetTexture()
{
if (CameraTargetTextureSize.x == 0 || CameraTargetTextureSize.y == 0)
{
#if !UNITY_EDITOR && UNITY_ANDROID
if (SecondaryViewConfiguration.TextureSize.x == 0 || SecondaryViewConfiguration.TextureSize.y == 0)
{
CameraTargetTextureSize = new Vector2(TextureWidthDefault, TextureHeightDefault);
}
else
{
CameraTargetTextureSize = SecondaryViewConfiguration.TextureSize;
}
#else
CameraTargetTextureSize = new Vector2(TextureWidthDefault, TextureHeightDefault);
#endif
}
if (!CameraTargetTexture)
{
// Texture is not create yet. Create it.
CameraTargetTexture = new RenderTexture
(
(int)CameraTargetTextureSize.x,
(int)CameraTargetTextureSize.y,
24,
RenderTextureFormat.ARGB32
);
InitPostProcessing();
return;
}
if (CameraTargetTexture.width == (int)CameraTargetTextureSize.x &&
CameraTargetTexture.height == (int)CameraTargetTextureSize.y)
{
// Texture size is same, just return.
return;
}
// Release the last time resource
SpectatorCamera.targetTexture = null;
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = null;
}
CameraTargetTexture.Release();
// Re-init
CameraTargetTexture.width = (int)CameraTargetTextureSize.x;
CameraTargetTexture.height = (int)CameraTargetTextureSize.y;
CameraTargetTexture.depth = 24;
CameraTargetTexture.format = RenderTextureFormat.ARGB32;
InitPostProcessing();
return;
void InitPostProcessing()
{
if (!CameraTargetTexture.IsCreated())
{
Debug.Log("The RenderTexture is not create yet. Will create it.");
bool created = CameraTargetTexture.Create();
Debug.Log($"Try to create RenderTexture: {created}");
if (created)
{
SpectatorCamera.targetTexture = CameraTargetTexture;
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = SpectatorCamera.targetTexture;
}
}
}
else
{
Debug.Log("The RenderTexture is already created.");
}
}
}
#if !UNITY_EDITOR && UNITY_ANDROID
/// <summary>
/// Update camera texture and then copy data of the camera texture to native texture space
/// </summary>
private void SecondViewTextureUpdate()
{
if (SecondaryViewConfiguration.MyTexture)
{
SpectatorCamera.enabled = true;
SpectatorCamera.Render();
SpectatorCamera.enabled = false;
if (SpectatorCamera.targetTexture)
{
// Copy Unity texture data to native texture
Graphics.CopyTexture(
SpectatorCamera.targetTexture,
0,
0,
SecondaryViewConfiguration.MyTexture,
0,
0);
}
else
{
Debug.LogError("Cannot copy the rendering data because the camera target texture is null!");
}
// Call native function that finishes the texture update
ViveSecondaryViewConfiguration.ReleaseSecondaryViewTexture();
}
else
{
Debug.LogError("Cannot copy the rendering data because SecondaryViewConfiguration.MyTexture is null!");
}
}
#endif
/// <summary>
/// Set the main texture of SpectatorCameraViewMaterial material as spectator camera texture
/// </summary>
private void SetCameraBasedTargetTexture2SpectatorCameraViewMaterial()
{
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = SpectatorCamera.targetTexture;
}
}
/// <summary>
/// Set the main texture of SpectatorCameraViewMaterial material as NULL value
/// </summary>
private void SetNull2SpectatorCameraViewMaterial()
{
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = null;
}
}
/// <summary>
/// Set whether the current camera viewpoint comes from HMD or not
/// </summary>
/// <param name="isViewFromHmd">The bool value represents the current view of whether the spectator camera is coming from hmd or not.</param>
public void SetViewFromHmd(bool isViewFromHmd)
{
#if !UNITY_EDITOR && UNITY_ANDROID
ViveSecondaryViewConfiguration.SetViewFromHmd(isViewFromHmd);
#endif
FollowHmd = isViewFromHmd;
}
/// <summary>
/// Get MainCamera in the current scene.
/// </summary>
/// <returns>The Camera component with MainCamera tag in the current scene</returns>
public static Camera GetMainCamera()
{
return Camera.main;
}
#region Unity life-cycle event
private void Start()
{
InitSuccess = false;
if (_instance != null && _instance != this)
{
Debug.Log("Destroy the SpectatorCameraBased");
if (SpectatorCameraViewMaterial)
{
Debug.Log("Copy SpectatorCameraBased material setting before destroy.");
_instance.SpectatorCameraViewMaterial = SpectatorCameraViewMaterial;
}
DestroyImmediate(this);
return;
}
else
{
_instance = this;
// To prevent this from being destroyed on load, check whether this gameObject has a parent;
// if so, set it to no game parent.
if (transform.parent != null)
{
transform.SetParent(null);
}
DontDestroyOnLoad(_instance.gameObject);
}
#if !UNITY_EDITOR && UNITY_ANDROID
if (SecondaryViewConfiguration && FirstPersonObserver)
{
// To check, "XR_MSFT_first_person_observer" is enough because it
// requires "XR_MSFT_secondary_view_configuration" to be enabled also.
if (!ViveFirstPersonObserver.IsExtensionEnabled())
{
Debug.LogWarning(
$"The OpenXR extension, {ViveSecondaryViewConfiguration.OPEN_XR_EXTENSION_STRING} " +
$"or {ViveFirstPersonObserver.OPEN_XR_EXTENSION_STRING}, is disabled. " +
"Please enable the extension before building the app.");
Debug.Log("Destroy the SpectatorCameraBased");
DestroyImmediate(this);
return;
}
SecondaryViewConfiguration.onTextureSizeUpdated += OnTextureSizeUpdated;
SecondaryViewConfiguration.onTextureUpdated += OnTextureUpdated;
SecondaryViewConfiguration.onFovUpdated += OnFovUpdated;
}
else
{
Debug.LogError(
"Cannot find the static instance of ViveSecondaryViewConfiguration or ViveFirstPersonObserver," +
" pls reopen the app later.");
Debug.Log("Destroy the SpectatorCameraBased");
DestroyImmediate(this);
return;
}
bool isSecondaryViewAlreadyEnabled = SecondaryViewConfiguration.IsEnabled;
Debug.Log(
$"The state of ViveSecondaryViewConfiguration.IsEnabled is {isSecondaryViewAlreadyEnabled}");
lock (_needReInitTextureLock)
{
NeedReInitTexture = isSecondaryViewAlreadyEnabled;
}
lock (_needUpdateTextureLock)
{
NeedUpdateTexture = isSecondaryViewAlreadyEnabled;
}
IsRecording = isSecondaryViewAlreadyEnabled;
#endif
SpectatorCameraGameObject = new GameObject(CameraGameObjectBasedName)
{
transform = { position = Vector3.zero, rotation = Quaternion.identity }
};
DontDestroyOnLoad(SpectatorCameraGameObject);
SpectatorCamera = SpectatorCameraGameObject.AddComponent<Camera>();
SpectatorCamera.stereoTargetEye = StereoTargetEyeMask.None;
MainCamera = GetMainCamera();
if (MainCamera != null)
{
// Set spectator camera to render after the main camera
SpectatorCamera.depth = MainCamera.depth + 1;
}
// Manually call Render() function once time at Start()
// because it can reduce the performance impact of first-time calls at SecondViewTextureUpdate
SpectatorCamera.Render();
SpectatorCamera.enabled = false;
FollowHmd = true;
IsInBackground = false;
IsPerformedStartRecordingCallback = false;
IsPerformedCloseRecordingCallback = false;
LastRecordingStateIsActiveTime = 0f;
LastRecordingStateIsDisableTime = 0f;
OnSpectatorStart += SetCameraBasedTargetTexture2SpectatorCameraViewMaterial;
OnSpectatorStop += SetNull2SpectatorCameraViewMaterial;
SceneManager.sceneLoaded += OnSceneLoaded;
#if !UNITY_EDITOR && UNITY_ANDROID
if (isSecondaryViewAlreadyEnabled)
{
OnSpectatorStart?.Invoke();
}
#endif
#if UNITY_EDITOR
OnSpectatorStart += () => { SpectatorCamera.enabled = true; };
OnSpectatorStop += () => { SpectatorCamera.enabled = false; };
CameraTargetTextureSize = new Vector2
(
TextureWidthDefault,
TextureHeightDefault
);
InitCameraTargetTexture();
SpectatorCamera.enabled = IsDebugSpectatorCamera && IsRecording;
#endif
InitSuccess = true;
}
private void LateUpdate()
{
if (!InitSuccess)
{
return;
}
if (IsInBackground)
{
return;
}
if (SpectatorCamera.transform.parent != null &&
(SpectatorCamera.transform.localPosition != Vector3.zero ||
SpectatorCamera.transform.localRotation != Quaternion.identity))
{
Debug.Log("The local position or rotation should not be modified. Will reset the SpectatorCamera transform.");
SpectatorCamera.transform.localPosition = Vector3.zero;
SpectatorCamera.transform.localRotation = Quaternion.identity;
}
if (FollowHmd)
{
if (MainCamera != null || (MainCamera = GetMainCamera()) != null)
{
Transform spectatorCameraTransform = SpectatorCamera.transform;
Transform hmdCameraTransform = MainCamera.transform;
spectatorCameraTransform.position = hmdCameraTransform.position;
spectatorCameraTransform.rotation = hmdCameraTransform.rotation;
}
}
else
{
#if !UNITY_EDITOR && UNITY_ANDROID
if (!SecondaryViewConfiguration.IsStopped)
{
Transform referenceTransform = SpectatorCamera.transform;
// Left-handed coordinate system (Unity) -> right-handed coordinate system (OpenXR)
var spectatorCameraPositionInOpenXRSpace = new XrVector3f
(
referenceTransform.position.x,
referenceTransform.position.y,
-referenceTransform.position.z
);
var spectatorCameraQuaternionInOpenXRSpace = new XrQuaternionf
(
referenceTransform.rotation.x,
referenceTransform.rotation.y,
-referenceTransform.rotation.z,
-referenceTransform.rotation.w
);
var spectatorCameraPose = new XrPosef
(
spectatorCameraQuaternionInOpenXRSpace,
spectatorCameraPositionInOpenXRSpace
);
ViveSecondaryViewConfiguration.SetNonHmdViewPose(spectatorCameraPose);
}
#endif
}
#if !UNITY_EDITOR && UNITY_ANDROID
IsRecording = SecondaryViewConfiguration.IsEnabled;
#endif
if (IsRecording)
{
LastRecordingStateIsActiveTime = Time.unscaledTime;
}
else
{
LastRecordingStateIsDisableTime = Time.unscaledTime;
if (!IsPerformedCloseRecordingCallback &&
/* Because OpenXR periodically changes the spectator enabled flag, we need
to consider checking the state with a time delay so that we can make sure
it is changing for a long while or just periodically. */
Math.Abs(LastRecordingStateIsDisableTime - LastRecordingStateIsActiveTime) >
RECORDING_STATE_CHANGE_THRESHOLD_IN_SECOND)
{
IsPerformedCloseRecordingCallback = true;
IsPerformedStartRecordingCallback = false;
OnSpectatorStop?.Invoke();
}
return;
}
#if !UNITY_EDITOR && UNITY_ANDROID
lock (_needReInitTextureLock)
{
if (NeedReInitTexture)
{
NeedReInitTexture = false;
InitCameraTargetTexture();
}
}
lock (_needUpdateTextureLock)
{
if (NeedUpdateTexture)
{
NeedUpdateTexture = false;
ViveSecondaryViewConfiguration.SetStateSecondaryViewImageDataReady(false);
SecondViewTextureUpdate();
ViveSecondaryViewConfiguration.SetStateSecondaryViewImageDataReady(true);
}
}
#endif
}
private void OnApplicationFocus(bool hasFocus)
{
if (!InitSuccess)
{
Debug.Log("Init unsuccessfully, just return from SpectatorCameraBased.OnApplicationFocus.");
return;
}
Debug.Log($"SpectatorCameraBased.OnApplicationFocus: {hasFocus}");
}
private void OnApplicationPause(bool pauseStatus)
{
if (!InitSuccess)
{
Debug.Log("Init unsuccessfully, just return from SpectatorCameraBased.OnApplicationPause.");
return;
}
Debug.Log($"SpectatorCameraBased.OnApplicationPause: {pauseStatus}");
#if !UNITY_EDITOR && UNITY_ANDROID
// Need to re-create the swapchain when recording is active and Unity app is resumed
if (SecondaryViewConfiguration.IsEnabled && !pauseStatus)
{
ViveSecondaryViewConfiguration.RequireReinitSwapchain();
}
#endif
IsInBackground = pauseStatus;
}
private void OnDestroy()
{
if (!InitSuccess)
{
Debug.Log("Init unsuccessfully, just return from SpectatorCameraBased.OnDestroy.");
return;
}
Debug.Log("SpectatorCameraBased.OnDestroy");
#if !UNITY_EDITOR && UNITY_ANDROID
SecondaryViewConfiguration.onTextureSizeUpdated -= OnTextureSizeUpdated;
SecondaryViewConfiguration.onTextureUpdated -= OnTextureUpdated;
#endif
if (SpectatorCamera)
{
SpectatorCamera.targetTexture = null;
}
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = null;
}
if (CameraTargetTexture)
{
Destroy(CameraTargetTexture);
}
#if !UNITY_EDITOR && UNITY_ANDROID
ViveSecondaryViewConfiguration.ReleaseAllResources();
#endif
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (!InitSuccess)
{
Debug.Log("Init unsuccessfully, just return from SpectatorCameraBased.OnSceneLoaded.");
return;
}
Debug.Log($"SpectatorCameraBased.OnSceneLoaded: {scene.name}");
MainCamera = GetMainCamera();
#if !UNITY_EDITOR && UNITY_ANDROID
if (!SecondaryViewConfiguration.IsStopped)
{
// Need to re-init the swapchain when recording is active and new Unity scene is loaded
ViveSecondaryViewConfiguration.RequireReinitSwapchain();
}
#endif
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,93 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: SecondaryViewConfiguration.Editor.cs
/// Role: General script use in Unity Editor only
/// Responsibility: Display the SecondaryViewConfiguration.cs in Unity Project Settings
/// </summary>
public partial class ViveSecondaryViewConfiguration
{
[field: SerializeField] internal bool IsAllowSpectatorCameraCapture360Image { get; set; }
[field: SerializeField] internal bool IsEnableDebugLog { get; set; }
#if UNITY_EDITOR
[CustomEditor(typeof(ViveSecondaryViewConfiguration))]
public class ViveSecondaryViewConfigurationEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
// Just return if not "ViveSecondaryViewConfiguration" class
if (!(target is ViveSecondaryViewConfiguration))
{
return;
}
serializedObject.Update();
DrawGUI();
serializedObject.ApplyModifiedProperties();
}
private void DrawGUI()
{
var script = (ViveSecondaryViewConfiguration)target;
EditorGUI.BeginChangeCheck();
var currentIsAllowSpectatorCameraCapture360Image =
EditorGUILayout.Toggle("Allow capture panorama", script.IsAllowSpectatorCameraCapture360Image);
if (EditorGUI.EndChangeCheck())
{
if (currentIsAllowSpectatorCameraCapture360Image && !PlayerSettings.enable360StereoCapture)
{
const string acceptButtonString =
"OK";
const string cancelButtonString =
"Cancel";
const string openCapture360ImageAdditionRequestTitle =
"Additional Request of Capturing 360 Image throughout the Spectator Camera";
const string openCapture360ImageAdditionRequestDescription =
"Allow the spectator camera to capture 360 images. Addition Request:\n" +
"1.) Open the \"enable360StereoCapture\" in the Unity Player Setting " +
"Page.";
bool acceptDialog1 = EditorUtility.DisplayDialog(
openCapture360ImageAdditionRequestTitle,
openCapture360ImageAdditionRequestDescription,
acceptButtonString,
cancelButtonString);
if (acceptDialog1)
{
PlayerSettings.enable360StereoCapture = true;
}
else
{
return;
}
}
Undo.RecordObject(target, "Modified ViveSecondaryViewConfiguration IsAllowSpectatorCameraCapture360Image");
EditorUtility.SetDirty(target);
script.IsAllowSpectatorCameraCapture360Image = currentIsAllowSpectatorCameraCapture360Image;
}
EditorGUI.BeginChangeCheck();
var currentIsEnableDebugLog =
EditorGUILayout.Toggle("Print log for debugging", script.IsEnableDebugLog);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Modified ViveSecondaryViewConfiguration IsEnableDebugLog");
EditorUtility.SetDirty(target);
script.IsEnableDebugLog = currentIsEnableDebugLog;
}
}
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,898 @@
// Copyright HTC Corporation All Rights Reserved.
using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: SecondaryViewConfiguration.cs
/// Role: OpenXR SecondaryViewConfiguration Extension Class
/// Responsibility: The OpenXR extension implementation and its lifecycles logic in OpenXR
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Spectator Camera (Beta)",
BuildTargetGroups = new[] { BuildTargetGroup.Android },
Company = "HTC",
Desc = "Allows an application to enable support for one or more secondary view configurations.",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = OPEN_XR_EXTENSION_STRING,
Version = "1.0.0",
FeatureId = FeatureId)]
#endif
public partial class ViveSecondaryViewConfiguration : OpenXRFeature
{
#region Varibles
private static ViveSecondaryViewConfiguration _instance;
/// <summary>
/// ViveSecondaryViewConfiguration static instance (Singleton).
/// </summary>
public static ViveSecondaryViewConfiguration Instance
{
get
{
if (_instance == null)
{
_instance =
OpenXRSettings.Instance.GetFeature<ViveSecondaryViewConfiguration>();
}
return _instance;
}
}
#region OpenXR variables related to definition
/// <summary>
/// The log identification.
/// </summary>
private const string LogTag = "VIVE.OpenXR.SecondaryViewConfiguration";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string FeatureId = "vive.openxr.feature.secondaryviewconfiguration";
/// <summary>
/// OpenXR specification <a href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_MSFT_secondary_view_configuration">12.122. XR_MSFT_secondary_view_configuration</a>.
/// </summary>
public const string OPEN_XR_EXTENSION_STRING = "XR_MSFT_secondary_view_configuration";
/// <summary>
/// The extension library name.
/// </summary>
private const string ExtLib = "libviveopenxr";
#endregion
#region OpenXR variables related to its life-cycle
/// <summary>
/// The flag represents whether the OpenXR loader created an instance or not.
/// </summary>
private bool XrInstanceCreated { get; set; } = false;
/// <summary>
/// The flag represents whether the OpenXR loader created a session or not.
/// </summary>
private bool XrSessionCreated { get; set; } = false;
/// <summary>
/// The flag represents whether the OpenXR loader started a session or not.
/// </summary>
private bool XrSessionStarted { get; set; } = false;
/// <summary>
/// The instance created through xrCreateInstance.
/// </summary>
private XrInstance XrInstance { get; set; } = 0;
/// <summary>
/// An XrSystemId is an opaque atom used by the runtime to identify a system.
/// </summary>
private XrSystemId XrSystemId { get; set; } = 0;
/// <summary>
/// A session represents an applications intention to display XR content to the user.
/// </summary>
private XrSession XrSession { get; set; } = 0;
/// <summary>
/// New possible session lifecycle states.
/// </summary>
private XrSessionState XrSessionNewState { get; set; } = XrSessionState.XR_SESSION_STATE_UNKNOWN;
/// <summary>
/// The previous state possible session lifecycle states.
/// </summary>
private XrSessionState XrSessionOldState { get; set; } = XrSessionState.XR_SESSION_STATE_UNKNOWN;
/// <summary>
/// The function delegate declaration of xrGetInstanceProcAddr.
/// </summary>
private OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr { get; set; }
#endregion
#region Variables related to handle agent functions
/// <summary>
/// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod".
/// </summary>
private delegate void SetSecondaryViewConfigurationStateDelegate(bool isEnabled);
/// <summary>
/// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod".
/// </summary>
private delegate void StopEnableSecondaryViewConfigurationDelegate(bool isStopped);
/// <summary>
/// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod".
/// </summary>
private delegate void SetTextureSizeDelegate(UInt32 width, UInt32 height);
/// <summary>
/// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod".
/// </summary>
private delegate void SetFovDelegate(XrFovf fov);
#endregion
#region Variables related to callback functions instantiation
/// <summary>
/// A delegate declaration can encapsulate the method that takes a Vector2 argument and returns void.
/// </summary>
public delegate void OnTextureSizeUpdatedDelegate(Vector2 size);
/// <summary>
/// The instantiation of the delegate OnTextureSizeUpdatedDelegate. This will be called when the texture size coming from the native plugin is updated.
/// </summary>
public OnTextureSizeUpdatedDelegate onTextureSizeUpdated;
/// <summary>
/// A delegate declaration can encapsulate the method that takes no argument and returns void.
/// </summary>
public delegate void OnTextureUpdatedDelegate();
/// <summary>
/// The instantiation of the delegate OnTextureUpdatedDelegate. This will be called when the texture coming from the native plugin is updated.
/// </summary>
public OnTextureUpdatedDelegate onTextureUpdated;
/// <summary>
/// A delegate declaration can encapsulate the method that takes four floating-point arguments, left, right, up, and down, respectively, and returns void.
/// </summary>
public delegate void OnFovUpdatedDelegate(float left, float right, float up, float down);
/// <summary>
/// The instantiation of the delegate OnFovUpdatedDelegate. This will be called when the fov setting coming from the native plugin is updated.
/// </summary>
public OnFovUpdatedDelegate onFovUpdated;
#endregion
#region Rendering and texture varibles
/// <summary>
/// The graphics backend that the current application is used.
/// </summary>
private static GraphicsAPI MyGraphicsAPI
{
get
{
return SystemInfo.graphicsDeviceType switch
{
GraphicsDeviceType.OpenGLES3 => GraphicsAPI.GLES3,
GraphicsDeviceType.Vulkan => GraphicsAPI.Vulkan,
_ => GraphicsAPI.Unknown
};
}
}
private Vector2 _textureSize;
/// <summary>
/// The value of texture size coming from the native plugin.
/// </summary>
public Vector2 TextureSize
{
get => _textureSize;
private set
{
_textureSize = value;
onTextureSizeUpdated?.Invoke(value);
}
}
private Texture _myTexture;
/// <summary>
/// The texture handler adopts the native 2D texture object coming from the native plugin.
/// </summary>
public Texture MyTexture
{
get => _myTexture;
private set
{
_myTexture = value;
onTextureUpdated?.Invoke();
}
}
#endregion
#region The variables (flag) represent the state related to this OpenXR extension
/// <summary>
/// The state of the second view configuration comes from runtime.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// The flag represents to whether the second view configuration is disabled or not.
/// </summary>
public bool IsStopped { get; set; }
/// <summary>
/// The flag represents to whether the "SpectatorCameraBased" script exists in the Unity scene.
/// </summary>
private bool IsExistSpectatorCameraBased { get; set; }
#endregion
#endregion
#region Function
#region OpenXR life-cycle functions
/// <summary>
/// Called after xrCreateInstance.
/// </summary>
/// <param name="xrInstance">Handle of the xrInstance.</param>
/// <returns>Returns true if successful. Returns false otherwise.</returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!IsExtensionEnabled())
{
Warning("OnInstanceCreate() " + OPEN_XR_EXTENSION_STRING + " is NOT enabled.");
return false;
}
var mySpectatorCameraBasedGameObject = GetSpectatorCameraBased();
IsExistSpectatorCameraBased = mySpectatorCameraBasedGameObject != null;
XrInstanceCreated = true;
XrInstance = xrInstance;
Debug("OnInstanceCreate() " + XrInstance);
if (!GetXrFunctionDelegates(XrInstance))
{
Error("Get function pointer of OpenXRFunctionPointerAccessor failed.");
return false;
}
Debug("Get function pointer of OpenXRFunctionPointerAccessor succeed.");
return base.OnInstanceCreate(xrInstance);
}
/// <summary>
/// Called after xrGetSystem
/// </summary>
/// <param name="xrSystem">Handle of the xrSystemId</param>
protected override void OnSystemChange(ulong xrSystem)
{
XrSystemId = xrSystem;
Debug("OnSystemChange() " + XrSystemId);
base.OnSystemChange(xrSystem);
}
/// <summary>
/// Called after xrCreateSession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionCreate(ulong xrSession)
{
XrSessionCreated = true;
XrSession = xrSession;
Debug("OnSessionCreate() " + XrSession);
base.OnSessionCreate(xrSession);
}
/// <summary>
/// Called after xrSessionBegin.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionBegin(ulong xrSession)
{
XrSessionStarted = true;
Debug("OnSessionBegin() " + XrSessionStarted);
base.OnSessionBegin(xrSession);
}
/// <summary>
/// Called when the OpenXR loader receives the XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED event from the runtime signaling that the XrSessionState has changed.
/// </summary>
/// <param name="oldState">Previous state.</param>
/// <param name="newState">New state.</param>
protected override void OnSessionStateChange(int oldState, int newState)
{
Debug("OnSessionStateChange() oldState: " + oldState + " newState:" + newState);
if (Enum.IsDefined(typeof(XrSessionState), oldState))
{
XrSessionOldState = (XrSessionState)oldState;
}
else
{
Warning("OnSessionStateChange() oldState undefined");
}
if (Enum.IsDefined(typeof(XrSessionState), newState))
{
XrSessionNewState = (XrSessionState)newState;
}
else
{
Warning("OnSessionStateChange() newState undefined");
}
base.OnSessionStateChange(oldState, newState);
}
/// <summary>
/// Called to hook xrGetInstanceProcAddr. Returning a different function pointer allows intercepting any OpenXR method.
/// </summary>
/// <param name="func">xrGetInstanceProcAddr native function pointer.</param>
/// <returns>Function pointer that Unity will use to look up OpenXR native functions.</returns>
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
Debug("HookGetInstanceProcAddr Start");
if (MyGraphicsAPI is GraphicsAPI.GLES3 or GraphicsAPI.Vulkan)
{
Debug($"The app graphics API is {MyGraphicsAPI}");
return SecondaryViewConfigurationInterceptOpenXRMethod
(
MyGraphicsAPI,
SystemInfo.graphicsUVStartsAtTop,
func,
SetSecondaryViewConfigurationState,
StopEnableSecondaryViewConfiguration,
SetTextureSize,
SetFov
);
}
Error(
"The render backend is not supported. Requires OpenGL or Vulkan backend for secondary view configuration feature.");
return base.HookGetInstanceProcAddr(func);
}
/// <summary>
/// Called before xrEndSession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionEnd(ulong xrSession)
{
XrSessionStarted = false;
Debug("OnSessionEnd() " + XrSession);
base.OnSessionEnd(xrSession);
}
/// <summary>
/// Called before xrDestroySession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
XrSessionCreated = false;
Debug("OnSessionDestroy() " + xrSession);
base.OnSessionDestroy(xrSession);
}
/// <summary>
/// Called before xrDestroyInstance.
/// </summary>
/// <param name="xrInstance">Handle of the xrInstance.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
XrInstanceCreated = false;
XrInstance = 0;
Debug("OnInstanceDestroy() " + xrInstance);
base.OnInstanceDestroy(xrInstance);
}
#endregion
#region Handle agent functions
/// <summary>
/// This function is defined as the "SetSecondaryViewConfigurationStateDelegate" delegate function.
/// <b>Please be careful that this function should ONLY be called by native plug-ins.
/// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE.</b>
/// </summary>
/// <param name="isEnabled">The state of the second view configuration comes from runtime. True if enabled. False otherwise.</param>
[MonoPInvokeCallback(typeof(SetSecondaryViewConfigurationStateDelegate))]
private static void SetSecondaryViewConfigurationState(bool isEnabled)
{
Instance.IsEnabled = isEnabled;
if (Instance.IsEnableDebugLog)
{
Debug($"SetSecondaryViewConfigurationState: Instance.IsEnabled set as {Instance.IsEnabled}");
}
}
/// <summary>
/// This function is defined as the "StopEnableSecondaryViewConfigurationDelegate" delegate function.
/// <b>Please be careful that this function should ONLY be called by native plug-ins.
/// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE.</b>
/// </summary>
/// <param name="isStopped">The flag refers to whether the second view configuration is disabled or not. True if the second view configuration is disabled. False otherwise.</param>
[MonoPInvokeCallback(typeof(StopEnableSecondaryViewConfigurationDelegate))]
private static void StopEnableSecondaryViewConfiguration(bool isStopped)
{
Instance.IsStopped = isStopped;
if (Instance.IsEnableDebugLog)
{
Debug($"StopEnableSecondaryViewConfiguration: Instance.IsStopped set as {Instance.IsStopped}");
}
}
/// <summary>
/// This function is defined as the "SetTextureSizeDelegate" delegate function.
/// <b>Please be careful that this function should ONLY be called by native plug-ins.
/// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE.</b>
/// </summary>
/// <param name="width">The texture width comes from runtime.</param>
/// <param name="height">The texture height comes from runtime.</param>
[MonoPInvokeCallback(typeof(SetTextureSizeDelegate))]
private static void SetTextureSize(uint width, uint height)
{
if (!Instance.IsExistSpectatorCameraBased)
{
CreateSpectatorCameraBased();
}
Instance.TextureSize = new Vector2(width, height);
if (Instance.IsEnableDebugLog)
{
Debug($"SetTextureSize width: {Instance.TextureSize.x}, height: {Instance.TextureSize.y}");
}
IntPtr texPtr = GetSecondaryViewTextureId(out uint imageIndex);
if (Instance.IsEnableDebugLog)
{
Debug($"SetTextureSize texPtr: {texPtr}, imageIndex: {imageIndex}");
}
if (texPtr == IntPtr.Zero)
{
Error($"SetTextureSize texPtr is invalid: {texPtr}");
return;
}
if (Instance.IsEnableDebugLog)
{
Debug("Get ptr successfully");
}
Instance.MyTexture = Texture2D.CreateExternalTexture(
(int)Instance.TextureSize.x,
(int)Instance.TextureSize.y,
TextureFormat.RGBA32,
false,
false,
texPtr);
#region For development usage (Just for reference)
/*
if (Instance.IsEnableDebugLog)
{
Debug("Create texture successfully");
Debug($"Instance.MyTexture.height: {Instance.MyTexture.height}");
Debug($"Instance.MyTexture.width: {Instance.MyTexture.width}");
Debug($"Instance.MyTexture.dimension: {Instance.MyTexture.dimension}");
Debug($"Instance.MyTexture.anisoLevel: {Instance.MyTexture.anisoLevel}");
Debug($"Instance.MyTexture.filterMode: {Instance.MyTexture.filterMode}");
Debug($"Instance.MyTexture.wrapMode: {Instance.MyTexture.wrapMode}");
Debug($"Instance.MyTexture.graphicsFormat: {Instance.MyTexture.graphicsFormat}");
Debug($"Instance.MyTexture.isReadable: {Instance.MyTexture.isReadable}");
Debug($"Instance.MyTexture.texelSize: {Instance.MyTexture.texelSize}");
Debug($"Instance.MyTexture.mipmapCount: {Instance.MyTexture.mipmapCount}");
Debug($"Instance.MyTexture.updateCount: {Instance.MyTexture.updateCount}");
Debug($"Instance.MyTexture.mipMapBias: {Instance.MyTexture.mipMapBias}");
Debug($"Instance.MyTexture.wrapModeU: {Instance.MyTexture.wrapModeU}");
Debug($"Instance.MyTexture.wrapModeV: {Instance.MyTexture.wrapModeV}");
Debug($"Instance.MyTexture.wrapModeW: {Instance.MyTexture.wrapModeW}");
Debug($"Instance.MyTexture.filterMode: {Instance.MyTexture.name}");
Debug($"Instance.MyTexture.hideFlags: {Instance.MyTexture.hideFlags}");
Debug($"Instance.MyTexture.GetInstanceID(): {Instance.MyTexture.GetInstanceID()}");
Debug($"Instance.MyTexture.GetType(): {Instance.MyTexture.GetType()}");
Debug($"Instance.MyTexture.GetNativeTexturePtr(): {Instance.MyTexture.GetNativeTexturePtr()}");
// Print imageContentsHash will cause an error
// Debug($"Instance.MyTexture.imageContentsHash: {Instance.MyTexture.imageContentsHash}");
}
*/
#endregion
}
/// <summary>
/// This function is defined as the "SetFovDelegate" delegate function.
/// <b>Please be careful that this function should ONLY be called by native plug-ins.
/// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE.</b>
/// </summary>
/// <param name="fov">The fov value comes from runtime.</param>
[MonoPInvokeCallback(typeof(SetFovDelegate))]
private static void SetFov(XrFovf fov)
{
if (Instance.IsEnableDebugLog)
{
Debug($"fov.AngleDown {fov.angleDown}");
Debug($"fov.AngleLeft {fov.angleLeft}");
Debug($"fov.AngleRight {fov.angleRight}");
Debug($"fov.AngleUp {fov.angleUp}");
}
Instance.onFovUpdated?.Invoke(fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown);
}
#endregion
#region C++ interop functions
/// <summary>
/// Call this function to trigger the native plug-in that gets a specific OpenXR function for services to the
/// Unity engine, such as dispatching the Unity data to runtime and returning the data from runtime to the
/// Unity engine.
/// </summary>
/// <param name="xrInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <param name="xrGetInstanceProcAddrFuncPtr">Accessor for xrGetInstanceProcAddr function pointer.</param>
/// <returns>Return true if get successfully. False otherwise.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "secondary_view_configuration_get_function_address")]
private static extern bool SecondaryViewConfigurationGetFunctionAddress
(
XrInstance xrInstance,
IntPtr xrGetInstanceProcAddrFuncPtr
);
/// <summary>
/// Call this function to dispatch/hook all OpenXR functions to native plug-ins.
/// </summary>
/// <param name="graphicsAPI">The graphics backend adopted in the Unity engine.</param>
/// <param name="graphicsUVStartsAtTop">The bool value represents whether the texture UV coordinate convention for this platform has Y starting at the top of the image.</param>
/// <param name="func">xrGetInstanceProcAddr native function pointer.</param>
/// <param name="setSecondaryViewConfigurationStateDelegate">The delegate function pointer that functions types as "SetSecondaryViewConfigurationStateDelegate".</param>
/// <param name="stopEnableSecondaryViewConfigurationDelegate">The delegate function pointer that functions types as "StopEnableSecondaryViewConfigurationDelegate".</param>
/// <param name="setTextureSizeDelegate">The delegate function pointer that functions types as "SetTextureSizeDelegate".</param>
/// <param name="setFovDelegate">The delegate function pointer that functions types as "SetFovDelegate".</param>
/// <returns></returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "secondary_view_configuration_intercept_openxr_method")]
private static extern IntPtr SecondaryViewConfigurationInterceptOpenXRMethod
(
GraphicsAPI graphicsAPI,
bool graphicsUVStartsAtTop,
IntPtr func,
[MarshalAs(UnmanagedType.FunctionPtr)]
SetSecondaryViewConfigurationStateDelegate setSecondaryViewConfigurationStateDelegate,
[MarshalAs(UnmanagedType.FunctionPtr)]
StopEnableSecondaryViewConfigurationDelegate stopEnableSecondaryViewConfigurationDelegate,
[MarshalAs(UnmanagedType.FunctionPtr)] SetTextureSizeDelegate setTextureSizeDelegate,
[MarshalAs(UnmanagedType.FunctionPtr)] SetFovDelegate setFovDelegate
);
/// <summary>
/// Call this function to get the current swapchain image handler (its ID and memory address).
/// </summary>
/// <param name="imageIndex">The current handler index.</param>
/// <returns>The current handler memory address.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "get_secondary_view_texture_id")]
private static extern IntPtr GetSecondaryViewTextureId
(
out UInt32 imageIndex
);
/// <summary>
/// Call this function to tell native plug-in submit the swapchain image immediately.
/// </summary>
/// <returns>Return true if submit the swapchain image successfully. False otherwise.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "release_secondary_view_texture")]
public static extern bool ReleaseSecondaryViewTexture();
/// <summary>
/// Call this function to release all resources in native plug-in. Please be careful that this function should
/// ONLY call in the Unity "OnDestroy" lifecycle event in the class "SpectatorCameraBased".
/// </summary>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "release_all_resources")]
public static extern void ReleaseAllResources();
/// <summary>
/// Call this function if requiring swapchain re-initialization. The native plug-in will set a re-initialization
/// flag. Once the secondary view is enabled after that, the swapchain will re-init immediately.
/// </summary>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "require_reinit_swapchain")]
public static extern void RequireReinitSwapchain();
/// <summary>
/// Call this function to tell the native plug-in where the current spectator camera source comes from.
/// </summary>
/// <param name="isViewFromHmd">Please set true if the source comes from hmd. Otherwise, please set false.</param>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "set_view_from_hmd")]
public static extern void SetViewFromHmd(bool isViewFromHmd);
/// <summary>
/// Call this function to tell the non-hmd pose to the native plug-in.
/// </summary>
/// <param name="pose">The current non-hmd pose</param>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "set_non_hmd_view_pose")]
public static extern void SetNonHmdViewPose(XrPosef pose);
/// <summary>
/// Call this function to tell the native plug-in whether the texture data is ready or not.
/// </summary>
/// <param name="isReady">The texture data written by Unity Engine is ready or not.</param>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "set_state_secondary_view_image_data_ready")]
public static extern void SetStateSecondaryViewImageDataReady(bool isReady);
#endregion
#region Utilities functions
/// <summary>
/// Check ViveSecondaryViewConfiguration extension is enabled or not.
/// </summary>
/// <returns>Return true if enabled. False otherwise.</returns>
public static bool IsExtensionEnabled()
{
#if UNITY_2022_1_OR_NEWER
return OpenXRRuntime.IsExtensionEnabled(OPEN_XR_EXTENSION_STRING);
#else
// Does not support 2021 or lower
return false;
#endif
}
/// <summary>
/// Get the OpenXR function via XrInstance.
/// </summary>
/// <param name="xrInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <returns>Return true if get successfully. False otherwise.</returns>
private bool GetXrFunctionDelegates(XrInstance xrInstance)
{
if (xrGetInstanceProcAddr != IntPtr.Zero)
{
Debug("Get function pointer of openXRFunctionPointerAccessor.");
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(xrGetInstanceProcAddr,
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
if (XrGetInstanceProcAddr == null)
{
Error(
"Get function pointer of openXRFunctionPointerAccessor failed due to the XrGetInstanceProcAddr is null.");
return false;
}
}
else
{
Error(
"Get function pointer of openXRFunctionPointerAccessor failed due to the xrGetInstanceProcAddr is null.");
return false;
}
Debug("Try to get the function pointer for XR_MSFT_secondary_view_configuration.");
return SecondaryViewConfigurationGetFunctionAddress(xrInstance, xrGetInstanceProcAddr);
#region Get function in C# (Just for reference)
/* if (GetOpenXRDelegateFunction(
XrGetInstanceProcAddr,
xrInstance,
"xrEnumerateViewConfigurations",
out _xrEnumerateViewConfigurations) is false)
{
Error("Get delegate function of XrEnumerateViewConfigurations failed.");
return false;
} */
#endregion
}
/// <summary>
/// Get the specific OpenXR function.
/// </summary>
/// <param name="openXRFunctionPointerAccessor">The function pointer accessor provide by OpenXR.</param>
/// <param name="openXRInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <param name="functionName">The specific OpenXR function.</param>
/// <param name="delegateFunction">Override value. The specific OpenXR function.</param>
/// <typeparam name="T">The class of the delegate function.</typeparam>
/// <returns>Return true if get successfully. False otherwise.</returns>
private static bool GetOpenXRDelegateFunction<T>
(
in OpenXRHelper.xrGetInstanceProcAddrDelegate openXRFunctionPointerAccessor,
in XrInstance openXRInstance,
in string functionName,
out T delegateFunction
) where T : class
{
delegateFunction = default(T);
if (openXRFunctionPointerAccessor == null || openXRInstance == 0 || string.IsNullOrEmpty(functionName))
{
Error($"Get OpenXR delegate function, {functionName}, failed due to the invalid parameter(s).");
return false;
}
XrResult getFunctionState = openXRFunctionPointerAccessor(openXRInstance, functionName, out IntPtr funcPtr);
bool funcPtrIsNull = funcPtr == IntPtr.Zero;
Debug("Get OpenXR delegate function, " + functionName + ", state: " + getFunctionState);
Debug("Get OpenXR delegate function, " + functionName + ", funcPtrIsNull: " + funcPtrIsNull);
if (getFunctionState != XrResult.XR_SUCCESS || funcPtrIsNull)
{
Error(
$"Get OpenXR delegate function, {functionName}, failed due to the native error or invalid return.");
return false;
}
try
{
delegateFunction = Marshal.GetDelegateForFunctionPointer(funcPtr, typeof(T)) as T;
}
catch (Exception e)
{
Error($"Get OpenXR delegate function, {functionName}, failed due to the exception: {e.Message}");
return false;
}
Debug($"Get OpenXR delegate function, {functionName}, succeed.");
return true;
}
/// <summary>
/// Print log with tag "VIVE.OpenXR.SecondaryViewConfiguration".
/// </summary>
/// <param name="msg">The log you want to print.</param>
private static void Debug(string msg)
{
UnityEngine.Debug.Log(LogTag + " " + msg);
}
/// <summary>
/// Print warning message with tag "VIVE.OpenXR.SecondaryViewConfiguration".
/// </summary>
/// <param name="msg">The warning message you want to print.</param>
private static void Warning(string msg)
{
UnityEngine.Debug.LogWarning(LogTag + " " + msg);
}
/// <summary>
/// Print an error message with the tag "VIVE.OpenXR.SecondaryViewConfiguration."
/// </summary>
/// <param name="msg">The error message you want to print.</param>
private static void Error(string msg)
{
UnityEngine.Debug.LogError(LogTag + " " + msg);
}
/// <summary>
/// Get the SpectatorCameraBased component in the current Unity scene.
/// </summary>
/// <returns>SpectatorCameraBased array if there are any SpectatorCameraBased components. Otherwise, return null.</returns>
private static SpectatorCameraBased[] GetSpectatorCameraBased()
{
var spectatorCameraBasedArray = (SpectatorCameraBased[])FindObjectsOfType(typeof(SpectatorCameraBased));
return (spectatorCameraBasedArray != null && spectatorCameraBasedArray.Length > 0)
? spectatorCameraBasedArray
: null;
}
/// <summary>
/// Create a GameObject that includes SpectatorCameraBased script in Unity scene for cooperation with extension native plugins.
/// </summary>
private static void CreateSpectatorCameraBased()
{
if (IsExtensionEnabled())
{
Debug($"Instance.IsExistSpectatorCameraBased = {Instance.IsExistSpectatorCameraBased}");
Instance.IsExistSpectatorCameraBased = true;
if (GetSpectatorCameraBased() != null)
{
Debug("No need to add SpectatorCameraBased because the scene already exist.");
return;
}
Debug("Start to add SpectatorCameraBased.");
var spectatorCameraBase =
new GameObject("Spectator Camera Base", typeof(SpectatorCameraBased))
{
transform =
{
position = Vector3.zero,
rotation = Quaternion.identity
}
};
Debug($"Create Spectator Camera Base GameObject successfully: {spectatorCameraBase != null}");
Debug(
$"Included SpectatorCameraBased component: {spectatorCameraBase.GetComponent<SpectatorCameraBased>() != null}");
}
else
{
Debug("Create Spectator Camera Base GameObject failed because the related extensions are not enabled.");
}
}
#endregion
#endregion
#region Enum definition
/// <summary>
/// The enum definition of supporting rendering backend.
/// </summary>
private enum GraphicsAPI
{
Unknown = 0,
GLES3 = 1,
Vulkan = 2
}
#endregion
}
}

View File

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

View File

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

View File

@@ -0,0 +1,395 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Scripting;
using UnityEngine.XR.OpenXR.Input;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.XR;
using UnityEngine.InputSystem;
using System.Runtime.InteropServices;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
using PoseControl = UnityEngine.InputSystem.XR.PoseControl;
#else
using PoseControl = UnityEngine.XR.OpenXR.Input.PoseControl;
#endif
namespace UnityEngine.XR.OpenXR.Features.Interactions
{
/// <summary>
/// This <see cref="OpenXRInteractionFeature"/> enables the use of HTC Vive Trackers interaction profiles in OpenXR.
/// </summary>
#if UNITY_EDITOR
[UnityEditor.XR.OpenXR.Features.OpenXRFeature(
UiName = "HTC Vive Tracker Profile",
BuildTargetGroups = new[] { BuildTargetGroup.Standalone, BuildTargetGroup.WSA },
Company = "MASSIVE",
Desc = "Allows for mapping input to the HTC Vive Tracker interaction profile.",
DocumentationLink = Constants.k_DocumentationManualURL,
OpenxrExtensionStrings = HTCViveTrackerProfile.extensionName,
Version = "0.0.1",
Category = UnityEditor.XR.OpenXR.Features.FeatureCategory.Interaction,
FeatureId = featureId)]
#endif
public class HTCViveTrackerProfile : OpenXRInteractionFeature
{
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "com.massive.openxr.feature.input.htcvivetracker";
/// <summary>
/// The interaction profile string used to reference the <a href="https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#:~:text=in%20this%20case.-,VIVE%20Tracker%20interaction%20profile,-Interaction%20profile%20path">HTC Vive Tracker</a>.
/// </summary>
public const string profile = "/interaction_profiles/htc/vive_tracker_htcx";
/// <summary>
/// The name of the OpenXR extension that supports the Vive Tracker
/// </summary>
public const string extensionName = "XR_HTCX_vive_tracker_interaction";
private const string kLayoutName = "ViveTracker";
private const string kDeviceLocalizedName = "HTC Vive Tracker OpenXR";
/// <summary>
/// OpenXR user path definitions for the tracker.
/// </summary>
public static class TrackerUserPaths
{
/// <summary>
/// Path for user left foot
/// </summary>
public const string leftFoot = "/user/vive_tracker_htcx/role/left_foot";
/// <summary>
/// Path for user roght foot
/// </summary>
public const string rightFoot = "/user/vive_tracker_htcx/role/right_foot";
/// <summary>
/// Path for user left shoulder
/// </summary>
public const string leftShoulder = "/user/vive_tracker_htcx/role/left_shoulder";
/// <summary>
/// Path for user right shoulder
/// </summary>
public const string rightShoulder = "/user/vive_tracker_htcx/role/right_shoulder";
/// <summary>
/// Path for user left elbow
/// </summary>
public const string leftElbow = "/user/vive_tracker_htcx/role/left_elbow";
/// <summary>
/// Path for user right elbow
/// </summary>
public const string rightElbow = "/user/vive_tracker_htcx/role/right_elbow";
/// <summary>
/// Path for user left knee
/// </summary>
public const string leftKnee = "/user/vive_tracker_htcx/role/left_knee";
/// <summary>
/// Path for user right knee
/// </summary>
public const string rightKnee = "/user/vive_tracker_htcx/role/right_knee";
/// <summary>
/// Path for user waist
/// </summary>
public const string waist = "/user/vive_tracker_htcx/role/waist";
/// <summary>
/// Path for user chest
/// </summary>
public const string chest = "/user/vive_tracker_htcx/role/chest";
/// <summary>
/// Path for user custom camera
/// </summary>
public const string camera = "/user/vive_tracker_htcx/role/camera";
/// <summary>
/// Path for user keyboard
/// </summary>
public const string keyboard = "/user/vive_tracker_htcx/role/keyboard";
}
/// <summary>
/// OpenXR component path definitions for the tracker.
/// </summary>
public static class TrackerComponentPaths
{
/// <summary>
/// Constant for a pose interaction binding '.../input/grip/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs.
/// </summary>
public const string grip = "/input/grip/pose";
}
/// <summary>
/// A base Input System device for XR Trackers, based off the TrackedDevice
/// </summary>
[InputControlLayout(isGenericTypeOfDevice = true, displayName = "XR Tracker")]
public class XRTracker : TrackedDevice
{
}
/// <summary>
/// An Input System device based off the <a href="https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#_htc_vive_controller_profile">HTC Vive Tracker</a>.
/// </summary>
[Preserve, InputControlLayout(displayName = "HTC Vive Tracker (OpenXR)", commonUsages = new[] { "Left Foot", "Right Foot", "Left Shoulder", "Right Shoulder", "Left Elbow", "Right Elbow", "Left Knee", "Right Knee", "Waist", "Chest", "Camera", "Keyboard" })]
public class XRViveTracker : XRTracker
{
/// <summary>
/// A <see cref="PoseControl"/> that represents information from the <see cref="HTCViveTrackerProfile.grip"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, aliases = new[] { "device", "gripPose" }, usage = "Device", noisy = true)]
public PoseControl devicePose { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for back compatibility with the XRSDK layouts. This is the device position. For the Oculus Touch device, this is both the grip and the pointer position. This value is equivalent to mapping devicePose/position.
/// </summary>
[Preserve, InputControl(offset = 8, alias = "gripPosition", noisy = true)]
new public Vector3Control devicePosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the device orientation. For the Oculus Touch device, this is both the grip and the pointer rotation. This value is equivalent to mapping devicePose/rotation.
/// </summary>
[Preserve, InputControl(offset = 20, alias = "gripOrientation", noisy = true)]
new public QuaternionControl deviceRotation { get; private set; }
[Preserve, InputControl(offset = 60)]
new public ButtonControl isTracked { get; private set; }
[Preserve, InputControl(offset = 64)]
new public IntegerControl trackingState { get; private set; }
/// <inheritdoc cref="OpenXRDevice"/>
protected override void FinishSetup()
{
base.FinishSetup();
devicePose = GetChildControl<PoseControl>("devicePose");
devicePosition = GetChildControl<Vector3Control>("devicePosition");
deviceRotation = GetChildControl<QuaternionControl>("deviceRotation");
isTracked = GetChildControl<ButtonControl>("isTracked");
trackingState = GetChildControl<IntegerControl>("trackingState");
var capabilities = description.capabilities;
var deviceDescriptor = XRDeviceDescriptor.FromJson(capabilities);
if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerLeftFoot) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Left Foot");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerRightFoot) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Right Foot");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerLeftShoulder) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Left Shoulder");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerRightShoulder) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Right Shoulder");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerLeftElbow) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Left Elbow");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerRightElbow) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Right Elbow");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerLeftKnee) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Left Knee");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerRightKnee) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Right Knee");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerWaist) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Waist");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerChest) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Chest");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerCamera) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Camera");
else if ((deviceDescriptor.characteristics & (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerKeyboard) != 0)
InputSystem.InputSystem.SetDeviceUsage(this, "Keyboard");
Debug.Log("Device added");
}
}
/// <summary>
/// Registers the <see cref="ViveTracker"/> layout with the Input System.
/// </summary>
protected override void RegisterDeviceLayout()
{
InputSystem.InputSystem.RegisterLayout<XRTracker>();
InputSystem.InputSystem.RegisterLayout(typeof(XRViveTracker),
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
}
/// <summary>
/// Removes the <see cref="ViveTracker"/> layout from the Input System.
/// </summary>
protected override void UnregisterDeviceLayout()
{
InputSystem.InputSystem.RemoveLayout(nameof(XRViveTracker));
InputSystem.InputSystem.RemoveLayout(nameof(XRTracker));
}
#if UNITY_XR_OPENXR_1_9_1
/// <summary>
/// Return interaction profile type. XRViveTracker profile is Device type.
/// </summary>
/// <returns>Interaction profile type.</returns>
protected override InteractionProfileType GetInteractionProfileType()
{
return typeof(XRViveTracker).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device;
}
/// <summary>
/// Return device layer out string used for registering device VIVEFocus3Controller in InputSystem.
/// </summary>
/// <returns>Device layout string.</returns>
protected override string GetDeviceLayoutName()
{
return kLayoutName;
}
#endif
//
// Summary:
// A set of bit flags describing XR.InputDevice characteristics.
//"Left Foot", "Right Foot", "Left Shoulder", "Right Shoulder", "Left Elbow", "Right Elbow", "Left Knee", "Right Knee", "Waist", "Chest", "Camera", "Keyboard"
[Flags]
public enum InputDeviceTrackerCharacteristics : uint
{
TrackerLeftFoot = 0x1000u,
TrackerRightFoot = 0x2000u,
TrackerLeftShoulder = 0x4000u,
TrackerRightShoulder = 0x8000u,
TrackerLeftElbow = 0x10000u,
TrackerRightElbow = 0x20000u,
TrackerLeftKnee = 0x40000u,
TrackerRightKnee = 0x80000u,
TrackerWaist = 0x100000u,
TrackerChest = 0x200000u,
TrackerCamera = 0x400000u,
TrackerKeyboard = 0x800000u
}
/// <inheritdoc/>
protected override void RegisterActionMapsWithRuntime()
{
ActionMapConfig actionMap = new ActionMapConfig()
{
name = "htcvivetracker",
localizedName = kDeviceLocalizedName,
desiredInteractionProfile = profile,
manufacturer = "HTC",
serialNumber = "",
deviceInfos = new List<DeviceConfig>()
{
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerLeftFoot,
userPath = TrackerUserPaths.leftFoot
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerRightFoot,
userPath = TrackerUserPaths.rightFoot
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerLeftShoulder,
userPath = TrackerUserPaths.leftShoulder
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerRightShoulder,
userPath = TrackerUserPaths.rightShoulder
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerLeftElbow,
userPath = TrackerUserPaths.leftElbow
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerRightElbow,
userPath = TrackerUserPaths.rightElbow
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerLeftKnee,
userPath = TrackerUserPaths.leftKnee
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerRightKnee,
userPath = TrackerUserPaths.rightKnee
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerWaist,
userPath = TrackerUserPaths.waist
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerChest,
userPath = TrackerUserPaths.chest
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerCamera,
userPath = TrackerUserPaths.camera
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics.TrackedDevice) | (InputDeviceCharacteristics)InputDeviceTrackerCharacteristics.TrackerKeyboard,
userPath = TrackerUserPaths.keyboard
}
},
actions = new List<ActionConfig>()
{
new ActionConfig()
{
name = "devicePose",
localizedName = "Device Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Device",
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = TrackerComponentPaths.grip,
interactionProfileName = profile,
}
}
},
}
};
AddActionMap(actionMap);
}
protected override bool OnInstanceCreate(ulong xrInstance)
{
bool res = base.OnInstanceCreate(xrInstance);
if (OpenXRRuntime.IsExtensionEnabled("XR_HTCX_vive_tracker_interaction"))
{
Debug.Log("HTC Vive Tracker Extension Enabled");
}
else
{
Debug.Log("HTC Vive Tracker Extension Not Enabled");
}
return res;
}
}
}

View File

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

View File

@@ -32,10 +32,11 @@ namespace VIVE.OpenXR.Tracker
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Wrist Tracker",
BuildTargetGroups = new[] { BuildTargetGroup.Android , BuildTargetGroup.Standalone},
Hidden = true,
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "HTC",
Desc = "Support for enabling the wrist tracker interaction profile. Will register the controller map for wrist tracker if enabled.",
DocumentationLink = "..\\Documentation",
DocumentationLink = "https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_HTC_vive_wrist_tracker_interaction",
Version = "1.0.0",
OpenxrExtensionStrings = kOpenxrExtensionString,
Category = FeatureCategory.Interaction,
@@ -43,16 +44,19 @@ namespace VIVE.OpenXR.Tracker
#endif
public class ViveWristTracker : OpenXRInteractionFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Tracker.ViveWristTracker ";
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 WARNING(StringBuilder msg) { Debug.LogWarning(msg); }
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#endregion
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_vive_wrist_tracker_interaction">12.72. XR_HTC_vive_wrist_tracker_interaction</see>.
@@ -70,12 +74,12 @@ namespace VIVE.OpenXR.Tracker
private const string leftWrist = "/user/wrist_htc/left";
private const string rightWrist = "/user/wrist_htc/right";
// Available Bindings
// Left Hand Only
/// <summary>
/// Constant for a boolean interaction binding '.../input/x/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the <see cref="leftWrist"/> user path.
/// </summary>
public const string buttonX = "/input/x/click";
// Available Bindings
// Left Hand Only
/// <summary>
/// Constant for a boolean interaction binding '.../input/x/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the <see cref="leftWrist"/> user path.
/// </summary>
public const string buttonX = "/input/x/click";
/// <summary>
/// Constant for a boolean interaction binding '.../input/menu/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the <see cref="leftWrist"/> user path.
/// </summary>
@@ -100,15 +104,19 @@ namespace VIVE.OpenXR.Tracker
[Preserve, InputControlLayout(displayName = "VIVE Wrist Tracker (OpenXR)", commonUsages = new[] { "LeftHand", "RightHand" }, isGenericTypeOfDevice = true)]
public class WristTrackerDevice : OpenXRDevice
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Tracker.ViveWristTracker.WristTrackerDevice ";
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(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#endregion
/// <summary>
/// A <see href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/api/UnityEngine.InputSystem.Controls.ButtonControl.html">ButtonControl</see> that represents the <see cref="buttonA"/> <see cref="buttonX"/> OpenXR bindings, depending on handedness.
@@ -167,8 +175,7 @@ namespace VIVE.OpenXR.Tracker
devicePosition = GetChildControl<Vector3Control>("devicePosition");
deviceRotation = GetChildControl<QuaternionControl>("deviceRotation");
sb.Clear().Append(LOG_TAG)
.Append(" FinishSetup() device interfaceName: ").Append(description.interfaceName)
sb.Clear().Append(" FinishSetup() device interfaceName: ").Append(description.interfaceName)
.Append(", deviceClass: ").Append(description.deviceClass)
.Append(", product: ").Append(description.product)
.Append(", serial: ").Append(description.serial);
@@ -189,7 +196,7 @@ namespace VIVE.OpenXR.Tracker
{
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
sb.Clear().Append(LOG_TAG).Append(" OnInstanceCreate() " + m_XrInstance); DEBUG(sb);
sb.Clear().Append(" OnInstanceCreate() " + m_XrInstance); DEBUG(sb);
return base.OnInstanceCreate(xrInstance);
}
@@ -201,11 +208,12 @@ namespace VIVE.OpenXR.Tracker
/// </summary>
protected override void RegisterDeviceLayout()
{
sb.Clear().Append("RegisterDeviceLayout() ").Append(kLayoutName).Append(", product: ").Append(kDeviceLocalizedName); DEBUG(sb);
InputSystem.RegisterLayout(typeof(WristTrackerDevice),
kLayoutName,
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
kLayoutName,
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
}
/// <summary>
@@ -213,9 +221,30 @@ namespace VIVE.OpenXR.Tracker
/// </summary>
protected override void UnregisterDeviceLayout()
{
sb.Clear().Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb);
InputSystem.RemoveLayout(kLayoutName);
}
#if UNITY_XR_OPENXR_1_9_1
/// <summary>
/// Return interaction profile type. WristTrackerDevice profile is Device type.
/// </summary>
/// <returns>Interaction profile type.</returns>
protected override InteractionProfileType GetInteractionProfileType()
{
return typeof(WristTrackerDevice).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device;
}
/// <summary>
/// Return device layer out string used for registering device WristTrackerDevice in InputSystem.
/// </summary>
/// <returns>Device layout string.</returns>
protected override string GetDeviceLayoutName()
{
return kLayoutName;
}
#endif
/// <summary>
/// Registers action maps to Unity XR.
/// </summary>
@@ -245,30 +274,30 @@ namespace VIVE.OpenXR.Tracker
{
// X / A Press
new ActionConfig()
{
name = "primaryButton",
localizedName = "Primary Pressed",
type = ActionType.Binary,
usages = new List<string>()
{
"PrimaryButton"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = buttonX,
interactionProfileName = profile,
userPaths = new List<string>() { leftWrist }
},
new ActionBinding()
{
interactionPath = buttonA,
interactionProfileName = profile,
userPaths = new List<string>() { rightWrist }
},
}
},
{
name = "primaryButton",
localizedName = "Primary Pressed",
type = ActionType.Binary,
usages = new List<string>()
{
"PrimaryButton"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = buttonX,
interactionProfileName = profile,
userPaths = new List<string>() { leftWrist }
},
new ActionBinding()
{
interactionPath = buttonA,
interactionProfileName = profile,
userPaths = new List<string>() { rightWrist }
},
}
},
// Menu
new ActionConfig()
{

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
# 12.38. XR_EXT_user_presence
## Name String
XR_EXT_user_presence
## Revision
1
## New Enum Constants
[XrStructureType](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrStructureType) enumeration is extended with:
- XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT
- XR_TYPE_SYSTEM_USER_PRESENCE_PROPERTIES_EXT
## New Structures
- [XrSystemUserPresencePropertiesEXT](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrSystemUserPresencePropertiesEXT)
- [XrEventDataUserPresenceChangedEXT ](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XrEventDataUserPresenceChangedEXT)
## VIVE Plugin
Enable "VIVE XR User Presence" in "Project Settings > XR Plugin-in Management > OpenXR > Android Tab > OpenXR Feature Groups" to use the user presence feature provided by VIVE OpenXR plugin.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4cf0d8092d0049c4abd5759298b85224
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,252 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.UserPresence
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR User Presence",
BuildTargetGroups = new[] { BuildTargetGroup.Android },
Company = "HTC",
Desc = "Support the User Presence extension.",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = kOpenxrExtensionString,
Version = "1.0.0",
FeatureId = featureId)]
#endif
public class ViveUserPresence : OpenXRFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.UserPresence.ViveUserPresence";
StringBuilder m_sb = null;
StringBuilder sb {
get {
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
void WARNING(StringBuilder msg) { Debug.LogWarningFormat("{0} {1}", LOG_TAG, msg); }
void ERROR(StringBuilder msg) { Debug.LogErrorFormat("{0} {1}", LOG_TAG, msg); }
#endregion
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_EXT_user_presence">12.39. XR_EXT_user_presence</see>.
/// </summary>
public const string kOpenxrExtensionString = "XR_EXT_user_presence";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.userpresence";
#region OpenXR Life Cycle
/// <inheritdoc />
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
sb.Clear().Append("HookGetInstanceProcAddr() xrPollEvent"); DEBUG(sb);
ViveInterceptors.Instance.AddRequiredFunction("xrPollEvent");
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
}
private bool m_XrInstanceCreated = false;
private XrInstance m_XrInstance = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The created instance.</param>
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
sb.Clear().Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
return false;
}
m_XrInstance = xrInstance;
m_XrInstanceCreated = true;
sb.Clear().Append("OnInstanceCreate() ").Append(m_XrInstance); DEBUG(sb);
return GetXrFunctionDelegates(m_XrInstance);
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyInstance">xrDestroyInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The instance to destroy.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
sb.Clear().Append("OnInstanceDestroy() ").Append(xrInstance).Append(", current: ").Append(m_XrInstance); DEBUG(sb);
if (m_XrInstance == xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
}
}
private bool m_XrSessionCreated = false;
private XrSession m_XrSession = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateSession">xrCreateSession</see> is done.
/// </summary>
/// <param name="xrSession">The created session ID.</param>
protected override void OnSessionCreate(ulong xrSession)
{
m_XrSession = xrSession;
m_XrSessionCreated = true;
CheckUserPresenceSupport();
sb.Clear().Append("OnSessionCreate() ").Append(m_XrSession).Append(", support User Presence: ").Append(SupportedUserPresence()); DEBUG(sb);
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroySession">xrDestroySession</see> is done.
/// </summary>
/// <param name="xrSession">The session ID to destroy.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
sb.Clear().Append("OnSessionDestroy() ").Append(xrSession).Append(", current: ").Append(m_XrSession); DEBUG(sb);
if (m_XrSession == xrSession)
{
m_XrSessionCreated = false;
m_XrSession = 0;
}
}
private XrSystemId m_XrSystemId = 0;
/// <summary>
/// Called when the <see cref="XrSystemId">XrSystemId</see> retrieved by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystem">xrGetSystem</see> is changed.
/// </summary>
/// <param name="xrSystem">The system id.</param>
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
sb.Clear().Append("OnSystemChange() " + m_XrSystemId); DEBUG(sb);
}
#endregion
#region OpenXR function delegates
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
OpenXRHelper.xrGetSystemPropertiesDelegate xrGetSystemProperties;
/// <summary>
/// An application can call GetSystemProperties to retrieve information about the system such as vendor ID, system name, and graphics and tracking properties.
/// </summary>
/// <param name="properties">Points to an instance of the XrSystemProperties structure, that will be filled with returned information.</param>
/// <returns>XR_SUCCESS for success.</returns>
private XrResult GetSystemProperties(ref XrSystemProperties properties)
{
if (!m_XrSessionCreated)
{
sb.Clear().Append("GetSystemProperties() XR_ERROR_SESSION_LOST."); ERROR(sb);
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
sb.Clear().Append("GetSystemProperties() XR_ERROR_INSTANCE_LOST."); ERROR(sb);
return XrResult.XR_ERROR_INSTANCE_LOST;
}
return xrGetSystemProperties(m_XrInstance, m_XrSystemId, ref properties);
}
private bool GetXrFunctionDelegates(XrInstance xrInstance)
{
/// xrGetInstanceProcAddr
if (xrGetInstanceProcAddr != null && xrGetInstanceProcAddr != IntPtr.Zero)
{
sb.Clear().Append("Get function pointer of xrGetInstanceProcAddr."); DEBUG(sb);
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(
xrGetInstanceProcAddr,
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
}
else
{
sb.Clear().Append("xrGetInstanceProcAddr"); ERROR(sb);
return false;
}
IntPtr funcPtr = IntPtr.Zero;
/// xrGetSystemProperties
if (XrGetInstanceProcAddr(xrInstance, "xrGetSystemProperties", out funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
sb.Clear().Append("Get function pointer of xrGetSystemProperties."); DEBUG(sb);
xrGetSystemProperties = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(OpenXRHelper.xrGetSystemPropertiesDelegate)) as OpenXRHelper.xrGetSystemPropertiesDelegate;
}
}
else
{
sb.Clear().Append("xrGetSystemProperties"); ERROR(sb);
return false;
}
return true;
}
#endregion
private bool m_SupportUserPresence = false;
XrSystemProperties systemProperties;
XrSystemUserPresencePropertiesEXT userPresenceProperties;
private void CheckUserPresenceSupport()
{
m_SupportUserPresence = false;
if (!m_XrSessionCreated)
{
sb.Clear().Append("CheckUserPresenceSupport() session is not created."); ERROR(sb);
return;
}
userPresenceProperties.type = XrStructureType.XR_TYPE_SYSTEM_USER_PRESENCE_PROPERTIES_EXT;
systemProperties.type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES;
systemProperties.next = Marshal.AllocHGlobal(Marshal.SizeOf(userPresenceProperties));
long offset = 0;
if (IntPtr.Size == 4)
offset = systemProperties.next.ToInt32();
else
offset = systemProperties.next.ToInt64();
IntPtr userPresencePropertiesPtr = new IntPtr(offset);
Marshal.StructureToPtr(userPresenceProperties, userPresencePropertiesPtr, false);
#pragma warning disable 0618
if (GetSystemProperties(ref systemProperties) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
if (IntPtr.Size == 4)
offset = systemProperties.next.ToInt32();
else
offset = systemProperties.next.ToInt64();
userPresencePropertiesPtr = new IntPtr(offset);
userPresenceProperties = (XrSystemUserPresencePropertiesEXT)Marshal.PtrToStructure(userPresencePropertiesPtr, typeof(XrSystemUserPresencePropertiesEXT));
sb.Clear().Append("CheckUserPresenceSupport() userPresenceProperties.supportsUserPresence: ").Append((UInt32)userPresenceProperties.supportsUserPresence); DEBUG(sb);
m_SupportUserPresence = userPresenceProperties.supportsUserPresence > 0;
}
else
{
sb.Clear().Append("CheckUserPresenceSupport() GetSystemProperties failed."); ERROR(sb);
}
Marshal.FreeHGlobal(systemProperties.next);
}
public bool SupportedUserPresence() { return m_SupportUserPresence; }
public bool IsUserPresent()
{
if (!SupportedUserPresence()) { return true; } // user is always present
return ViveInterceptors.Instance.IsUserPresent();
}
}
}

View File

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

View File

@@ -0,0 +1,92 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
namespace VIVE.OpenXR.UserPresence
{
// -------------------- 12.39. XR_EXT_user_presence --------------------
#region New Structures
/// <summary>
/// The application can use the XrSystemUserPresencePropertiesEXT event in xrGetSystemProperties to detect if the given system supports the sensing of user presence.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrSystemUserPresencePropertiesEXT
{
/// <summary>
/// The <see cref="XrStructureType"/> of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// An <see cref="XrBool32"/> value that indicates whether the system supports user presence sensing.
/// </summary>
public XrBool32 supportsUserPresence;
}
/// <summary>
/// The XrEventDataUserPresenceChangedEXT event is queued for retrieval using xrPollEvent when the user presence is changed, as well as when a session starts running.<br></br>
/// Receiving XrEventDataUserPresenceChangedEXT with the isUserPresent is XR_TRUE indicates that the system has detected the presence of a user in the XR experience.For example, this may indicate that the user has put on the headset, or has entered the tracking area of a non-head-worn XR system.<br></br>
/// Receiving XrEventDataUserPresenceChangedEXT with the isUserPresent is XR_FALSE indicates that the system has detected the absence of a user in the XR experience.For example, this may indicate that the user has removed the headset or has stepped away from the tracking area of a non-head-worn XR system.<br></br>
/// The runtime must queue this event upon a successful call to the xrBeginSession function, regardless of the value of isUserPresent, so that the application can be in sync on the state when a session begins running.<br></br>
/// The runtime must return a valid XrSession handle for a running session.<br></br>
/// After the application calls xrEndSession, a running session is ended and the runtime must not enqueue any more user presence events.Therefore, the application will no longer observe any changes of the isUserPresent until another running session.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct XrEventDataUserPresenceChangedEXT
{
/// <summary>
/// The <see cref="XrStructureType"/> of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// The <see cref="XrSession"/> that is receiving the notification.
/// </summary>
public XrSession session;
/// <summary>
/// An <see cref="XrBool32"/> value for new state of user presence after the change.
/// </summary>
public XrBool32 isUserPresent;
/// <summary>
/// The XR_EXT_user_presence extension must be enabled prior to using XrEventDataUserPresenceChangedEXT.
/// </summary>
public XrEventDataUserPresenceChangedEXT(XrStructureType in_type, IntPtr in_next, XrSession in_session, XrBool32 in_present)
{
type = in_type;
next = in_next;
session = in_session;
isUserPresent = in_present;
}
/// <summary>
/// Retrieves the identity value of XrEventDataUserPresenceChangedEXT.
/// </summary>
public static XrEventDataUserPresenceChangedEXT identity {
get {
return new XrEventDataUserPresenceChangedEXT(XrStructureType.XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT, IntPtr.Zero, 0, true); // user is default present
}
}
public static bool Get(XrEventDataBuffer eventDataBuffer, out XrEventDataUserPresenceChangedEXT eventDataUserPresenceChangedEXT)
{
eventDataUserPresenceChangedEXT = identity;
if (eventDataBuffer.type == XrStructureType.XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT)
{
eventDataUserPresenceChangedEXT.next = eventDataBuffer.next;
eventDataUserPresenceChangedEXT.session = BitConverter.ToUInt64(eventDataBuffer.varying, 0);
eventDataUserPresenceChangedEXT.isUserPresent = BitConverter.ToUInt32(eventDataBuffer.varying, 8);
return true;
}
return false;
}
}
#endregion
}

View File

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