version 2.3.0
This commit is contained in:
@@ -75,6 +75,11 @@ namespace VIVE.OpenXR.CompositionLayer.Editor
|
|||||||
static GUIContent Label_ApplyColorScaleBias = new GUIContent("Apply Color Scale Bias", "Color scale and bias are applied to a layer color during composition, after its conversion to premultiplied alpha representation. LayerColor = LayerColor * colorScale + colorBias");
|
static GUIContent Label_ApplyColorScaleBias = new GUIContent("Apply Color Scale Bias", "Color scale and bias are applied to a layer color during composition, after its conversion to premultiplied alpha representation. LayerColor = LayerColor * colorScale + colorBias");
|
||||||
SerializedProperty Property_ApplyColorScaleBias;
|
SerializedProperty Property_ApplyColorScaleBias;
|
||||||
|
|
||||||
|
static string PropertyName_SolidEffect = "solidEffect";
|
||||||
|
static GUIContent Label_SolidEffect = new GUIContent("Solid Effect", "Apply UnderLay Color Scale Bias in Runtime Level.");
|
||||||
|
SerializedProperty Property_SolidEffect;
|
||||||
|
|
||||||
|
|
||||||
static string PropertyName_ColorScale = "colorScale";
|
static string PropertyName_ColorScale = "colorScale";
|
||||||
static GUIContent Label_ColorScale = new GUIContent("Color Scale", "Will be used for modulatting the color sourced from the images.");
|
static GUIContent Label_ColorScale = new GUIContent("Color Scale", "Will be used for modulatting the color sourced from the images.");
|
||||||
SerializedProperty Property_ColorScale;
|
SerializedProperty Property_ColorScale;
|
||||||
@@ -114,6 +119,7 @@ namespace VIVE.OpenXR.CompositionLayer.Editor
|
|||||||
if (Property_AngleOfArc == null) Property_AngleOfArc = serializedObject.FindProperty(PropertyName_AngleOfArc);
|
if (Property_AngleOfArc == null) Property_AngleOfArc = serializedObject.FindProperty(PropertyName_AngleOfArc);
|
||||||
if (Property_IsDynamicLayer == null) Property_IsDynamicLayer = serializedObject.FindProperty(PropertyName_IsDynamicLayer);
|
if (Property_IsDynamicLayer == null) Property_IsDynamicLayer = serializedObject.FindProperty(PropertyName_IsDynamicLayer);
|
||||||
if (Property_ApplyColorScaleBias == null) Property_ApplyColorScaleBias = serializedObject.FindProperty(PropertyName_ApplyColorScaleBias);
|
if (Property_ApplyColorScaleBias == null) Property_ApplyColorScaleBias = serializedObject.FindProperty(PropertyName_ApplyColorScaleBias);
|
||||||
|
if (Property_SolidEffect == null) Property_SolidEffect = serializedObject.FindProperty(PropertyName_SolidEffect);
|
||||||
if (Property_ColorScale == null) Property_ColorScale = serializedObject.FindProperty(PropertyName_ColorScale);
|
if (Property_ColorScale == null) Property_ColorScale = serializedObject.FindProperty(PropertyName_ColorScale);
|
||||||
if (Property_ColorBias == null) Property_ColorBias = serializedObject.FindProperty(PropertyName_ColorBias);
|
if (Property_ColorBias == null) Property_ColorBias = serializedObject.FindProperty(PropertyName_ColorBias);
|
||||||
if (Property_IsProtectedSurface == null) Property_IsProtectedSurface = serializedObject.FindProperty(PropertyName_IsProtectedSurface);
|
if (Property_IsProtectedSurface == null) Property_IsProtectedSurface = serializedObject.FindProperty(PropertyName_IsProtectedSurface);
|
||||||
@@ -441,6 +447,11 @@ namespace VIVE.OpenXR.CompositionLayer.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditorGUI.indentLevel++;
|
EditorGUI.indentLevel++;
|
||||||
|
if (targetCompositionLayer.layerType == CompositionLayer.LayerType.Underlay)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(Property_SolidEffect, Label_SolidEffect);
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
showColorScaleBiasParams = EditorGUILayout.Foldout(showColorScaleBiasParams, "Color Scale Bias Parameters");
|
showColorScaleBiasParams = EditorGUILayout.Foldout(showColorScaleBiasParams, "Color Scale Bias Parameters");
|
||||||
if (showColorScaleBiasParams)
|
if (showColorScaleBiasParams)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,24 +7,26 @@ using UnityEditor.XR.OpenXR.Features;
|
|||||||
namespace VIVE.OpenXR
|
namespace VIVE.OpenXR
|
||||||
{
|
{
|
||||||
[OpenXRFeatureSet(
|
[OpenXRFeatureSet(
|
||||||
FeatureIds = new string[] {
|
FeatureIds = new string[] {
|
||||||
VIVEFocus3Feature.featureId,
|
VIVEFocus3Feature.featureId,
|
||||||
VIVEFocus3Profile.featureId,
|
VIVEFocus3Profile.featureId,
|
||||||
Hand.ViveHandTracking.featureId,
|
Hand.ViveHandTracking.featureId,
|
||||||
"vive.vive.openxr.feature.compositionlayer",
|
"vive.openxr.feature.compositionlayer",
|
||||||
"vive.vive.openxr.feature.compositionlayer.cylinder",
|
"vive.openxr.feature.compositionlayer.cylinder",
|
||||||
"vive.vive.openxr.feature.compositionlayer.colorscalebias",
|
"vive.openxr.feature.compositionlayer.colorscalebias",
|
||||||
Tracker.ViveWristTracker.featureId,
|
Tracker.ViveWristTracker.featureId,
|
||||||
Hand.ViveHandInteraction.featureId,
|
Hand.ViveHandInteraction.featureId,
|
||||||
"vive.vive.openxr.feature.foveation",
|
"vive.openxr.feature.foveation",
|
||||||
FacialTracking.ViveFacialTracking.featureId,
|
FacialTracking.ViveFacialTracking.featureId,
|
||||||
},
|
PlaneDetection.VivePlaneDetection.featureId,
|
||||||
UiName = "VIVE XR Support",
|
Anchor.ViveAnchor.featureId,
|
||||||
Description = "Necessary to deploy an VIVE XR compatible app.",
|
},
|
||||||
FeatureSetId = "com.htc.vive.openxr.featureset.vivexr",
|
UiName = "VIVE XR Support",
|
||||||
DefaultFeatureIds = new string[] { VIVEFocus3Feature.featureId, VIVEFocus3Profile.featureId, },
|
Description = "Necessary to deploy an VIVE XR compatible app.",
|
||||||
SupportedBuildTargets = new BuildTargetGroup[] { BuildTargetGroup.Android }
|
FeatureSetId = "com.htc.vive.openxr.featureset.vivexr",
|
||||||
)]
|
DefaultFeatureIds = new string[] { VIVEFocus3Feature.featureId, VIVEFocus3Profile.featureId, },
|
||||||
|
SupportedBuildTargets = new BuildTargetGroup[] { BuildTargetGroup.Android }
|
||||||
|
)]
|
||||||
sealed class VIVEFocus3FeatureSet { }
|
sealed class VIVEFocus3FeatureSet { }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 28cd706d4fe07ba4c85df80c80bbdee6
|
guid: d55a23fb1f9c7b0479d1f15716620072
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Feature
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// To use this wrapper, you need to call CommonWrapper.Instance.OnInstanceCreate() in your feature's OnInstanceCreate(),
|
||||||
|
/// and call CommonWrapper.Instance.OnInstanceDestroy() in your feature's OnInstanceDestroy().
|
||||||
|
/// </summary>
|
||||||
|
public class CommonWrapper
|
||||||
|
{
|
||||||
|
static CommonWrapper instance = null;
|
||||||
|
public static CommonWrapper Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
instance = new CommonWrapper();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInited = false;
|
||||||
|
|
||||||
|
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
||||||
|
OpenXRHelper.xrGetSystemPropertiesDelegate XrGetSystemProperties;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In feature's OnInstanceCreate(), call CommonWrapper.Instance.OnInstanceCreate() for init common APIs.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="xrInstance">Passed in feature's OnInstanceCreate.</param>
|
||||||
|
/// <param name="xrGetInstanceProcAddr">Pass OpenXRFeature.xrGetInstanceProcAddr in.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="Exception">If input data not valid.</exception>
|
||||||
|
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddr)
|
||||||
|
{
|
||||||
|
if (isInited) return true;
|
||||||
|
|
||||||
|
if (xrInstance == 0)
|
||||||
|
throw new Exception("CommonWrapper: xrInstance is null");
|
||||||
|
|
||||||
|
Debug.Log("CommonWrapper: OnInstanceCreate()");
|
||||||
|
/// OpenXRFeature.xrGetInstanceProcAddr
|
||||||
|
if (xrGetInstanceProcAddr == null || xrGetInstanceProcAddr == IntPtr.Zero)
|
||||||
|
throw new Exception("CommonWrapper: xrGetInstanceProcAddr is null");
|
||||||
|
|
||||||
|
Debug.Log("CommonWrapper: Get function pointer of xrGetInstanceProcAddr.");
|
||||||
|
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(
|
||||||
|
xrGetInstanceProcAddr,
|
||||||
|
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
|
||||||
|
|
||||||
|
bool ret = true;
|
||||||
|
IntPtr funcPtr = IntPtr.Zero;
|
||||||
|
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(XrGetInstanceProcAddr, xrInstance, "xrGetSystemProperties", out XrGetSystemProperties);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
throw new Exception("CommonWrapper: Get function pointers failed.");
|
||||||
|
|
||||||
|
isInited = ret;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In feature's OnInstanceDestroy(), call CommonWrapper.Instance.OnInstanceDestroy() for disable common APIs.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public void OnInstanceDestroy()
|
||||||
|
{
|
||||||
|
isInited = false;
|
||||||
|
XrGetInstanceProcAddr = null;
|
||||||
|
XrGetSystemProperties = null;
|
||||||
|
Debug.Log("CommonWrapper: OnInstanceDestroy()");
|
||||||
|
}
|
||||||
|
|
||||||
|
public XrResult GetInstanceProcAddr(XrInstance instance, string name, out IntPtr function)
|
||||||
|
{
|
||||||
|
if (isInited == false || XrGetInstanceProcAddr == null)
|
||||||
|
{
|
||||||
|
function = IntPtr.Zero;
|
||||||
|
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return XrGetInstanceProcAddr(instance, name, out function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper function to get system properties. Need input your features' xrInstance and xrSystemId. Fill the system properites in next for you feature.
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystemProperties">xrGetSystemProperties</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance"></param>
|
||||||
|
/// <param name="systemId"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrResult GetSystemProperties(XrInstance instance, XrSystemId systemId, ref XrSystemProperties properties)
|
||||||
|
{
|
||||||
|
if (isInited == false || XrGetSystemProperties == null)
|
||||||
|
{
|
||||||
|
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return XrGetSystemProperties(instance, systemId, ref properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public XrResult GetProperties<T>(XrInstance instance, XrSystemId systemId, ref T featureProperty)
|
||||||
|
{
|
||||||
|
XrSystemProperties systemProperties = new XrSystemProperties();
|
||||||
|
systemProperties.type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES;
|
||||||
|
systemProperties.next = Marshal.AllocHGlobal(Marshal.SizeOf(featureProperty));
|
||||||
|
|
||||||
|
long offset = 0;
|
||||||
|
if (IntPtr.Size == 4)
|
||||||
|
offset = systemProperties.next.ToInt32();
|
||||||
|
else
|
||||||
|
offset = systemProperties.next.ToInt64();
|
||||||
|
|
||||||
|
IntPtr pdPropertiesPtr = new IntPtr(offset);
|
||||||
|
Marshal.StructureToPtr(featureProperty, pdPropertiesPtr, false);
|
||||||
|
|
||||||
|
var ret = GetSystemProperties(instance, systemId, ref systemProperties);
|
||||||
|
if (ret == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (IntPtr.Size == 4)
|
||||||
|
offset = systemProperties.next.ToInt32();
|
||||||
|
else
|
||||||
|
offset = systemProperties.next.ToInt64();
|
||||||
|
|
||||||
|
pdPropertiesPtr = new IntPtr(offset);
|
||||||
|
featureProperty = Marshal.PtrToStructure<T>(pdPropertiesPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(systemProperties.next);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 93517fb9198ee33428984693d60b4061
|
guid: b940cd65f52cd5c44bd79869c5d521b2
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
|
||||||
|
// Remove FAKE_DATA if editor or windows is supported.
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
#define FAKE_DATA
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Feature
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To use this wrapper, you need to call CommonWrapper.Instance.OnInstanceCreate() in your feature's OnInstanceCreate(),
|
||||||
|
/// and call CommonWrapper.Instance.OnInstanceDestroy() in your feature's OnInstanceDestroy().
|
||||||
|
/// </summary>
|
||||||
|
public class SpaceWrapper
|
||||||
|
{
|
||||||
|
static SpaceWrapper instance = null;
|
||||||
|
public static SpaceWrapper Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
instance = new SpaceWrapper();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInited = false;
|
||||||
|
|
||||||
|
delegate XrResult DelegateXrLocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location);
|
||||||
|
delegate XrResult DelegateXrDestroySpace(XrSpace space);
|
||||||
|
|
||||||
|
OpenXRHelper.xrCreateReferenceSpaceDelegate XrCreateReferenceSpace;
|
||||||
|
DelegateXrLocateSpace XrLocateSpace;
|
||||||
|
DelegateXrDestroySpace XrDestroySpace;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Features should call ViveSpaceWrapper.Instance.OnInstanceCreate() in their OnInstanceCreate().
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="xrInstance"></param>
|
||||||
|
/// <param name="GetAddr"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="Exception"></exception>
|
||||||
|
public bool OnInstanceCreate(XrInstance xrInstance, OpenXRHelper.xrGetInstanceProcAddrDelegate GetAddr)
|
||||||
|
{
|
||||||
|
if (isInited) return true;
|
||||||
|
|
||||||
|
if (xrInstance == null)
|
||||||
|
throw new Exception("ViveSpace: xrInstance is null");
|
||||||
|
|
||||||
|
if (GetAddr == null)
|
||||||
|
throw new Exception("ViveSpace: xrGetInstanceProcAddr is null");
|
||||||
|
|
||||||
|
Debug.Log("ViveSpace: OnInstanceCreate()");
|
||||||
|
|
||||||
|
bool ret = true;
|
||||||
|
IntPtr funcPtr = IntPtr.Zero;
|
||||||
|
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrCreateReferenceSpace", out XrCreateReferenceSpace);
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrLocateSpace", out XrLocateSpace);
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrDestroySpace", out XrDestroySpace);
|
||||||
|
isInited = ret;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnInstanceDestroy()
|
||||||
|
{
|
||||||
|
isInited = false;
|
||||||
|
XrCreateReferenceSpace = null;
|
||||||
|
XrLocateSpace = null;
|
||||||
|
XrDestroySpace = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a reference space without create info.
|
||||||
|
/// Example:
|
||||||
|
/// CreateReferenceSpace(session, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_LOCAL, XrPosef.identity, out space);
|
||||||
|
/// CreateReferenceSpace(session, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_STAGE, XrPosef.identity, out space);
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session"></param>
|
||||||
|
/// <param name="referenceSpaceType"></param>
|
||||||
|
/// <param name="pose"></param>
|
||||||
|
/// <param name="space"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrResult CreateReferenceSpace(XrSession session, XrReferenceSpaceType referenceSpaceType, XrPosef pose, out XrSpace space)
|
||||||
|
{
|
||||||
|
if (!isInited)
|
||||||
|
throw new Exception("ViveSpace: not initialized");
|
||||||
|
|
||||||
|
var createInfo = new XrReferenceSpaceCreateInfo();
|
||||||
|
createInfo.type = XrStructureType.XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
|
||||||
|
createInfo.next = IntPtr.Zero;
|
||||||
|
createInfo.referenceSpaceType = referenceSpaceType;
|
||||||
|
createInfo.poseInReferenceSpace = pose;
|
||||||
|
return XrCreateReferenceSpace(session, ref createInfo, out space);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a reference space.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session"></param>
|
||||||
|
/// <param name="createInfo"></param>
|
||||||
|
/// <param name="space"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrResult CreateReferenceSpace(XrSession session, XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
|
||||||
|
{
|
||||||
|
if (!isInited)
|
||||||
|
throw new Exception("ViveSpace: not initialized");
|
||||||
|
|
||||||
|
return XrCreateReferenceSpace(session, ref createInfo, out space);
|
||||||
|
}
|
||||||
|
|
||||||
|
public XrResult LocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location)
|
||||||
|
{
|
||||||
|
if (!isInited)
|
||||||
|
throw new Exception("ViveSpace: not initialized");
|
||||||
|
Debug.Log($"LocateSpace(s={space}, bs={baseSpace}, t={time}");
|
||||||
|
return XrLocateSpace(space, baseSpace, time, ref location);
|
||||||
|
}
|
||||||
|
|
||||||
|
public XrResult DestroySpace(XrSpace space)
|
||||||
|
{
|
||||||
|
if (!isInited)
|
||||||
|
throw new Exception("ViveSpace: not initialized");
|
||||||
|
Debug.Log($"DestroySpace({space})");
|
||||||
|
return XrDestroySpace(space);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The XrSpace's Unity wrapper. Input and output are in Unity coordinate system.
|
||||||
|
/// After use it, you should call Dispose() to release the XrSpace.
|
||||||
|
/// </summary>
|
||||||
|
public class Space : IDisposable
|
||||||
|
{
|
||||||
|
protected XrSpace space;
|
||||||
|
private bool disposed = false;
|
||||||
|
|
||||||
|
public Space(XrSpace space)
|
||||||
|
{
|
||||||
|
Debug.Log($"Space({space})");
|
||||||
|
this.space = space;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the raw XrSpace. Only use it when class Space instance is alive.
|
||||||
|
/// You should not try to store this XrSpace, because it may be destroyed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrSpace GetXrSpace()
|
||||||
|
{
|
||||||
|
return space;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetRelatedPose(XrSpace baseSpace, XrTime time, out UnityEngine.Pose pose)
|
||||||
|
{
|
||||||
|
#if FAKE_DATA
|
||||||
|
if (Application.isEditor)
|
||||||
|
{
|
||||||
|
// make a random Pose
|
||||||
|
//var pos = new Vector3(UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f));
|
||||||
|
//var rot = new Quaternion(UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f));
|
||||||
|
var pos = Vector3.up;
|
||||||
|
var rot = Quaternion.identity;
|
||||||
|
rot.Normalize();
|
||||||
|
pose = new Pose(pos, rot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// If the xrBaseSpace is changed, the pose will be updated.
|
||||||
|
pose = default;
|
||||||
|
XrSpaceLocation location = new XrSpaceLocation();
|
||||||
|
location.type = XrStructureType.XR_TYPE_SPACE_LOCATION;
|
||||||
|
location.next = IntPtr.Zero;
|
||||||
|
var ret = SpaceWrapper.Instance.LocateSpace(space, baseSpace, time, ref location);
|
||||||
|
|
||||||
|
if (ret != XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
Debug.Log("Space: LocateSpace ret=" + ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("Space: baseSpace=" + baseSpace + ", space=" + space + ", time=" + time + ", ret=" + ret);
|
||||||
|
Debug.Log("Space: location.locationFlags=" + location.locationFlags);
|
||||||
|
Debug.Log("Space: location.pose.position=" + location.pose.position.x + "," + location.pose.position.y + "," + location.pose.position.z);
|
||||||
|
Debug.Log("Space: location.pose.orientation=" + location.pose.orientation.x + "," + location.pose.orientation.y + "," + location.pose.orientation.z + "," + location.pose.orientation.w);
|
||||||
|
if ((location.locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_VALID_BIT) > 0 &&
|
||||||
|
(location.locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) > 0)
|
||||||
|
{
|
||||||
|
pose = new Pose(location.pose.position.ToUnityVector(), location.pose.orientation.ToUnityQuaternion());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// Managered resource
|
||||||
|
}
|
||||||
|
// Non managered resource
|
||||||
|
Debug.Log($"Space: DestroySpace({space})");
|
||||||
|
SpaceWrapper.Instance.DestroySpace(space);
|
||||||
|
space = 0;
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~Space()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: f0160138780388c4aaa979212523b31f
|
guid: e8fbeadd31afbe14996c061ac261041d
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
83
com.htc.upm.vive.openxr/Runtime/Common/ViveInterceptors.cs
Normal file
83
com.htc.upm.vive.openxr/Runtime/Common/ViveInterceptors.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using AOT;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is made for all features that need to intercept OpenXR API calls.
|
||||||
|
/// Some APIs will be called by Unity internally, and we need to intercept them in c# to get some information.
|
||||||
|
/// Append more interceptable functions for this class by adding a new partial class.
|
||||||
|
/// The partial class can help the delegate name be nice to read and search.
|
||||||
|
/// Please create per function in one partial class.
|
||||||
|
///
|
||||||
|
/// For all features want to use this class, please call <see cref="HookGetInstanceProcAddr" /> in your feature class.
|
||||||
|
/// For example:
|
||||||
|
/// protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||||
|
/// {
|
||||||
|
/// return HtcInterceptors.Instance.HookGetInstanceProcAddr(func);
|
||||||
|
/// }
|
||||||
|
/// </summary>
|
||||||
|
partial class ViveInterceptors
|
||||||
|
{
|
||||||
|
public const string TAG = "Interceptors";
|
||||||
|
|
||||||
|
public static ViveInterceptors instance = null;
|
||||||
|
public static ViveInterceptors Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
instance = new ViveInterceptors();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViveInterceptors()
|
||||||
|
{
|
||||||
|
Debug.Log("HtcInterceptors");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInited = false;
|
||||||
|
|
||||||
|
public delegate XrResult DelegateXrGetInstanceProcAddr(XrInstance instance, string name, out IntPtr function);
|
||||||
|
private static readonly DelegateXrGetInstanceProcAddr hookXrGetInstanceProcAddrHandle = new DelegateXrGetInstanceProcAddr(XrGetInstanceProcAddrInterceptor);
|
||||||
|
private static readonly IntPtr hookGetInstanceProcAddrHandlePtr = Marshal.GetFunctionPointerForDelegate(hookXrGetInstanceProcAddrHandle);
|
||||||
|
static DelegateXrGetInstanceProcAddr XrGetInstanceProcAddrOriginal = null;
|
||||||
|
|
||||||
|
[MonoPInvokeCallback(typeof(DelegateXrGetInstanceProcAddr))]
|
||||||
|
private static XrResult XrGetInstanceProcAddrInterceptor(XrInstance instance, string name, out IntPtr function)
|
||||||
|
{
|
||||||
|
// Custom interceptors
|
||||||
|
if (name == "xrWaitFrame")
|
||||||
|
{
|
||||||
|
Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted.");
|
||||||
|
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||||
|
if (ret == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
XrWaitFrameOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrWaitFrame>(function);
|
||||||
|
function = xrWaitFrameInterceptorPtr;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||||
|
{
|
||||||
|
Debug.Log($"{TAG}: registering our own xrGetInstanceProcAddr");
|
||||||
|
if (XrGetInstanceProcAddrOriginal == null)
|
||||||
|
{
|
||||||
|
XrGetInstanceProcAddrOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrGetInstanceProcAddr>(func);
|
||||||
|
isInited = true;
|
||||||
|
return hookGetInstanceProcAddrHandlePtr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: be2b7b0a80ee6ac498ac4ff6bc764cf6
|
guid: 118a2474c266d174d834b364821865b5
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using AOT;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR
|
||||||
|
{
|
||||||
|
partial class ViveInterceptors
|
||||||
|
{
|
||||||
|
#region XRWaitFrame
|
||||||
|
public struct XrFrameWaitInfo
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct XrFrameState
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
public XrTime predictedDisplayTime;
|
||||||
|
public XrDuration predictedDisplayPeriod;
|
||||||
|
public XrBool32 shouldRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate XrResult DelegateXrWaitFrame(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState);
|
||||||
|
private static readonly DelegateXrWaitFrame xrWaitFrameInterceptorHandle = new DelegateXrWaitFrame(XrWaitFrameInterceptor);
|
||||||
|
private static readonly IntPtr xrWaitFrameInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrWaitFrameInterceptorHandle);
|
||||||
|
static DelegateXrWaitFrame XrWaitFrameOriginal = null;
|
||||||
|
|
||||||
|
[MonoPInvokeCallback(typeof(DelegateXrWaitFrame))]
|
||||||
|
private static XrResult XrWaitFrameInterceptor(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState)
|
||||||
|
{
|
||||||
|
var ret = XrWaitFrameOriginal(session, ref frameWaitInfo, ref frameState);
|
||||||
|
currentFrameState = frameState;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XrFrameState currentFrameState = new XrFrameState() { predictedDisplayTime = 0 };
|
||||||
|
|
||||||
|
public XrFrameState GetCurrentFrameState()
|
||||||
|
{
|
||||||
|
if (!isInited) throw new Exception("ViveInterceptors is not inited");
|
||||||
|
|
||||||
|
return currentFrameState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XrTime GetPredictTime()
|
||||||
|
{
|
||||||
|
if (!isInited) throw new Exception("ViveInterceptors is not inited");
|
||||||
|
|
||||||
|
Debug.Log($"{TAG}: XrWaitFrameInterceptor(predictedDisplayTime={currentFrameState.predictedDisplayTime}");
|
||||||
|
if (currentFrameState.predictedDisplayTime == 0)
|
||||||
|
return new XrTime((long)(1000000L * (Time.unscaledTimeAsDouble + 0.011f)));
|
||||||
|
else
|
||||||
|
return currentFrameState.predictedDisplayTime;
|
||||||
|
}
|
||||||
|
#endregion XRWaitFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: fbe8b74550a44c54fabdef905e9b1678
|
guid: 9bfc07f267ee39b47a4bcbb8c1c786cb
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// "VIVE SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the VIVE SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
Shader "VIVE/OpenXR/CompositionLayer/UnderlayAlphaZeroSolid"
|
||||||
|
{
|
||||||
|
Properties
|
||||||
|
{
|
||||||
|
_MainTex("Texture", 2D) = "white" {}
|
||||||
|
_ColorScale("Color Scale", Color) = (1,1,1,1)
|
||||||
|
_ColorBias("Color Bias", Color) = (0,0,0,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
SubShader
|
||||||
|
{
|
||||||
|
Tags {"Queue" = "Transparent" "RenderType" = "Transparent"}
|
||||||
|
LOD 500
|
||||||
|
|
||||||
|
ZWrite On
|
||||||
|
Blend Zero Zero
|
||||||
|
|
||||||
|
Pass
|
||||||
|
{
|
||||||
|
CGPROGRAM
|
||||||
|
#pragma vertex vert
|
||||||
|
#pragma fragment frag
|
||||||
|
|
||||||
|
#pragma multi_compile_local _ COLOR_SCALE_BIAS_ENABLED
|
||||||
|
|
||||||
|
#include "UnityCG.cginc"
|
||||||
|
|
||||||
|
struct appdata
|
||||||
|
{
|
||||||
|
float4 vertex : POSITION;
|
||||||
|
float2 uv : TEXCOORD0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct v2f
|
||||||
|
{
|
||||||
|
float2 uv : TEXCOORD0;
|
||||||
|
float4 vertex : SV_POSITION;
|
||||||
|
};
|
||||||
|
|
||||||
|
sampler2D _MainTex;
|
||||||
|
float4 _MainTex_ST;
|
||||||
|
|
||||||
|
fixed4 _ColorScale;
|
||||||
|
fixed4 _ColorBias;
|
||||||
|
|
||||||
|
v2f vert(appdata v)
|
||||||
|
{
|
||||||
|
v2f o;
|
||||||
|
o.vertex = UnityObjectToClipPos(v.vertex);
|
||||||
|
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed4 frag(v2f i) : SV_Target
|
||||||
|
{
|
||||||
|
fixed4 col = tex2D(_MainTex, i.uv);
|
||||||
|
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
ENDCG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 33a39a24d09f3cf48ae13735ee2209f6
|
||||||
|
ShaderImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
defaultTextures: []
|
||||||
|
nonModifiableTextures: []
|
||||||
|
preprocessorOverride: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -99,6 +99,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
public bool applyColorScaleBias = false;
|
public bool applyColorScaleBias = false;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
public bool solidEffect = false;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
public Color colorScale = Color.white;
|
public Color colorScale = Color.white;
|
||||||
|
|
||||||
@@ -457,7 +460,7 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
{
|
{
|
||||||
if (layerType == LayerType.Underlay)
|
if (layerType == LayerType.Underlay)
|
||||||
{
|
{
|
||||||
if (!enabledColorScaleBiasInShader)
|
//if (!enabledColorScaleBiasInShader)
|
||||||
{
|
{
|
||||||
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
|
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
|
||||||
{
|
{
|
||||||
@@ -482,6 +485,11 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
UnityToOpenXRConversionHelper.GetOpenXRColor4f(colorScale, ref CompositionLayerParamsColorScaleBias.colorScale);
|
UnityToOpenXRConversionHelper.GetOpenXRColor4f(colorScale, ref CompositionLayerParamsColorScaleBias.colorScale);
|
||||||
UnityToOpenXRConversionHelper.GetOpenXRColor4f(colorBias, ref CompositionLayerParamsColorScaleBias.colorBias);
|
UnityToOpenXRConversionHelper.GetOpenXRColor4f(colorBias, ref CompositionLayerParamsColorScaleBias.colorBias);
|
||||||
|
if (!solidEffect && enabledColorScaleBiasInShader)
|
||||||
|
{
|
||||||
|
CompositionLayerParamsColorScaleBias.colorScale.a = 1.0f;
|
||||||
|
CompositionLayerParamsColorScaleBias.colorBias.a = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
compositionLayerColorScaleBias.Submit_CompositionLayerColorBias(CompositionLayerParamsColorScaleBias, layerID);
|
compositionLayerColorScaleBias.Submit_CompositionLayerColorBias(CompositionLayerParamsColorScaleBias, layerID);
|
||||||
}
|
}
|
||||||
@@ -616,6 +624,11 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
CompositionLayerParamsQuad.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_QUAD;
|
CompositionLayerParamsQuad.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_QUAD;
|
||||||
CompositionLayerParamsQuad.layerFlags = ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT | ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
CompositionLayerParamsQuad.layerFlags = ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT | ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||||||
|
|
||||||
|
if (!enabledColorScaleBiasInShader)
|
||||||
|
{
|
||||||
|
CompositionLayerParamsQuad.layerFlags |= ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
switch (layerVisibility)
|
switch (layerVisibility)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
@@ -683,6 +696,11 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
CompositionLayerParamsCylinder.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR;
|
CompositionLayerParamsCylinder.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR;
|
||||||
CompositionLayerParamsCylinder.layerFlags = ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT | ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
CompositionLayerParamsCylinder.layerFlags = ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT | ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||||||
|
|
||||||
|
if (!enabledColorScaleBiasInShader)
|
||||||
|
{
|
||||||
|
CompositionLayerParamsCylinder.layerFlags |= ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
if (isHeadLock)
|
if (isHeadLock)
|
||||||
{
|
{
|
||||||
CompositionLayerParamsCylinder.space = compositionLayerFeature.HeadLockSpace;
|
CompositionLayerParamsCylinder.space = compositionLayerFeature.HeadLockSpace;
|
||||||
@@ -1419,7 +1437,10 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
generatedUnderlayMeshRenderer = generatedUnderlayMesh.AddComponent<MeshRenderer>();
|
generatedUnderlayMeshRenderer = generatedUnderlayMesh.AddComponent<MeshRenderer>();
|
||||||
generatedUnderlayMeshFilter = generatedUnderlayMesh.AddComponent<MeshFilter>();
|
generatedUnderlayMeshFilter = generatedUnderlayMesh.AddComponent<MeshFilter>();
|
||||||
generatedUnderlayMeshRenderer.sharedMaterial = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"));
|
if (solidEffect)
|
||||||
|
generatedUnderlayMeshRenderer.sharedMaterial = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZeroSolid"));
|
||||||
|
else
|
||||||
|
generatedUnderlayMeshRenderer.sharedMaterial = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"));
|
||||||
generatedUnderlayMeshRenderer.material.mainTexture = texture;
|
generatedUnderlayMeshRenderer.material.mainTexture = texture;
|
||||||
|
|
||||||
//Generate Mesh
|
//Generate Mesh
|
||||||
@@ -1441,7 +1462,10 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
generatedUnderlayMeshRenderer = generatedUnderlayMesh.AddComponent<MeshRenderer>();
|
generatedUnderlayMeshRenderer = generatedUnderlayMesh.AddComponent<MeshRenderer>();
|
||||||
generatedUnderlayMeshFilter = generatedUnderlayMesh.AddComponent<MeshFilter>();
|
generatedUnderlayMeshFilter = generatedUnderlayMesh.AddComponent<MeshFilter>();
|
||||||
generatedUnderlayMeshRenderer.material = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"));
|
if (solidEffect)
|
||||||
|
generatedUnderlayMeshRenderer.material = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZeroSolid"));
|
||||||
|
else
|
||||||
|
generatedUnderlayMeshRenderer.material = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"));
|
||||||
generatedUnderlayMeshRenderer.material.mainTexture = texture;
|
generatedUnderlayMeshRenderer.material.mainTexture = texture;
|
||||||
|
|
||||||
//Generate Mesh
|
//Generate Mesh
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: ac2dcc122c218e7419c571b30aad6bbc
|
guid: 673b5df0bff21a84c8b23a4f3c6a6268
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 046b5fd65fa996041a970e1fd193d213
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 69ae1c3151561af42ba226f0e563ebc6
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: c44051e3658496a428f4b92daa752ebf
|
guid: c5cbfbcf56aaffa4fab38659c00c3903
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
|
||||||
|
// Remove FAKE_DATA if editor or windows is supported.
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
#define FAKE_DATA
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using System;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
#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
|
||||||
|
)]
|
||||||
|
#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;
|
||||||
|
|
||||||
|
#region struct, enum, const of this extensions
|
||||||
|
|
||||||
|
public struct XrSystemAnchorPropertiesHTC
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public System.IntPtr next;
|
||||||
|
public XrBool32 supportsAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||||
|
public struct XrSpatialAnchorNameHTC
|
||||||
|
{
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||||
|
public string name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct XrSpatialAnchorCreateInfoHTC
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public System.IntPtr next;
|
||||||
|
public XrSpace space;
|
||||||
|
public XrPosef poseInSpace;
|
||||||
|
public XrSpatialAnchorNameHTC name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region delegates and delegate instances
|
||||||
|
delegate XrResult DelegateXrCreateSpatialAnchorHTC(XrSession session, ref XrSpatialAnchorCreateInfoHTC createInfo, ref XrSpace anchor);
|
||||||
|
delegate XrResult DelegateXrGetSpatialAnchorNameHTC(XrSpace anchor, ref XrSpatialAnchorNameHTC name);
|
||||||
|
|
||||||
|
DelegateXrCreateSpatialAnchorHTC XrCreateSpatialAnchorHTC;
|
||||||
|
DelegateXrGetSpatialAnchorNameHTC XrGetSpatialAnchorNameHTC;
|
||||||
|
#endregion delegates and delegate instances
|
||||||
|
|
||||||
|
#region override functions
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||||
|
{
|
||||||
|
Debug.Log("ViveAnchor HookGetInstanceProcAddr() ");
|
||||||
|
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_XrInstance = xrInstance;
|
||||||
|
//Debug.Log("OnInstanceCreate() " + m_XrInstance);
|
||||||
|
CommonWrapper.Instance.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr);
|
||||||
|
SpaceWrapper.Instance.OnInstanceCreate(xrInstance, CommonWrapper.Instance.GetInstanceProcAddr);
|
||||||
|
|
||||||
|
return GetXrFunctionDelegates(m_XrInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInstanceDestroy(ulong xrInstance)
|
||||||
|
{
|
||||||
|
CommonWrapper.Instance.OnInstanceDestroy();
|
||||||
|
SpaceWrapper.Instance.OnInstanceDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnSessionCreate(ulong xrSession)
|
||||||
|
{
|
||||||
|
Debug.Log("ViveAnchor OnSessionCreate() ");
|
||||||
|
|
||||||
|
// here's one way you can grab the session
|
||||||
|
Debug.Log($"EXT: Got xrSession: {xrSession}");
|
||||||
|
session = xrSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnSessionBegin(ulong xrSession)
|
||||||
|
{
|
||||||
|
Debug.Log("ViveAnchor OnSessionBegin() ");
|
||||||
|
Debug.Log($"EXT: xrBeginSession: {xrSession}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnSessionEnd(ulong xrSession)
|
||||||
|
{
|
||||||
|
Debug.Log("ViveAnchor OnSessionEnd() ");
|
||||||
|
Debug.Log($"EXT: about to xrEndSession: {xrSession}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 xrInstance)
|
||||||
|
{
|
||||||
|
Debug.Log("ViveAnchor GetXrFunctionDelegates() ");
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
#if FAKE_DATA
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c17aa731a6f4fb54bb9a2c28df667e5e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -22,7 +22,7 @@ using UnityEditor.XR.OpenXR.Features;
|
|||||||
namespace VIVE.OpenXR.DisplayRefreshRate
|
namespace VIVE.OpenXR.DisplayRefreshRate
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
[OpenXRFeature(UiName = "XR FB Display Refresh Rate",
|
[OpenXRFeature(UiName = "VIVE XR Display Refresh Rate",
|
||||||
BuildTargetGroups = new[] { BuildTargetGroup.Android},
|
BuildTargetGroups = new[] { BuildTargetGroup.Android},
|
||||||
Company = "HTC",
|
Company = "HTC",
|
||||||
Desc = "Support the display refresh rate.",
|
Desc = "Support the display refresh rate.",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine.XR;
|
using UnityEngine.XR;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using AOT;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.XR.OpenXR.Features;
|
using UnityEditor.XR.OpenXR.Features;
|
||||||
@@ -45,6 +45,51 @@ namespace VIVE.OpenXR.Hand
|
|||||||
#region OpenXR Life Cycle
|
#region OpenXR Life Cycle
|
||||||
private bool m_XrInstanceCreated = false;
|
private bool m_XrInstanceCreated = false;
|
||||||
private XrInstance m_XrInstance = 0;
|
private XrInstance m_XrInstance = 0;
|
||||||
|
private static IntPtr xrGetInstanceProcAddr_prev;
|
||||||
|
private static IntPtr WaitFrame_prev;
|
||||||
|
private static XrFrameWaitInfo m_frameWaitInfo;
|
||||||
|
private static XrFrameState m_frameState;
|
||||||
|
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.Log("EXT: registering our own xrGetInstanceProcAddr");
|
||||||
|
xrGetInstanceProcAddr_prev = func;
|
||||||
|
return Marshal.GetFunctionPointerForDelegate(m_intercept_xrWaitFrame_xrGetInstanceProcAddr);
|
||||||
|
}
|
||||||
|
[MonoPInvokeCallback(typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate))]
|
||||||
|
private static XrResult intercept_xrWaitFrame_xrGetInstanceProcAddr(XrInstance instance, string name, out IntPtr function)
|
||||||
|
{
|
||||||
|
if (xrGetInstanceProcAddr_prev == null || xrGetInstanceProcAddr_prev == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError("xrGetInstanceProcAddr_prev is null");
|
||||||
|
function = IntPtr.Zero;
|
||||||
|
return XrResult.XR_ERROR_VALIDATION_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get delegate of old xrGetInstanceProcAddr.
|
||||||
|
var xrGetProc = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrGetInstanceProcAddrDelegate>(xrGetInstanceProcAddr_prev);
|
||||||
|
XrResult result = xrGetProc(instance, name, out function);
|
||||||
|
if (name == "xrWaitFrame")
|
||||||
|
{
|
||||||
|
WaitFrame_prev = function;
|
||||||
|
m_intercept_xrWaitFrame = intercepted_xrWaitFrame;
|
||||||
|
function = Marshal.GetFunctionPointerForDelegate(m_intercept_xrWaitFrame); ;
|
||||||
|
UnityEngine.Debug.Log("Getting xrWaitFrame func");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
[MonoPInvokeCallback(typeof(OpenXRHelper.xrWaitFrameDelegate))]
|
||||||
|
private static int intercepted_xrWaitFrame(ulong session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState)
|
||||||
|
{
|
||||||
|
// Get delegate of prev xrWaitFrame.
|
||||||
|
var xrWaitFrame = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrWaitFrameDelegate>(WaitFrame_prev);
|
||||||
|
int res = xrWaitFrame(session, ref frameWaitInfo, ref frameState);
|
||||||
|
m_frameWaitInfo = frameWaitInfo;
|
||||||
|
m_frameState = frameState;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
|
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -276,6 +321,9 @@ namespace VIVE.OpenXR.Hand
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region OpenXR function delegates
|
#region OpenXR function delegates
|
||||||
|
private static readonly OpenXRHelper.xrGetInstanceProcAddrDelegate m_intercept_xrWaitFrame_xrGetInstanceProcAddr
|
||||||
|
= new OpenXRHelper.xrGetInstanceProcAddrDelegate(intercept_xrWaitFrame_xrGetInstanceProcAddr);
|
||||||
|
private static OpenXRHelper.xrWaitFrameDelegate m_intercept_xrWaitFrame;
|
||||||
/// xrGetInstanceProcAddr
|
/// xrGetInstanceProcAddr
|
||||||
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
||||||
|
|
||||||
@@ -747,7 +795,7 @@ namespace VIVE.OpenXR.Hand
|
|||||||
in_type: XrStructureType.XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT,
|
in_type: XrStructureType.XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT,
|
||||||
in_next: IntPtr.Zero,
|
in_next: IntPtr.Zero,
|
||||||
in_baseSpace: baseSpace,
|
in_baseSpace: baseSpace,
|
||||||
in_time: 1);//
|
in_time: m_frameState.predictedDisplayTime);
|
||||||
|
|
||||||
/// Configures XrHandJointLocationsEXT
|
/// Configures XrHandJointLocationsEXT
|
||||||
locations.type = XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT;
|
locations.type = XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT;
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ using UnityEngine.XR.OpenXR;
|
|||||||
using UnityEngine.XR.OpenXR.Features;
|
using UnityEngine.XR.OpenXR.Features;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
@@ -14,7 +17,7 @@ namespace VIVE.OpenXR
|
|||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
[OpenXRFeature(UiName = "VIVE XR Path Enumeration",
|
[OpenXRFeature(UiName = "VIVE XR Path Enumeration",
|
||||||
BuildTargetGroups = new[] { BuildTargetGroup.Android },
|
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
|
||||||
Company = "HTC",
|
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.",
|
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.",
|
||||||
DocumentationLink = "..\\Documentation",
|
DocumentationLink = "..\\Documentation",
|
||||||
@@ -34,7 +37,7 @@ namespace VIVE.OpenXR
|
|||||||
}
|
}
|
||||||
void DEBUG(StringBuilder msg) { Debug.Log(msg); }
|
void DEBUG(StringBuilder msg) { Debug.Log(msg); }
|
||||||
void WARNING(StringBuilder msg) { Debug.LogWarning(msg); }
|
void WARNING(StringBuilder msg) { Debug.LogWarning(msg); }
|
||||||
|
void ERROR(StringBuilder msg) { Debug.LogError(msg); }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_path_enumeration">12.1. XR_HTC_path_enumeration</see>.
|
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_path_enumeration">12.1. XR_HTC_path_enumeration</see>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -66,7 +69,7 @@ namespace VIVE.OpenXR
|
|||||||
m_XrInstanceCreated = true;
|
m_XrInstanceCreated = true;
|
||||||
m_XrInstance = xrInstance;
|
m_XrInstance = xrInstance;
|
||||||
sb.Clear().Append(LOG_TAG).Append("OnInstanceCreate() ").Append(m_XrInstance); DEBUG(sb);
|
sb.Clear().Append(LOG_TAG).Append("OnInstanceCreate() ").Append(m_XrInstance); DEBUG(sb);
|
||||||
|
GetXrFunctionDelegates(m_XrInstance);
|
||||||
return base.OnInstanceCreate(xrInstance);
|
return base.OnInstanceCreate(xrInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,5 +121,183 @@ namespace VIVE.OpenXR
|
|||||||
m_XrSessionCreated = false;
|
m_XrSessionCreated = false;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
#region OpenXR function delegates
|
||||||
|
/// xrGetInstanceProcAddr
|
||||||
|
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
||||||
|
VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate xrEnumeratePathsForInteractionProfileHTC;
|
||||||
|
private bool GetXrFunctionDelegates(XrInstance xrInstance)
|
||||||
|
{
|
||||||
|
// xrGetInstanceProcAddr
|
||||||
|
if (xrGetInstanceProcAddr != null && xrGetInstanceProcAddr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("Get function pointer of xrGetInstanceProcAddr."); DEBUG(sb);
|
||||||
|
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(
|
||||||
|
xrGetInstanceProcAddr,
|
||||||
|
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("No function pointer of xrGetInstanceProcAddr"); ERROR(sb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPtr funcPtr = IntPtr.Zero;
|
||||||
|
/// xrEnumeratePathsForInteractionProfileHTC
|
||||||
|
if (XrGetInstanceProcAddr(xrInstance, "xrEnumeratePathsForInteractionProfileHTC", out funcPtr) == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (funcPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("Get function pointer of xrEnumeratePathsForInteractionProfileHTC."); DEBUG(sb);
|
||||||
|
xrEnumeratePathsForInteractionProfileHTC = Marshal.GetDelegateForFunctionPointer(
|
||||||
|
funcPtr,
|
||||||
|
typeof(VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate)) as VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC.");
|
||||||
|
ERROR(sb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC");
|
||||||
|
ERROR(sb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private List<T> CreateList<T>(UInt32 count, T initialValue)
|
||||||
|
{
|
||||||
|
List<T> list = new List<T>();
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
list.Add(initialValue);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XrResult EnumeratePathsForInteractionProfileHTC(
|
||||||
|
ref XrPathsForInteractionProfileEnumerateInfoHTC createInfo,
|
||||||
|
UInt32 pathCapacityInput,
|
||||||
|
ref UInt32 pathCountOutput,
|
||||||
|
[In, Out] XrPath[] paths)
|
||||||
|
{
|
||||||
|
if (!m_XrInstanceCreated)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("EnumeratePathsForInteractionProfileHTC() XR_ERROR_INSTANCE_LOST."); ERROR(sb);
|
||||||
|
paths = null;
|
||||||
|
return XrResult.XR_ERROR_INSTANCE_LOST;
|
||||||
|
}
|
||||||
|
return xrEnumeratePathsForInteractionProfileHTC(m_XrInstance,
|
||||||
|
ref createInfo, pathCapacityInput,ref pathCountOutput, paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetUserPaths(string interactionProfileString, out XrPath[] userPaths)
|
||||||
|
{
|
||||||
|
XrPathsForInteractionProfileEnumerateInfoHTC enumerateInfo;
|
||||||
|
if (!m_XrInstanceCreated) { userPaths = null; return false; }
|
||||||
|
|
||||||
|
string func = "GetUserPaths() ";
|
||||||
|
|
||||||
|
if (xrEnumeratePathsForInteractionProfileHTC == null)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC"); WARNING(sb);
|
||||||
|
userPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 1. Get user path count of sepecified profile.
|
||||||
|
UInt32 trackerCount = 0;
|
||||||
|
enumerateInfo.type = (XrStructureType)1000319000;//Todo : update openxr spec to prevent hot code.
|
||||||
|
enumerateInfo.next = IntPtr.Zero;
|
||||||
|
enumerateInfo.interactionProfile = StringToPath(interactionProfileString);
|
||||||
|
enumerateInfo.userPath = OpenXRHelper.XR_NULL_PATH;
|
||||||
|
|
||||||
|
XrResult result = xrEnumeratePathsForInteractionProfileHTC(m_XrInstance, ref enumerateInfo, 0, ref trackerCount, null);
|
||||||
|
if (result != XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("Retrieves trackerCount failed."); ERROR(sb);
|
||||||
|
userPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
// .Append("Get profile ").Append(interactionProfileString).Append(" user path count: ").Append(trackerCount); DEBUG(sb);
|
||||||
|
if (trackerCount > 0)
|
||||||
|
{
|
||||||
|
// 2. Get user paths of sepecified profile.
|
||||||
|
List<XrPath> trackerList = CreateList<XrPath>(trackerCount, OpenXRHelper.XR_NULL_PATH);
|
||||||
|
XrPath[] trackers = trackerList.ToArray();
|
||||||
|
result = xrEnumeratePathsForInteractionProfileHTC(
|
||||||
|
m_XrInstance,
|
||||||
|
ref enumerateInfo,
|
||||||
|
pathCapacityInput: (UInt32)(trackers.Length & 0x7FFFFFFF),
|
||||||
|
pathCountOutput: ref trackerCount,
|
||||||
|
paths: trackers);
|
||||||
|
if (result != XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("Retrieves trackers failed."); ERROR(sb);
|
||||||
|
userPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
userPaths = trackers;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetInputPathsWithUserPath(string interactionProfileString, XrPath userPath, out XrPath[] inputPaths)
|
||||||
|
{
|
||||||
|
string func = "GetInputPathsWithUserPath() ";
|
||||||
|
if (!m_XrInstanceCreated) { inputPaths = null; return false; }
|
||||||
|
UInt32 trackerCount = 0;
|
||||||
|
XrPathsForInteractionProfileEnumerateInfoHTC enumerateInfo;
|
||||||
|
enumerateInfo.type = (XrStructureType)1000319000;//Todo : update openxr spec and prevent hard-code.
|
||||||
|
enumerateInfo.next = IntPtr.Zero;
|
||||||
|
enumerateInfo.interactionProfile = StringToPath(interactionProfileString);
|
||||||
|
enumerateInfo.userPath = userPath;
|
||||||
|
UInt32 Count = 0;
|
||||||
|
xrEnumeratePathsForInteractionProfileHTC(
|
||||||
|
m_XrInstance,
|
||||||
|
ref enumerateInfo,
|
||||||
|
0,
|
||||||
|
pathCountOutput: ref Count,
|
||||||
|
paths: null);
|
||||||
|
if (Count > 0)
|
||||||
|
{
|
||||||
|
List<XrPath> pathlist = CreateList<XrPath>(Count, OpenXRHelper.XR_NULL_PATH);
|
||||||
|
inputPaths = pathlist.ToArray();
|
||||||
|
XrResult result = xrEnumeratePathsForInteractionProfileHTC(
|
||||||
|
m_XrInstance,
|
||||||
|
ref enumerateInfo,
|
||||||
|
pathCapacityInput: (UInt32)(inputPaths.Length & 0x7FFFFFFF),
|
||||||
|
pathCountOutput: ref Count,
|
||||||
|
paths: inputPaths);
|
||||||
|
UnityEngine.Debug.Log("Get inputpath str : "+PathToString(inputPaths[0]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inputPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
public string xrPathToString(ulong path)
|
||||||
|
{
|
||||||
|
return PathToString(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong xrStringToPath(string str)
|
||||||
|
{
|
||||||
|
return StringToPath(str);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 33a547f8c3209594c84b8a4a968d8073
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 226c5d25c53e5794baacc000d2eaf274
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9209a4fdd88b4bd4e88afcf05e69cdfd
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: a88ad0ff22dd2484192540d5b8b63ed6
|
guid: 174d574cfa752ab4e9c6354d2a6c14c4
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,530 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
#define FAKE_DATA
|
||||||
|
#endif
|
||||||
|
using System;
|
||||||
|
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.PlaneDetection
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
[OpenXRFeature(UiName = "VIVE XR PlaneDetection",
|
||||||
|
Desc = "VIVE's implementaion of the XR_EXT_plane_detection.",
|
||||||
|
Company = "HTC",
|
||||||
|
DocumentationLink = "..\\Documentation",
|
||||||
|
OpenxrExtensionStrings = kOpenxrExtensionString,
|
||||||
|
Version = "1.0.0",
|
||||||
|
BuildTargetGroups = new[] { BuildTargetGroup.Android },
|
||||||
|
FeatureId = featureId
|
||||||
|
)]
|
||||||
|
#endif
|
||||||
|
public class VivePlaneDetection : OpenXRFeature
|
||||||
|
{
|
||||||
|
public const string kOpenxrExtensionString = "XR_EXT_plane_detection";
|
||||||
|
/// <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.planedetection";
|
||||||
|
private bool m_XrInstanceCreated = false;
|
||||||
|
private XrInstance m_XrInstance = 0;
|
||||||
|
private bool m_XrSessionCreated = false;
|
||||||
|
private XrSession session = 0;
|
||||||
|
private XrSystemId m_XrSystemId = 0;
|
||||||
|
|
||||||
|
|
||||||
|
#region struct, enum, const of this extensions
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectorOrientationEXT">XrPlaneDetectorOrientationEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public enum XrPlaneDetectorOrientationEXT
|
||||||
|
{
|
||||||
|
HORIZONTAL_UPWARD_EXT = 0,
|
||||||
|
HORIZONTAL_DOWNWARD_EXT = 1,
|
||||||
|
VERTICAL_EXT = 2,
|
||||||
|
ARBITRARY_EXT = 3,
|
||||||
|
MAX_ENUM_EXT = 0x7FFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectorSemanticTypeEXT">XrPlaneDetectorSemanticTypeEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public enum XrPlaneDetectorSemanticTypeEXT
|
||||||
|
{
|
||||||
|
UNDEFINED_EXT = 0,
|
||||||
|
CEILING_EXT = 1,
|
||||||
|
FLOOR_EXT = 2,
|
||||||
|
WALL_EXT = 3,
|
||||||
|
PLATFORM_EXT = 4,
|
||||||
|
MAX_ENUM_EXT = 0x7FFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectionStateEXT">XrPlaneDetectionStateEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public enum XrPlaneDetectionStateEXT
|
||||||
|
{
|
||||||
|
NONE_EXT = 0,
|
||||||
|
PENDING_EXT = 1, // Try get plane detection state again
|
||||||
|
DONE_EXT = 2, // Ready to get result
|
||||||
|
ERROR_EXT = 3, // Can try begin again
|
||||||
|
FATAL_EXT = 4, // Should destroy the plane detector
|
||||||
|
MAX_ENUM_EXT = 0x7FFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
//XrFlags64 XrPlaneDetectionCapabilityFlagsEXT;
|
||||||
|
|
||||||
|
// Flag bits for XrPlaneDetectionCapabilityFlagsEXT
|
||||||
|
public static XrFlags64 CAPABILITY_PLANE_DETECTION_BIT_EXT = 0x00000001;
|
||||||
|
public static XrFlags64 CAPABILITY_PLANE_HOLES_BIT_EXT = 0x00000002;
|
||||||
|
public static XrFlags64 CAPABILITY_SEMANTIC_CEILING_BIT_EXT = 0x00000004;
|
||||||
|
public static XrFlags64 CAPABILITY_SEMANTIC_FLOOR_BIT_EXT = 0x00000008;
|
||||||
|
public static XrFlags64 CAPABILITY_SEMANTIC_WALL_BIT_EXT = 0x00000010;
|
||||||
|
public static XrFlags64 CAPABILITY_SEMANTIC_PLATFORM_BIT_EXT = 0x00000020;
|
||||||
|
public static XrFlags64 CAPABILITY_ORIENTATION_BIT_EXT = 0x00000040;
|
||||||
|
|
||||||
|
//XrFlags64 XrPlaneDetectorFlagsEXT;
|
||||||
|
|
||||||
|
// Flag bits for XrPlaneDetectorFlagsEXT
|
||||||
|
public static XrFlags64 XR_PLANE_DETECTOR_ENABLE_CONTOUR_BIT_EXT = 0x00000001;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrSystemPlaneDetectionPropertiesEXT">XrSystemPlaneDetectionPropertiesEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public struct XrSystemPlaneDetectionPropertiesEXT
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
public XrFlags64 supportedFeatures; // XrPlaneDetectionCapabilityFlagsEXT
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectorCreateInfoEXT">XrPlaneDetectorCreateInfoEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public struct XrPlaneDetectorCreateInfoEXT
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
public XrFlags64 flags; // XrPlaneDetectorFlagsEXT
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrExtent3DfEXT">XrExtent3DfEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public struct XrExtent3DfEXT
|
||||||
|
{
|
||||||
|
public float width;
|
||||||
|
public float height;
|
||||||
|
public float depth;
|
||||||
|
public XrExtent3DfEXT(float width, float height, float depth)
|
||||||
|
{
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.depth = depth;
|
||||||
|
}
|
||||||
|
public XrExtent3DfEXT One => new XrExtent3DfEXT(1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectorBeginInfoEXT">XrPlaneDetectorBeginInfoEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public struct XrPlaneDetectorBeginInfoEXT
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
public XrSpace baseSpace;
|
||||||
|
public XrTime time;
|
||||||
|
public uint orientationCount;
|
||||||
|
public IntPtr orientations; // XrPlaneDetectorOrientationEXT[]
|
||||||
|
public uint semanticTypeCount;
|
||||||
|
public IntPtr semanticTypes; // XrPlaneDetectorSemanticTypeEXT[]
|
||||||
|
public uint maxPlanes;
|
||||||
|
public float minArea;
|
||||||
|
public XrPosef boundingBoxPose;
|
||||||
|
public XrExtent3DfEXT boundingBoxExtent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectorGetInfoEXT">XrPlaneDetectorGetInfoEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public struct XrPlaneDetectorGetInfoEXT
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
public XrSpace baseSpace;
|
||||||
|
public XrTime time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectorLocationEXT">XrPlaneDetectorLocationEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public struct XrPlaneDetectorLocationEXT
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
public ulong planeId;
|
||||||
|
public XrSpaceLocationFlags locationFlags;
|
||||||
|
public XrPosef pose;
|
||||||
|
public XrExtent2Df extents;
|
||||||
|
public XrPlaneDetectorOrientationEXT orientation;
|
||||||
|
public XrPlaneDetectorSemanticTypeEXT semanticType;
|
||||||
|
public uint polygonBufferCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectorLocationsEXT">XrPlaneDetectorLocationsEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public struct XrPlaneDetectorLocationsEXT
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
public uint planeLocationCapacityInput;
|
||||||
|
public uint planeLocationCountOutput;
|
||||||
|
public IntPtr planeLocations; // XrPlaneDetectorLocationEXT[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XrPlaneDetectorPolygonBufferEXT">XrPlaneDetectorPolygonBufferEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
public struct XrPlaneDetectorPolygonBufferEXT
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public IntPtr next;
|
||||||
|
public uint vertexCapacityInput;
|
||||||
|
public uint vertexCountOutput;
|
||||||
|
public IntPtr vertices; // XrVector2f[]
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region delegates and delegate instances
|
||||||
|
|
||||||
|
delegate XrResult DelegateXrCreatePlaneDetectorEXT(XrSession session, ref XrPlaneDetectorCreateInfoEXT createInfo, ref IntPtr/*XrPlaneDetectorEXT*/
|
||||||
|
planeDetector);
|
||||||
|
delegate XrResult DelegateXrDestroyPlaneDetectorEXT(IntPtr/*XrPlaneDetectorEXT*/ planeDetector);
|
||||||
|
delegate XrResult DelegateXrBeginPlaneDetectionEXT(IntPtr/*XrPlaneDetectorEXT*/ planeDetector, ref XrPlaneDetectorBeginInfoEXT beginInfo);
|
||||||
|
delegate XrResult DelegateXrGetPlaneDetectionStateEXT(IntPtr/*XrPlaneDetectorEXT*/planeDetector, ref XrPlaneDetectionStateEXT state);
|
||||||
|
delegate XrResult DelegateXrGetPlaneDetectionsEXT(IntPtr/*XrPlaneDetectorEXT*/planeDetector, ref XrPlaneDetectorGetInfoEXT info, ref XrPlaneDetectorLocationsEXT locations);
|
||||||
|
delegate XrResult DelegateXrGetPlanePolygonBufferEXT(IntPtr/*XrPlaneDetectorEXT*/planeDetector, ulong planeId, uint polygonBufferIndex, ref XrPlaneDetectorPolygonBufferEXT polygonBuffer);
|
||||||
|
|
||||||
|
DelegateXrCreatePlaneDetectorEXT XrCreatePlaneDetectorEXT;
|
||||||
|
DelegateXrDestroyPlaneDetectorEXT XrDestroyPlaneDetectorEXT;
|
||||||
|
DelegateXrBeginPlaneDetectionEXT XrBeginPlaneDetectionEXT;
|
||||||
|
DelegateXrGetPlaneDetectionStateEXT XrGetPlaneDetectionStateEXT;
|
||||||
|
DelegateXrGetPlaneDetectionsEXT XrGetPlaneDetectionsEXT;
|
||||||
|
DelegateXrGetPlanePolygonBufferEXT XrGetPlanePolygonBufferEXT;
|
||||||
|
#endregion delegates and delegate instances
|
||||||
|
|
||||||
|
#region override functions
|
||||||
|
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||||
|
{
|
||||||
|
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override bool OnInstanceCreate(ulong xrInstance)
|
||||||
|
{
|
||||||
|
//Debug.Log("VIVEPD OnInstanceCreate() ");
|
||||||
|
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
|
||||||
|
{
|
||||||
|
Debug.LogWarning("OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_XrInstanceCreated = true;
|
||||||
|
m_XrInstance = xrInstance;
|
||||||
|
//Debug.Log("OnInstanceCreate() " + m_XrInstance);
|
||||||
|
|
||||||
|
CommonWrapper.Instance.OnInstanceCreate(xrInstance, xrGetInstanceProcAddr);
|
||||||
|
SpaceWrapper.Instance.OnInstanceCreate(xrInstance, CommonWrapper.Instance.GetInstanceProcAddr);
|
||||||
|
|
||||||
|
return GetXrFunctionDelegates(m_XrInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInstanceDestroy(ulong xrInstance)
|
||||||
|
{
|
||||||
|
CommonWrapper.Instance.OnInstanceDestroy();
|
||||||
|
SpaceWrapper.Instance.OnInstanceDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnSessionCreate(ulong xrSession)
|
||||||
|
{
|
||||||
|
Debug.Log("VIVEPD OnSessionCreate() ");
|
||||||
|
|
||||||
|
// here's one way you can grab the session
|
||||||
|
Debug.Log($"EXT: Got xrSession: {xrSession}");
|
||||||
|
session = xrSession;
|
||||||
|
m_XrSessionCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnSessionBegin(ulong xrSession)
|
||||||
|
{
|
||||||
|
Debug.Log("VIVEPD OnSessionBegin() ");
|
||||||
|
|
||||||
|
Debug.Log($"EXT: xrBeginSession: {xrSession}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnSessionEnd(ulong xrSession)
|
||||||
|
{
|
||||||
|
Debug.Log("VIVEPD OnSessionEnd() ");
|
||||||
|
|
||||||
|
Debug.Log($"EXT: about to xrEndSession: {xrSession}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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($"VIVEPD OnAppSpaceChange({appSpace} -> {space})");
|
||||||
|
// appSpace = space;
|
||||||
|
//}
|
||||||
|
|
||||||
|
protected override void OnSystemChange(ulong xrSystem)
|
||||||
|
{
|
||||||
|
m_XrSystemId = xrSystem;
|
||||||
|
Debug.Log("OnSystemChange() " + m_XrSystemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion override functions
|
||||||
|
|
||||||
|
private bool GetXrFunctionDelegates(XrInstance xrInstance)
|
||||||
|
{
|
||||||
|
Debug.Log("VIVEPD GetXrFunctionDelegates() ");
|
||||||
|
|
||||||
|
bool ret = true;
|
||||||
|
IntPtr funcPtr = IntPtr.Zero;
|
||||||
|
OpenXRHelper.xrGetInstanceProcAddrDelegate GetAddr = CommonWrapper.Instance.GetInstanceProcAddr; // shorter name
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrCreatePlaneDetectorEXT", out XrCreatePlaneDetectorEXT);
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrDestroyPlaneDetectorEXT", out XrDestroyPlaneDetectorEXT);
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrBeginPlaneDetectionEXT", out XrBeginPlaneDetectionEXT);
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrGetPlaneDetectionStateEXT", out XrGetPlaneDetectionStateEXT);
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrGetPlaneDetectionsEXT", out XrGetPlaneDetectionsEXT);
|
||||||
|
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrGetPlanePolygonBufferEXT", out XrGetPlanePolygonBufferEXT);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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 XrSystemPlaneDetectionPropertiesEXT properties)
|
||||||
|
{
|
||||||
|
properties = new XrSystemPlaneDetectionPropertiesEXT();
|
||||||
|
properties.type = XrStructureType.XR_TYPE_SYSTEM_PLANE_DETECTION_PROPERTIES_EXT;
|
||||||
|
|
||||||
|
#if FAKE_DATA
|
||||||
|
if (Application.isEditor)
|
||||||
|
{
|
||||||
|
properties.supportedFeatures = CAPABILITY_PLANE_DETECTION_BIT_EXT;
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!m_XrSessionCreated)
|
||||||
|
{
|
||||||
|
Debug.LogError("GetProperties() XR_ERROR_SESSION_LOST.");
|
||||||
|
return XrResult.XR_ERROR_SESSION_LOST;
|
||||||
|
}
|
||||||
|
if (!m_XrInstanceCreated)
|
||||||
|
{
|
||||||
|
Debug.LogError("GetProperties() XR_ERROR_INSTANCE_LOST.");
|
||||||
|
return XrResult.XR_ERROR_INSTANCE_LOST;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommonWrapper.Instance.GetProperties(m_XrInstance, m_XrSystemId, ref properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a PlaneDetector with <paramref name="createInfo"/>. XrSession is implied. The output handle need be destroyed.
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreatePlaneDetectorEXT">xrCreatePlaneDetectorEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="createInfo">Fill flags for detection engine.</param>
|
||||||
|
/// <param name="planeDetector">The output detector's handle.</param>
|
||||||
|
/// <seealso cref="DestroyPlaneDetector"/>
|
||||||
|
public XrResult CreatePlaneDetector(XrPlaneDetectorCreateInfoEXT createInfo, out IntPtr/*XrPlaneDetectorEXT*/ planeDetector)
|
||||||
|
{
|
||||||
|
planeDetector = IntPtr.Zero;
|
||||||
|
#if FAKE_DATA
|
||||||
|
if (Application.isEditor)
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
#endif
|
||||||
|
return XrCreatePlaneDetectorEXT(session, ref createInfo, ref planeDetector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroy the PlaneDetector handle.
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyPlaneDetectorEXT">xrDestroyPlaneDetectorEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="planeDetector">The detector's handle to be destroyed.</param>
|
||||||
|
/// <seealso cref="CreatePlaneDetector"/>
|
||||||
|
public XrResult DestroyPlaneDetector(IntPtr/*XrPlaneDetectorEXT*/ planeDetector)
|
||||||
|
{
|
||||||
|
#if FAKE_DATA
|
||||||
|
if (Application.isEditor)
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
#endif
|
||||||
|
return XrDestroyPlaneDetectorEXT(planeDetector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Let Detector start to work.
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrBeginPlaneDetectionEXT">xrBeginPlaneDetectionEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="planeDetector">The detector's handle to be begined.</param>
|
||||||
|
/// <param name="beginInfo">Fill flags for detection engine. You can use the result of MakeGetAllXrPlaneDetectorBeginInfoEXT.</param>
|
||||||
|
/// <seealso cref="MakeGetAllXrPlaneDetectorBeginInfoEXT"/>
|
||||||
|
public XrResult BeginPlaneDetection(IntPtr/*XrPlaneDetectorEXT*/ planeDetector, XrPlaneDetectorBeginInfoEXT beginInfo)
|
||||||
|
{
|
||||||
|
#if FAKE_DATA
|
||||||
|
if (Application.isEditor)
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
#endif
|
||||||
|
return XrBeginPlaneDetectionEXT(planeDetector, ref beginInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check PlaneDetector's state. If the state is DONE_EXT, you can get the result by GetPlaneDetections.
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetPlaneDetectionStateEXT">xrGetPlaneDetectionStateEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="planeDetector">The detector's state to be check.</param>
|
||||||
|
/// <param name="state">Fill flags for detection engine. You can use the result of MakeGetAllXrPlaneDetectorBeginInfoEXT.</param>
|
||||||
|
public XrResult GetPlaneDetectionState(IntPtr/*XrPlaneDetectorEXT*/ planeDetector, ref XrPlaneDetectionStateEXT state)
|
||||||
|
{
|
||||||
|
#if FAKE_DATA
|
||||||
|
if (Application.isEditor)
|
||||||
|
{
|
||||||
|
state = XrPlaneDetectionStateEXT.DONE_EXT;
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return XrGetPlaneDetectionStateEXT(planeDetector, ref state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the result of PlaneDetector.
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetPlaneDetectionsEXT">xrGetPlaneDetectionsEXT</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="planeDetector">The detector's state to be check.</param>
|
||||||
|
/// <param name="info">Use info to specify the data's space.</param>
|
||||||
|
/// <param name="locations">The output data.</param>
|
||||||
|
public XrResult GetPlaneDetections(IntPtr/*XrPlaneDetectorEXT*/ planeDetector, ref XrPlaneDetectorGetInfoEXT info, ref XrPlaneDetectorLocationsEXT locations)
|
||||||
|
{
|
||||||
|
#if FAKE_DATA
|
||||||
|
if (Application.isEditor)
|
||||||
|
{
|
||||||
|
locations.planeLocationCountOutput = 1;
|
||||||
|
if (locations.planeLocationCapacityInput == 0)
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
if (locations.planeLocationCapacityInput < 1 || locations.planeLocations == IntPtr.Zero)
|
||||||
|
return XrResult.XR_ERROR_SIZE_INSUFFICIENT;
|
||||||
|
|
||||||
|
locations.planeLocationCountOutput = 1;
|
||||||
|
XrPlaneDetectorLocationEXT location = new XrPlaneDetectorLocationEXT();
|
||||||
|
location.planeId = 1;
|
||||||
|
location.extents = new XrExtent2Df(1, 1);
|
||||||
|
location.locationFlags = XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_VALID_BIT;
|
||||||
|
location.semanticType = XrPlaneDetectorSemanticTypeEXT.FLOOR_EXT;
|
||||||
|
location.polygonBufferCount = 1;
|
||||||
|
location.pose = new XrPosef(XrQuaternionf.Identity, new XrVector3f(0, 1, -1)); // This plane will face the Z axis
|
||||||
|
location.orientation = XrPlaneDetectorOrientationEXT.VERTICAL_EXT;
|
||||||
|
|
||||||
|
Marshal.StructureToPtr(location, locations.planeLocations, false);
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return XrGetPlaneDetectionsEXT(planeDetector, ref info, ref locations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the vertex buffer of a plane.
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetPlanePolygonBufferEXT">GetPlanePolygonBuffer</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="planeDetector">The detector's state to be check.</param>
|
||||||
|
/// <param name="planeId">The target plane's planeId. Get it from <see cref="XrPlaneDetectorLocationEXT" />.</param>
|
||||||
|
/// <param name="polygonBufferIndex">The buffer index in the plane. Get it from <see cref="XrPlaneDetectorLocationEXT" />. Currently VIVE will only return 1 buffer for each plane.</param>
|
||||||
|
/// <param name="polygonBuffer">The output data.</param>
|
||||||
|
public XrResult GetPlanePolygonBuffer(IntPtr/*XrPlaneDetectorEXT*/ planeDetector, ulong planeId, uint polygonBufferIndex, ref XrPlaneDetectorPolygonBufferEXT polygonBuffer)
|
||||||
|
{
|
||||||
|
#if FAKE_DATA
|
||||||
|
if (Application.isEditor)
|
||||||
|
{
|
||||||
|
if (planeId != 1) return XrResult.XR_ERROR_NAME_INVALID;
|
||||||
|
if (polygonBufferIndex != 0) return XrResult.XR_ERROR_INDEX_OUT_OF_RANGE;
|
||||||
|
polygonBuffer.vertexCountOutput = 4;
|
||||||
|
if (polygonBuffer.vertexCapacityInput == 0)
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
if (polygonBuffer.vertexCapacityInput != 4 || polygonBuffer.vertices == IntPtr.Zero)
|
||||||
|
return XrResult.XR_ERROR_SIZE_INSUFFICIENT;
|
||||||
|
XrVector2f[] vertices = new XrVector2f[4];
|
||||||
|
// Make a plane's contour
|
||||||
|
vertices[0] = new XrVector2f(-0.5f, -0.5f);
|
||||||
|
vertices[1] = new XrVector2f( 0.5f, -0.5f);
|
||||||
|
vertices[2] = new XrVector2f( 0.5f, 0.5f);
|
||||||
|
vertices[3] = new XrVector2f(-0.5f, 0.5f);
|
||||||
|
|
||||||
|
MemoryTools.CopyToRawMemory(polygonBuffer.vertices, vertices);
|
||||||
|
|
||||||
|
return XrResult.XR_SUCCESS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return XrGetPlanePolygonBufferEXT(planeDetector, planeId, polygonBufferIndex, ref polygonBuffer);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region tools for user
|
||||||
|
/// <summary>
|
||||||
|
/// A helper function to generate XrPlaneDetectorBeginInfoEXT. VIVE didn't implement all possible features of this extension.
|
||||||
|
/// Hardcode some parameters here.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="BeginPlaneDetection"/>
|
||||||
|
public XrPlaneDetectorBeginInfoEXT MakeGetAllXrPlaneDetectorBeginInfoEXT()
|
||||||
|
{
|
||||||
|
XrPlaneDetectorBeginInfoEXT beginInfo = new XrPlaneDetectorBeginInfoEXT
|
||||||
|
{
|
||||||
|
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_BEGIN_INFO_EXT,
|
||||||
|
baseSpace = new XrSpace(GetCurrentAppSpace()), // Cannot depend on GetCurrentAppSpace...
|
||||||
|
//baseSpace = GetTrackingSpace(),
|
||||||
|
time = ViveInterceptors.Instance.GetPredictTime(),
|
||||||
|
orientationCount = 0, // Any orientation
|
||||||
|
orientations = IntPtr.Zero, // XrPlaneDetectorOrientationEXT[]
|
||||||
|
semanticTypeCount = 0, // Any semantic type
|
||||||
|
semanticTypes = IntPtr.Zero, // XrPlaneDetectorSemanticTypeEXT[]
|
||||||
|
maxPlanes = 10000, // Hopefully enough
|
||||||
|
minArea = 0.1f, // 10cm^2
|
||||||
|
boundingBoxPose = XrPosef.Identity,
|
||||||
|
boundingBoxExtent = new XrExtent3DfEXT(1000, 1000, 1000),
|
||||||
|
};
|
||||||
|
return beginInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time here is only used for info of GetPlaneDetections. Not the real predictTime of XrWaitFrame.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrTime GetPredictTime()
|
||||||
|
{
|
||||||
|
return ViveInterceptors.Instance.GetPredictTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// According to XRInputSubsystem's tracking origin mode, return the corresponding XrSpace.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrSpace GetTrackingSpace()
|
||||||
|
{
|
||||||
|
return GetCurrentAppSpace();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cbc636a69caaad0418c5e52e22103f2e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -20,6 +20,8 @@ using UnityEngine.XR.OpenXR.Input;
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.XR.OpenXR.Features;
|
using UnityEditor.XR.OpenXR.Features;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Globalization;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
|
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
|
||||||
@@ -34,7 +36,7 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
/// This <see cref="OpenXRInteractionFeature"/> enables the use of tracker interaction profiles in OpenXR. It enables XR_HTC_vive_xr_tracker_interaction in the underyling runtime.
|
/// This <see cref="OpenXRInteractionFeature"/> enables the use of tracker interaction profiles in OpenXR. It enables XR_HTC_vive_xr_tracker_interaction in the underyling runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
[OpenXRFeature(UiName = "VIVE XR Tracker",
|
[OpenXRFeature(UiName = "VIVE XR Tracker (Beta)",
|
||||||
BuildTargetGroups = new[] { BuildTargetGroup.Android },
|
BuildTargetGroups = new[] { BuildTargetGroup.Android },
|
||||||
Company = "HTC",
|
Company = "HTC",
|
||||||
Desc = "Support for enabling the vive xr tracker interaction profile. Will register the controller map for xr tracker if enabled.",
|
Desc = "Support for enabling the vive xr tracker interaction profile. Will register the controller map for xr tracker if enabled.",
|
||||||
@@ -72,16 +74,25 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
|
|
||||||
#region Tracker Product Name
|
#region Tracker Product Name
|
||||||
public const string kProductUltimateTracker = "VIVE Ultimate Tracker";
|
public const string kProductUltimateTracker = "VIVE Ultimate Tracker";
|
||||||
public const string kProductTrackingTag = "VIVE Tracking Tag";
|
|
||||||
public const string kProductUltimateTracker0 = "VIVE Ultimate Tracker 0";
|
public const string kProductUltimateTracker0 = "VIVE Ultimate Tracker 0";
|
||||||
public const string kProductUltimateTracker1 = "VIVE Ultimate Tracker 1";
|
public const string kProductUltimateTracker1 = "VIVE Ultimate Tracker 1";
|
||||||
public const string kProductUltimateTracker2 = "VIVE Ultimate Tracker 2";
|
public const string kProductUltimateTracker2 = "VIVE Ultimate Tracker 2";
|
||||||
public const string kProductUltimateTracker3 = "VIVE Ultimate Tracker 3";
|
public const string kProductUltimateTracker3 = "VIVE Ultimate Tracker 3";
|
||||||
public const string kProductUltimateTracker4 = "VIVE Ultimate Tracker 4";
|
public const string kProductUltimateTracker4 = "VIVE Ultimate Tracker 4";
|
||||||
|
const string kProductTrackingTag = "VIVE Tracking Tag";
|
||||||
private const string kProducts = "^(" + kProductUltimateTracker
|
private const string kProducts = "^(" + kProductUltimateTracker
|
||||||
+ ")|^(" + kProductUltimateTracker0 + ")|^(" + kProductUltimateTracker1 + ")|^(" + kProductUltimateTracker2 + ")|^(" + kProductUltimateTracker3 + ")|^(" + kProductUltimateTracker4
|
+ ")|^(" + kProductUltimateTracker0 + ")|^(" + kProductUltimateTracker1 + ")|^(" + kProductUltimateTracker2 + ")|^(" + kProductUltimateTracker3 + ")|^(" + kProductUltimateTracker4
|
||||||
+ ")|^(" + kProductTrackingTag + ")";
|
+ ")|^(" + kProductTrackingTag + ")";
|
||||||
private readonly string[] s_UltimateTrackerProduct = { kProductUltimateTracker0, kProductUltimateTracker1, kProductUltimateTracker2, kProductUltimateTracker3, kProductUltimateTracker4 };
|
private readonly string[] s_UltimateTrackerProduct = { kProductUltimateTracker0, kProductUltimateTracker1, kProductUltimateTracker2, kProductUltimateTracker3, kProductUltimateTracker4 };
|
||||||
|
private bool IsUltimateTracker(string product)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < s_UltimateTrackerProduct.Length; i++)
|
||||||
|
{
|
||||||
|
if (s_UltimateTrackerProduct[i].Equals(product))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tracker Action Map Name
|
#region Tracker Action Map Name
|
||||||
@@ -93,32 +104,11 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tracker Usage
|
#region Tracker Usage
|
||||||
const string kTrackerUsage0 = "Tracker 0";
|
const string kUltimateTrackerUsage0 = "Ultimate Tracker 0";
|
||||||
const string kTrackerUsage1 = "Tracker 1";
|
const string kUltimateTrackerUsage1 = "Ultimate Tracker 1";
|
||||||
const string kTrackerUsage2 = "Tracker 2";
|
const string kUltimateTrackerUsage2 = "Ultimate Tracker 2";
|
||||||
const string kTrackerUsage3 = "Tracker 3";
|
const string kUltimateTrackerUsage3 = "Ultimate Tracker 3";
|
||||||
const string kTrackerUsage4 = "Tracker 4";
|
const string kUltimateTrackerUsage4 = "Ultimate Tracker 4";
|
||||||
|
|
||||||
public const string kTrackerRoleUndefined = "Undefined";
|
|
||||||
public const string kTrackerRoleWaist = "Waist";
|
|
||||||
public const string kTrackerRoleChest = "Chest";
|
|
||||||
public const string kTrackerRoleLeftElbow = "Left Elbow";
|
|
||||||
public const string kTrackerRoleLeftWrist = "Left Wrist";
|
|
||||||
public const string kTrackerRoleLeftKnee = "Left Knee";
|
|
||||||
public const string kTrackerRoleLeftAnkle = "Left Ankle";
|
|
||||||
public const string kTrackerRoleLeftFoot = "Left Foot";
|
|
||||||
|
|
||||||
public const string kTrackerRoleRightElbow = "Right Elbow";
|
|
||||||
public const string kTrackerRoleRightWrist = "Right Wrist";
|
|
||||||
public const string kTrackerRoleRightKnee = "Right Knee";
|
|
||||||
public const string kTrackerRoleRightAnkle = "Right Ankle";
|
|
||||||
public const string kTrackerRoleRightFoot = "Right Foot";
|
|
||||||
private readonly List<string> s_TrackerRoleName = new List<string>()
|
|
||||||
{
|
|
||||||
kTrackerRoleWaist, kTrackerRoleChest,
|
|
||||||
kTrackerRoleLeftElbow, kTrackerRoleLeftWrist, kTrackerRoleLeftKnee, kTrackerRoleLeftAnkle, kTrackerRoleLeftFoot,
|
|
||||||
kTrackerRoleRightElbow, kTrackerRoleRightWrist, kTrackerRoleRightKnee, kTrackerRoleRightAnkle, kTrackerRoleRightFoot,
|
|
||||||
};
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tracker User Path
|
#region Tracker User Path
|
||||||
@@ -135,19 +125,13 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
public const string kUltimateTrackerSN2 = "VIVE_Ultimate_Tracker_2";
|
public const string kUltimateTrackerSN2 = "VIVE_Ultimate_Tracker_2";
|
||||||
public const string kUltimateTrackerSN3 = "VIVE_Ultimate_Tracker_3";
|
public const string kUltimateTrackerSN3 = "VIVE_Ultimate_Tracker_3";
|
||||||
public const string kUltimateTrackerSN4 = "VIVE_Ultimate_Tracker_4";
|
public const string kUltimateTrackerSN4 = "VIVE_Ultimate_Tracker_4";
|
||||||
public const string kTrackingTagSN0 = "VIVE_Tracking_Tag_0";
|
const string kTrackingTagSN0 = "VIVE_Tracking_Tag_0";
|
||||||
public const string kTrackingTagSN1 = "VIVE_Tracking_Tag_1";
|
const string kTrackingTagSN1 = "VIVE_Tracking_Tag_1";
|
||||||
public const string kTrackingTagSN2 = "VIVE_Tracking_Tag_2";
|
const string kTrackingTagSN2 = "VIVE_Tracking_Tag_2";
|
||||||
public const string kTrackingTagSN3 = "VIVE_Tracking_Tag_3";
|
const string kTrackingTagSN3 = "VIVE_Tracking_Tag_3";
|
||||||
public const string kTrackingTagSN4 = "VIVE_Tracking_Tag_4";
|
const string kTrackingTagSN4 = "VIVE_Tracking_Tag_4";
|
||||||
public const string k6DoFTrackerSN0 = "VIVE_6DoF_Tracker_a_0";
|
const string k6DoFTrackerSN0 = "VIVE_6DoF_Tracker_a_0";
|
||||||
public const string k6DoFTrackerSN1 = "VIVE_6DoF_Tracker_a_1";
|
const string k6DoFTrackerSN1 = "VIVE_6DoF_Tracker_a_1";
|
||||||
private readonly List<string> s_TrackerSerialNumber = new List<string>()
|
|
||||||
{
|
|
||||||
kUltimateTrackerSN0, kUltimateTrackerSN1, kUltimateTrackerSN2, kUltimateTrackerSN3, kUltimateTrackerSN4,
|
|
||||||
kTrackingTagSN0, kTrackingTagSN1, kTrackingTagSN2, kTrackingTagSN3, kTrackingTagSN4,
|
|
||||||
k6DoFTrackerSN0, k6DoFTrackerSN1
|
|
||||||
};
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tracker Product Maps
|
#region Tracker Product Maps
|
||||||
@@ -160,11 +144,11 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
};
|
};
|
||||||
/// <summary> Mapping from product to tracker usage. </summary>
|
/// <summary> Mapping from product to tracker usage. </summary>
|
||||||
private static Dictionary<string, string> m_UltimateTrackerUsageMap = new Dictionary<string, string>() {
|
private static Dictionary<string, string> m_UltimateTrackerUsageMap = new Dictionary<string, string>() {
|
||||||
{ kProductUltimateTracker0, kTrackerUsage0 },
|
{ kProductUltimateTracker0, kUltimateTrackerUsage0 },
|
||||||
{ kProductUltimateTracker1, kTrackerUsage1 },
|
{ kProductUltimateTracker1, kUltimateTrackerUsage1 },
|
||||||
{ kProductUltimateTracker2, kTrackerUsage2 },
|
{ kProductUltimateTracker2, kUltimateTrackerUsage2 },
|
||||||
{ kProductUltimateTracker3, kTrackerUsage3 },
|
{ kProductUltimateTracker3, kUltimateTrackerUsage3 },
|
||||||
{ kProductUltimateTracker4, kTrackerUsage4 },
|
{ kProductUltimateTracker4, kUltimateTrackerUsage4 },
|
||||||
};
|
};
|
||||||
/// <summary> Mapping from product to user path. </summary>
|
/// <summary> Mapping from product to user path. </summary>
|
||||||
private Dictionary<string, string> m_UltimateTrackerPathMap = new Dictionary<string, string>() {
|
private Dictionary<string, string> m_UltimateTrackerPathMap = new Dictionary<string, string>() {
|
||||||
@@ -185,13 +169,11 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
[Preserve, InputControlLayout(displayName = "VIVE XR Tracker (OpenXR)", commonUsages = new[] {
|
[Preserve, InputControlLayout(displayName = "VIVE XR Tracker (OpenXR)", commonUsages = new[] {
|
||||||
kTrackerUsage0, kTrackerUsage1, kTrackerUsage2, kTrackerUsage3, kTrackerUsage4,
|
kUltimateTrackerUsage0, kUltimateTrackerUsage1, kUltimateTrackerUsage2, kUltimateTrackerUsage3, kUltimateTrackerUsage4,
|
||||||
//kTrackerRoleWaist, kTrackerRoleChest,
|
|
||||||
//kTrackerRoleLeftElbow, kTrackerRoleLeftWrist, kTrackerRoleLeftKnee, kTrackerRoleLeftAnkle, kTrackerRoleLeftFoot,
|
|
||||||
//kTrackerRoleRightElbow, kTrackerRoleRightWrist, kTrackerRoleRightKnee, kTrackerRoleRightAnkle, kTrackerRoleRightFoot,
|
|
||||||
}, isGenericTypeOfDevice = true)]
|
}, isGenericTypeOfDevice = true)]
|
||||||
public class XrTrackerDevice : OpenXRDevice, IInputUpdateCallbackReceiver
|
public class XrTrackerDevice : OpenXRDevice//, IInputUpdateCallbackReceiver
|
||||||
{
|
{
|
||||||
|
#region Log
|
||||||
const string LOG_TAG = "VIVE.OpenXR.Tracker.ViveXRTracker.XrTrackerDevice ";
|
const string LOG_TAG = "VIVE.OpenXR.Tracker.ViveXRTracker.XrTrackerDevice ";
|
||||||
StringBuilder m_sb = null;
|
StringBuilder m_sb = null;
|
||||||
StringBuilder sb {
|
StringBuilder sb {
|
||||||
@@ -201,8 +183,9 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void DEBUG(StringBuilder msg) { Debug.Log(msg); }
|
void DEBUG(StringBuilder msg) { Debug.Log(msg); }
|
||||||
void WARNING(StringBuilder msg) { Debug.LogWarning(msg); }
|
#endregion
|
||||||
|
|
||||||
|
#region Interactions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="PoseControl"/> that represents the <see cref="entityPose"/> OpenXR binding. The entity pose represents the location of the tracker.
|
/// A <see cref="PoseControl"/> that represents the <see cref="entityPose"/> OpenXR binding. The entity pose represents the location of the tracker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -232,10 +215,12 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Preserve, InputControl(offset = 20, alias = "gripOrientation")]
|
[Preserve, InputControl(offset = 20, alias = "gripOrientation")]
|
||||||
public QuaternionControl deviceRotation { get; private set; }
|
public QuaternionControl deviceRotation { get; private set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#if DEBUG_CODE
|
||||||
// Unity action binding path: <ViveXRTracker>{Tracker 0}/isTracked
|
// Unity action binding path: <ViveXRTracker>{Tracker 0}/isTracked
|
||||||
private bool UpdateInputDeviceInRuntime = true;
|
|
||||||
private InputAction inputActionIsTracked = null;
|
private InputAction inputActionIsTracked = null;
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal call used to assign controls to the the correct element.
|
/// Internal call used to assign controls to the the correct element.
|
||||||
@@ -244,50 +229,52 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
{
|
{
|
||||||
base.FinishSetup();
|
base.FinishSetup();
|
||||||
|
|
||||||
if (!m_UltimateTrackerUsageMap.ContainsKey(description.product))
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append("FinishSetup() Not support the product " + description.product); WARNING(sb);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
devicePose = GetChildControl<PoseControl>("devicePose");
|
devicePose = GetChildControl<PoseControl>("devicePose");
|
||||||
isTracked = GetChildControl<ButtonControl>("isTracked");
|
isTracked = GetChildControl<ButtonControl>("isTracked");
|
||||||
trackingState = GetChildControl<IntegerControl>("trackingState");
|
trackingState = GetChildControl<IntegerControl>("trackingState");
|
||||||
devicePosition = GetChildControl<Vector3Control>("devicePosition");
|
devicePosition = GetChildControl<Vector3Control>("devicePosition");
|
||||||
deviceRotation = GetChildControl<QuaternionControl>("deviceRotation");
|
deviceRotation = GetChildControl<QuaternionControl>("deviceRotation");
|
||||||
|
|
||||||
/// After RegisterActionMapsWithRuntime finished, each XrTrackerDevice will have a product name (e.g. kProductUltimateTracker0)
|
|
||||||
/// We have to assign the XrTrackerDeivce to a commonUsage (e.g. kTrackerUsage0)
|
|
||||||
///
|
|
||||||
/// Since we already established the m_UltimateTrackerUsageMap (kProductUltimateTracker0, kTrackerUsage0),
|
|
||||||
/// we can simply call SetDeviceUsage(m_UltimateTrackerUsageMap[description.product])
|
|
||||||
/// to set assign the XrTrackerDevice with product name kProductUltimateTracker0 to the commonUsage kTrackerUsage0.
|
|
||||||
InputSystem.SetDeviceUsage(this, m_UltimateTrackerUsageMap[description.product]);
|
|
||||||
|
|
||||||
if (inputActionIsTracked == null)
|
|
||||||
{
|
|
||||||
//string actionBindingIsTracked = "<XRController>{LeftHand}/isTracked";
|
|
||||||
string actionBindingIsTracked = "<" + kLayoutName + ">{" + m_UltimateTrackerUsageMap[description.product] + "}/isTracked";
|
|
||||||
sb.Clear().Append(LOG_TAG).Append("FinishSetup() ").Append(m_UltimateTrackerUsageMap[description.product]).Append(", acion binding of IsTracked: ").Append(actionBindingIsTracked);
|
|
||||||
DEBUG(sb);
|
|
||||||
|
|
||||||
inputActionIsTracked = new InputAction(
|
|
||||||
type: InputActionType.Value,
|
|
||||||
binding: actionBindingIsTracked);
|
|
||||||
|
|
||||||
inputActionIsTracked.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Clear().Append(LOG_TAG)
|
sb.Clear().Append(LOG_TAG)
|
||||||
.Append("FinishSetup() device interfaceName: ").Append(description.interfaceName)
|
.Append("FinishSetup() device interfaceName: ").Append(description.interfaceName)
|
||||||
.Append(", deviceClass: ").Append(description.deviceClass)
|
.Append(", deviceClass: ").Append(description.deviceClass)
|
||||||
.Append(", product: ").Append(description.product)
|
.Append(", product: ").Append(description.product)
|
||||||
.Append(", serial: ").Append(description.serial)
|
.Append(", serial: ").Append(description.serial)
|
||||||
.Append(", usage: ").Append(m_UltimateTrackerUsageMap[description.product])
|
|
||||||
.Append(", current profile: ").Append(profile);
|
.Append(", current profile: ").Append(profile);
|
||||||
DEBUG(sb);
|
DEBUG(sb);
|
||||||
|
|
||||||
|
if (m_UltimateTrackerUsageMap.ContainsKey(description.product))
|
||||||
|
{
|
||||||
|
/// After RegisterActionMapsWithRuntime finished, each XrTrackerDevice will have a product name (e.g. kProductUltimateTracker0)
|
||||||
|
/// We have to assign the XrTrackerDeivce to a commonUsage (e.g. kUltimateTrackerUsage0)
|
||||||
|
///
|
||||||
|
/// Since we already established the m_UltimateTrackerUsageMap (kProductUltimateTracker0, kUltimateTrackerUsage0),
|
||||||
|
/// we can simply call SetDeviceUsage(m_UltimateTrackerUsageMap[description.product])
|
||||||
|
/// to set assign the XrTrackerDevice with product name kProductUltimateTracker0 to the commonUsage kUltimateTrackerUsage0.
|
||||||
|
InputSystem.SetDeviceUsage(this, m_UltimateTrackerUsageMap[description.product]);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("FinishSetup() usage: ").Append(m_UltimateTrackerUsageMap[description.product]); DEBUG(sb);
|
||||||
|
#if DEBUG_CODE
|
||||||
|
/// We cannot update the ActionMap outside the RegisterActionMapsWithRuntime method so ignore this code.
|
||||||
|
if (inputActionIsTracked == null)
|
||||||
|
{
|
||||||
|
//string actionBindingIsTracked = "<XRController>{LeftHand}/isTracked";
|
||||||
|
string actionBindingIsTracked = "<" + kLayoutName + ">{" + m_UltimateTrackerUsageMap[description.product] + "}/isTracked";
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("FinishSetup() ").Append(m_UltimateTrackerUsageMap[description.product]).Append(", action binding of IsTracked: ").Append(actionBindingIsTracked);
|
||||||
|
DEBUG(sb);
|
||||||
|
|
||||||
|
inputActionIsTracked = new InputAction(
|
||||||
|
type: InputActionType.Value,
|
||||||
|
binding: actionBindingIsTracked);
|
||||||
|
|
||||||
|
inputActionIsTracked.Enable();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG_CODE
|
||||||
|
/// We cannot update the ActionMap outside the RegisterActionMapsWithRuntime method so ignore this code.
|
||||||
|
private bool UpdateInputDeviceInRuntime = true;
|
||||||
private bool bRoleUpdated = false;
|
private bool bRoleUpdated = false;
|
||||||
public void OnUpdate()
|
public void OnUpdate()
|
||||||
{
|
{
|
||||||
@@ -298,14 +285,14 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
/// Updates the Usage (tracker role) when IsTracked becomes true.
|
/// Updates the Usage (tracker role) when IsTracked becomes true.
|
||||||
if (inputActionIsTracked.ReadValue<float>() > 0 && !bRoleUpdated)
|
if (inputActionIsTracked.ReadValue<float>() > 0 && !bRoleUpdated)
|
||||||
{
|
{
|
||||||
sb.Clear().Append(LOG_TAG)
|
sb.Clear().Append(LOG_TAG).Append("OnUpdate() Update the InputDevice with product: ").Append(description.product); DEBUG(sb);
|
||||||
.Append("OnUpdate() Update the InputDevice with product: ").Append(description.product); DEBUG(sb);
|
|
||||||
|
|
||||||
//m_Instance.UpdateInputDevice(description.product, entityPose);
|
if (m_UltimateTrackerUsageMap.ContainsKey(description.product)) { m_Instance.UpdateInputDeviceUltimateTracker(description.product); }
|
||||||
|
|
||||||
bRoleUpdated = true;
|
bRoleUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The interaction profile string used to reference the wrist tracker interaction input device.</summary>
|
/// <summary>The interaction profile string used to reference the wrist tracker interaction input device.</summary>
|
||||||
@@ -370,7 +357,6 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
m_XrSessionCreated = false;
|
m_XrSessionCreated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
// "<" + kLayoutName + ">{" + m_UltimateTrackerUsageMap[description.product] + "}/isTracked"
|
// "<" + kLayoutName + ">{" + m_UltimateTrackerUsageMap[description.product] + "}/isTracked"
|
||||||
private const string kLayoutName = "ViveXRTracker";
|
private const string kLayoutName = "ViveXRTracker";
|
||||||
@@ -396,12 +382,11 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
sb.Clear().Append(LOG_TAG).Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb);
|
sb.Clear().Append(LOG_TAG).Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb);
|
||||||
InputSystem.RemoveLayout(kLayoutName);
|
InputSystem.RemoveLayout(kLayoutName);
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region OpenXR function delegates
|
#region OpenXR function delegates
|
||||||
/// xrGetInstanceProcAddr
|
/// xrGetInstanceProcAddr
|
||||||
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
||||||
/// xrEnumeratePathsForInteractionProfileHTC
|
|
||||||
VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate xrEnumeratePathsForInteractionProfileHTC = null;
|
|
||||||
/// xrGetInputSourceLocalizedName
|
/// xrGetInputSourceLocalizedName
|
||||||
static OpenXRHelper.xrGetInputSourceLocalizedNameDelegate xrGetInputSourceLocalizedName = null;
|
static OpenXRHelper.xrGetInputSourceLocalizedNameDelegate xrGetInputSourceLocalizedName = null;
|
||||||
/// xrEnumerateInstanceExtensionProperties
|
/// xrEnumerateInstanceExtensionProperties
|
||||||
@@ -446,28 +431,6 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
ERROR(sb);
|
ERROR(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// xrEnumeratePathsForInteractionProfileHTC
|
|
||||||
if (XrGetInstanceProcAddr(xrInstance, "xrEnumeratePathsForInteractionProfileHTC", out funcPtr) == XrResult.XR_SUCCESS)
|
|
||||||
{
|
|
||||||
if (funcPtr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append("Get function pointer of xrEnumeratePathsForInteractionProfileHTC."); DEBUG(sb);
|
|
||||||
xrEnumeratePathsForInteractionProfileHTC = Marshal.GetDelegateForFunctionPointer(
|
|
||||||
funcPtr,
|
|
||||||
typeof(VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate)) as VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC.");
|
|
||||||
ERROR(sb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC");
|
|
||||||
ERROR(sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// xrGetInputSourceLocalizedName
|
/// xrGetInputSourceLocalizedName
|
||||||
if (XrGetInstanceProcAddr(xrInstance, "xrGetInputSourceLocalizedName", out funcPtr) == XrResult.XR_SUCCESS)
|
if (XrGetInstanceProcAddr(xrInstance, "xrGetInputSourceLocalizedName", out funcPtr) == XrResult.XR_SUCCESS)
|
||||||
{
|
{
|
||||||
@@ -492,287 +455,7 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/*private List<T> CreateList<T>(UInt32 count, T initialValue)
|
|
||||||
{
|
|
||||||
List<T> list = new List<T>();
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
list.Add(initialValue);
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
XrPathsForInteractionProfileEnumerateInfoHTC enumerateInfo;
|
|
||||||
private bool EnumeratePath()
|
|
||||||
{
|
|
||||||
if (!m_XrInstanceCreated) { return false; }
|
|
||||||
|
|
||||||
string func = "EnumeratePath() ";
|
|
||||||
|
|
||||||
if (xrEnumeratePathsForInteractionProfileHTC == null)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC"); WARNING(sb);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Get user path count of /interaction_profiles/htc/vive_xr_tracker profile.
|
|
||||||
UInt32 trackerCount = 0;
|
|
||||||
enumerateInfo.type = XrStructureType.XR_TYPE_UNKNOWN;
|
|
||||||
enumerateInfo.next = IntPtr.Zero;
|
|
||||||
enumerateInfo.interactionProfile = StringToPath(profile); // /interaction_profiles/htc/vive_xr_tracker
|
|
||||||
enumerateInfo.userPath = OpenXRHelper.XR_NULL_PATH;
|
|
||||||
|
|
||||||
XrResult result = xrEnumeratePathsForInteractionProfileHTC(m_XrInstance, ref enumerateInfo, 0, ref trackerCount, null);
|
|
||||||
if (result != XrResult.XR_SUCCESS)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("Retrieves trackerCount failed."); ERROR(sb);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("Get profile ").Append(profile).Append(" user path count: ").Append(trackerCount); DEBUG(sb);
|
|
||||||
|
|
||||||
if (trackerCount > 0)
|
|
||||||
{
|
|
||||||
// 2. Get user paths of /interaction_profiles/htc/vive_xr_tracker profile.
|
|
||||||
List<XrPath> trackerList = CreateList<XrPath>(trackerCount, OpenXRHelper.XR_NULL_PATH);
|
|
||||||
XrPath[] trackers = trackerList.ToArray();
|
|
||||||
result = xrEnumeratePathsForInteractionProfileHTC(
|
|
||||||
m_XrInstance,
|
|
||||||
ref enumerateInfo,
|
|
||||||
pathCapacityInput: (UInt32)(trackers.Length & 0x7FFFFFFF),
|
|
||||||
pathCountOutput: ref trackerCount,
|
|
||||||
paths: trackers);
|
|
||||||
if (result != XrResult.XR_SUCCESS)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("Retrieves trackers failed."); ERROR(sb);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateMapsUltimateTracker(trackers, (Int32)(trackerCount & 0x7FFFFFFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
#region Usage Map of Ultimate Tracker
|
|
||||||
/*private void UpdateMapsUltimateTracker(XrPath[] paths, int pathLength)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < pathLength && i < s_UltimateTrackerProduct.Length; i++)
|
|
||||||
{
|
|
||||||
UpdateMapsUltimateTracker(paths[i], s_UltimateTrackerProduct[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void UpdateMapsUltimateTracker(string[] paths, string[] products)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < paths.Length; i++)
|
|
||||||
{
|
|
||||||
XrPath path = StringToPath(paths[i]);
|
|
||||||
UpdateMapsUltimateTracker(path, products[i]);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
private bool UpdateMapsUltimateTracker(XrPath path, string product)
|
|
||||||
{
|
|
||||||
string func = "UpdateMapsUltimateTracker() ";
|
|
||||||
string s_path = PathToString(path);
|
|
||||||
|
|
||||||
// -------------------- Tracker Role --------------------
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("path: ").Append(s_path)
|
|
||||||
.Append(", product: ").Append(product).Append(" GetInputSourceName(TrackerRole)"); DEBUG(sb);
|
|
||||||
|
|
||||||
if (GetInputSourceName(path, InputSourceType.TrackerRole, out string role) != XrResult.XR_SUCCESS)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("GetInputSourceName of TrackerRole failed."); ERROR(sb);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append(s_path).Append(" has role: ").Append(role); DEBUG(sb);
|
|
||||||
if (!s_TrackerRoleName.Contains(role))
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("role ").Append(role).Append(" is NOT definied."); ERROR(sb);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// For sample:
|
|
||||||
/// A user path (e.g. "/user/tracker_htc/index0") has the "sourceName" kTrackerRoleLeftWrist ("Left Wrist") which means
|
|
||||||
/// the corresponding product (e.g. kProductUltimateTracker0 = "VIVE Ultimate Tracker 0") has the "sourceName" kTrackerRoleLeftWrist.
|
|
||||||
/// So we have to set the usage of the product name kProductUltimateTracker0 to kTrackerRoleLeftWrist.
|
|
||||||
if (!m_UltimateTrackerUsageMap.ContainsKey(product))
|
|
||||||
m_UltimateTrackerUsageMap.Add(product, role);
|
|
||||||
else
|
|
||||||
m_UltimateTrackerUsageMap[product] = role;
|
|
||||||
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("Sets product ").Append(product).Append(" with usage ").Append(role); DEBUG(sb);
|
|
||||||
|
|
||||||
// -------------------- Tracker Serial Number --------------------
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("path: ").Append(s_path)
|
|
||||||
.Append(", product: ").Append(product).Append(" GetInputSourceName(SerialNumber)"); DEBUG(sb);
|
|
||||||
|
|
||||||
if (GetInputSourceName(path, InputSourceType.SerialNumber, out string sn) != XrResult.XR_SUCCESS)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("GetInputSourceName of SerialNumber failed."); ERROR(sb);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func).Append(s_path).Append(" has serial number: ").Append(sn); DEBUG(sb);
|
|
||||||
if (!s_TrackerSerialNumber.Contains(sn))
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("serial number ").Append(sn).Append(" is NOT definied."); ERROR(sb);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// For sample:
|
|
||||||
/// A user path (e.g. "/user/tracker_htc/index0") has the "sourceName" kUltimateTrackerSN0 ("VIVE_Ultimate_Tracker_0") which means
|
|
||||||
/// the corresponding product (e.g. kProductUltimateTracker0 = "VIVE Ultimate Tracker 0") has the "sourceName" kUltimateTrackerSN0.
|
|
||||||
/// So we have to set the serial of the product name kProductUltimateTracker0 to kUltimateTrackerSN0.
|
|
||||||
if (!m_UltimateTrackerSerialMap.ContainsKey(product))
|
|
||||||
m_UltimateTrackerSerialMap.Add(product, sn);
|
|
||||||
else
|
|
||||||
m_UltimateTrackerSerialMap[product] = sn;
|
|
||||||
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("Sets product ").Append(product).Append(" with serial number ").Append(sn); DEBUG(sb);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public enum InputSourceType : UInt64
|
|
||||||
{
|
|
||||||
SerialNumber = XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_SERIAL_NUMBER_BIT_HTC,
|
|
||||||
TrackerRole = XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT,
|
|
||||||
}
|
|
||||||
public static XrResult GetInputSourceName(XrPath path, InputSourceType sourceType, out string sourceName)
|
|
||||||
{
|
|
||||||
string func = "GetInputSourceName() ";
|
|
||||||
|
|
||||||
sourceName = "";
|
|
||||||
if (!m_XrSessionCreated || xrGetInputSourceLocalizedName == null) { return XrResult.XR_ERROR_VALIDATION_FAILURE; }
|
|
||||||
|
|
||||||
string userPath = PathToString(path);
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("userPath: ").Append(userPath).Append(", flag: ").Append((UInt64)sourceType); DEBUG(sb);
|
|
||||||
|
|
||||||
XrInputSourceLocalizedNameGetInfo nameInfo = new XrInputSourceLocalizedNameGetInfo(
|
|
||||||
XrStructureType.XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO,
|
|
||||||
IntPtr.Zero, path, (XrInputSourceLocalizedNameFlags)sourceType);
|
|
||||||
UInt32 nameSizeIn = 0;
|
|
||||||
UInt32 nameSizeOut = 0;
|
|
||||||
char[] buffer = new char[0];
|
|
||||||
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("1.xrGetInputSourceLocalizedName(").Append(userPath).Append(")")
|
|
||||||
.Append(", flag: ").Append((UInt64)sourceType)
|
|
||||||
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
|
||||||
.Append(", bufferCountOutput: ").Append(nameSizeOut);
|
|
||||||
DEBUG(sb);
|
|
||||||
XrResult result = xrGetInputSourceLocalizedName(m_XrSession, ref nameInfo, nameSizeIn, ref nameSizeOut, buffer);
|
|
||||||
if (result == XrResult.XR_SUCCESS)
|
|
||||||
{
|
|
||||||
if (nameSizeOut < 1)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("xrGetInputSourceLocalizedName(").Append(userPath).Append(")")
|
|
||||||
.Append(", flag: ").Append((UInt64)sourceType)
|
|
||||||
.Append("bufferCountOutput size is invalid!");
|
|
||||||
ERROR(sb);
|
|
||||||
return XrResult.XR_ERROR_VALIDATION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
nameSizeIn = nameSizeOut;
|
|
||||||
buffer = new char[nameSizeIn];
|
|
||||||
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("2.xrGetInputSourceLocalizedName(").Append(userPath).Append(")")
|
|
||||||
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
|
||||||
.Append(", bufferCountOutput: ").Append(nameSizeOut);
|
|
||||||
DEBUG(sb);
|
|
||||||
result = xrGetInputSourceLocalizedName(m_XrSession, ref nameInfo, nameSizeIn, ref nameSizeOut, buffer);
|
|
||||||
if (result == XrResult.XR_SUCCESS)
|
|
||||||
{
|
|
||||||
sourceName = new string(buffer).TrimEnd('\0');
|
|
||||||
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("2.xrGetInputSourceLocalizedName(").Append(userPath).Append(")")
|
|
||||||
.Append(", flag: ").Append((UInt64)sourceType)
|
|
||||||
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
|
||||||
.Append(", bufferCountOutput: ").Append(nameSizeOut)
|
|
||||||
.Append(", sourceName: ").Append(sourceName);
|
|
||||||
DEBUG(sb);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("2.xrGetInputSourceLocalizedName(").Append(userPath).Append(")")
|
|
||||||
.Append(", flag: ").Append((UInt64)sourceType)
|
|
||||||
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
|
||||||
.Append(", bufferCountOutput: ").Append(nameSizeOut)
|
|
||||||
.Append(" result: ").Append(result);
|
|
||||||
ERROR(sb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("1.xrGetInputSourceLocalizedName(").Append(userPath).Append(")")
|
|
||||||
.Append(", flag: ").Append((UInt64)sourceType)
|
|
||||||
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
|
||||||
.Append(", bufferCountOutput: ").Append(nameSizeOut)
|
|
||||||
.Append(" result: ").Append(result);
|
|
||||||
ERROR(sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
/*private void UpdateInputDevice(string product, string actionPath)
|
|
||||||
{
|
|
||||||
string func = "UpdateInputDevice() ";
|
|
||||||
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("product: ").Append(product)
|
|
||||||
.Append(", usage: ").Append(m_UltimateTrackerUsageMap[product])
|
|
||||||
.Append(" with user path: ").Append(m_UltimateTrackerPathMap[product]);
|
|
||||||
DEBUG(sb);
|
|
||||||
|
|
||||||
XrPath path = StringToPath(m_UltimateTrackerPathMap[product]);
|
|
||||||
if (UpdateMapsUltimateTracker(path, product))
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("Maps of ").Append(product)
|
|
||||||
.Append(" with user path ").Append(path).Append(" are updated.");
|
|
||||||
DEBUG(sb);
|
|
||||||
|
|
||||||
bool foundProduct = false;
|
|
||||||
for (int i = 0; i < InputSystem.devices.Count; i++)
|
|
||||||
{
|
|
||||||
if (InputSystem.devices[i] is XrTrackerDevice &&
|
|
||||||
InputSystem.devices[i].description.product.Equals(product))
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("Removes the XrTrackerDevice product ").Append(product);
|
|
||||||
DEBUG(sb);
|
|
||||||
|
|
||||||
InputSystem.RemoveDevice(InputSystem.devices[i]);
|
|
||||||
foundProduct = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (foundProduct)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
|
||||||
.Append("Adds a XrTrackerDevice product ").Append(product);
|
|
||||||
RegisterActionMap(product);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Available Bindings
|
// Available Bindings
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -780,28 +463,28 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string entityPose = "/input/entity_htc/pose";
|
public const string entityPose = "/input/entity_htc/pose";
|
||||||
|
|
||||||
private void RegisterActionMap(string product)
|
private void RegisterActionMap(string in_name, string product, string in_sn, string in_path)
|
||||||
{
|
{
|
||||||
sb.Clear().Append(LOG_TAG).Append("RegisterActionMapsWithRuntime() Added ActionMapConfig of ").Append(m_UltimateTrackerPathMap[product])
|
sb.Clear().Append(LOG_TAG).Append("RegisterActionMap() Added ActionMapConfig of ").Append(in_path)
|
||||||
.Append(", localizedName = ").Append(product)
|
.Append(", localizedName = ").Append(product)
|
||||||
.Append(" { name = ").Append(m_UltimateTrackerActionMap[product])
|
.Append(" { name = ").Append(in_name)
|
||||||
.Append(", desiredInteractionProfile = ").Append(profile)
|
.Append(", desiredInteractionProfile = ").Append(profile)
|
||||||
.Append(", serialNumber = ").Append(m_UltimateTrackerSerialMap[product]);
|
.Append(", serialNumber = ").Append(in_sn);
|
||||||
DEBUG(sb);
|
DEBUG(sb);
|
||||||
|
|
||||||
ActionMapConfig actionMap = new ActionMapConfig()
|
ActionMapConfig actionMap = new ActionMapConfig()
|
||||||
{
|
{
|
||||||
name = m_UltimateTrackerActionMap[product],
|
name = in_name,
|
||||||
localizedName = product,
|
localizedName = product,
|
||||||
desiredInteractionProfile = profile,
|
desiredInteractionProfile = profile,
|
||||||
manufacturer = "HTC",
|
manufacturer = "HTC",
|
||||||
serialNumber = m_UltimateTrackerSerialMap[product],
|
serialNumber = in_sn,
|
||||||
deviceInfos = new List<DeviceConfig>()
|
deviceInfos = new List<DeviceConfig>()
|
||||||
{
|
{
|
||||||
new DeviceConfig()
|
new DeviceConfig()
|
||||||
{
|
{
|
||||||
characteristics = InputDeviceCharacteristics.TrackedDevice,
|
characteristics = InputDeviceCharacteristics.TrackedDevice,
|
||||||
userPath = m_UltimateTrackerPathMap[product]
|
userPath = in_path
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = new List<ActionConfig>()
|
actions = new List<ActionConfig>()
|
||||||
@@ -837,14 +520,311 @@ namespace VIVE.OpenXR.Tracker
|
|||||||
{
|
{
|
||||||
if (OpenXRHelper.IsExtensionSupported(xrEnumerateInstanceExtensionProperties, kOpenxrExtensionString) != XrResult.XR_SUCCESS)
|
if (OpenXRHelper.IsExtensionSupported(xrEnumerateInstanceExtensionProperties, kOpenxrExtensionString) != XrResult.XR_SUCCESS)
|
||||||
{
|
{
|
||||||
sb.Clear().Append(LOG_TAG).Append("RegisterActionMapsWithRuntime() ").Append(kOpenxrExtensionString).Append(" is NOT supported."); DEBUG(sb);
|
sb.Clear().Append(LOG_TAG).Append("RegisterActionMapsWithRuntime() ").Append(kOpenxrExtensionString).Append(" is NOT supported."); ERROR(sb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates m_UltimateTrackerPathMap.
|
||||||
|
if (!EnumeratePath())
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append("RegisterActionMapsWithRuntime() EnumeratePath failed.");
|
||||||
|
ERROR(sb);
|
||||||
|
}
|
||||||
|
|
||||||
for (int userIndex = 0; userIndex < s_UltimateTrackerProduct.Length; userIndex++)
|
for (int userIndex = 0; userIndex < s_UltimateTrackerProduct.Length; userIndex++)
|
||||||
{
|
{
|
||||||
RegisterActionMap(s_UltimateTrackerProduct[userIndex]);
|
string product = s_UltimateTrackerProduct[userIndex];
|
||||||
|
RegisterActionMap(
|
||||||
|
product: product,
|
||||||
|
in_name: m_UltimateTrackerActionMap[product],
|
||||||
|
in_sn: m_UltimateTrackerSerialMap[product],
|
||||||
|
in_path: m_UltimateTrackerPathMap[product]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XrPathsForInteractionProfileEnumerateInfoHTC enumerateInfo;
|
||||||
|
private bool EnumeratePath()
|
||||||
|
{
|
||||||
|
if (!m_XrInstanceCreated) { return false; }
|
||||||
|
|
||||||
|
string func = "EnumeratePath() ";
|
||||||
|
|
||||||
|
// 1. Get user path count of /interaction_profiles/htc/vive_xr_tracker profile.
|
||||||
|
UInt32 trackerCount = 0;
|
||||||
|
enumerateInfo.type = XrStructureType.XR_TYPE_UNKNOWN;
|
||||||
|
enumerateInfo.next = IntPtr.Zero;
|
||||||
|
enumerateInfo.interactionProfile = StringToPath(profile); // /interaction_profiles/htc/vive_xr_tracker
|
||||||
|
enumerateInfo.userPath = OpenXRHelper.XR_NULL_PATH;
|
||||||
|
|
||||||
|
XrResult result = XR_HTC_path_enumeration.xrEnumeratePathsForInteractionProfileHTC(ref enumerateInfo, 0, ref trackerCount, null);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("xrEnumeratePathsForInteractionProfileHTC result: ").Append(result)
|
||||||
|
.Append(", profile: ").Append(profile)
|
||||||
|
.Append(", trackerCount: ").Append(trackerCount);
|
||||||
|
DEBUG(sb);
|
||||||
|
if (result != XrResult.XR_SUCCESS) { return false; }
|
||||||
|
|
||||||
|
if (trackerCount > 0)
|
||||||
|
{
|
||||||
|
// 2. Get user paths of /interaction_profiles/htc/vive_xr_tracker profile.
|
||||||
|
XrPath[] trackerPaths = new XrPath[trackerCount];
|
||||||
|
for (int i = 0; i < trackerPaths.Length; i++) { trackerPaths[i] = OpenXRHelper.XR_NULL_PATH; }
|
||||||
|
|
||||||
|
result = XR_HTC_path_enumeration.xrEnumeratePathsForInteractionProfileHTC(
|
||||||
|
ref enumerateInfo,
|
||||||
|
pathCapacityInput: (UInt32)(trackerPaths.Length & 0x7FFFFFFF),
|
||||||
|
pathCountOutput: ref trackerCount,
|
||||||
|
paths: trackerPaths);
|
||||||
|
if (result != XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("xrEnumeratePathsForInteractionProfileHTC result: ").Append(result); ERROR(sb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ultimate_tracker_index = 0;
|
||||||
|
for (int i = 0; i < trackerCount; i++)
|
||||||
|
{
|
||||||
|
string userPath = PathToString(trackerPaths[i]);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("xrEnumeratePathsForInteractionProfileHTC[").Append(i).Append("] ").Append(userPath); DEBUG(sb);
|
||||||
|
|
||||||
|
if (userPath.Contains("ultimate", StringComparison.OrdinalIgnoreCase) && ultimate_tracker_index < s_UltimateTrackerProduct.Length)
|
||||||
|
{
|
||||||
|
string product = s_UltimateTrackerProduct[ultimate_tracker_index];
|
||||||
|
|
||||||
|
m_UltimateTrackerPathMap[product] = userPath;
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("Updates ").Append(product).Append(" path to ").Append(m_UltimateTrackerPathMap[product]); DEBUG(sb);
|
||||||
|
|
||||||
|
m_UltimateTrackerSerialMap[product] = ConvertUserPathToSerialNumber(userPath);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("Updates ").Append(product).Append(" serial number to ").Append(m_UltimateTrackerSerialMap[product]); DEBUG(sb);
|
||||||
|
|
||||||
|
ultimate_tracker_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// For example, user path "/user/xr_tracker_htc/vive_ultimate_tracker_0" will become serial number "VIVE_Ultimate_Tracker_0".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userPath">The user path from <see cref="EnumeratePath"> EnumeratePath </see>.</param>
|
||||||
|
/// <returns>Serial Number in string.</returns>
|
||||||
|
private string ConvertUserPathToSerialNumber(string userPath)
|
||||||
|
{
|
||||||
|
string result = "";
|
||||||
|
|
||||||
|
int lastSlashIndex = userPath.LastIndexOf('/');
|
||||||
|
if (lastSlashIndex >= 0)
|
||||||
|
{
|
||||||
|
string[] parts = userPath.Substring(lastSlashIndex + 1).Split('_');
|
||||||
|
parts[0] = parts[0].ToUpper();
|
||||||
|
for (int i = 1; i < parts.Length - 1; i++)
|
||||||
|
{
|
||||||
|
parts[i] = char.ToUpper(parts[i][0]) + parts[i].Substring(1);
|
||||||
|
}
|
||||||
|
result = string.Join("_", parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("This enumeration is deprecated. Please use XrInputSourceLocalizedNameFlags instead.")]
|
||||||
|
public enum InputSourceType : UInt64
|
||||||
|
{
|
||||||
|
SerialNumber = XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_SERIAL_NUMBER_BIT_HTC,
|
||||||
|
TrackerRole = XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT,
|
||||||
|
}
|
||||||
|
[Obsolete("This function is deprecated. Please use OpenXRHelper.GetInputSourceName instead.")]
|
||||||
|
public static XrResult GetInputSourceName(XrPath path, InputSourceType sourceType, out string sourceName)
|
||||||
|
{
|
||||||
|
string func = "GetInputSourceName() ";
|
||||||
|
|
||||||
|
sourceName = "";
|
||||||
|
if (!m_XrSessionCreated || xrGetInputSourceLocalizedName == null) { return XrResult.XR_ERROR_VALIDATION_FAILURE; }
|
||||||
|
|
||||||
|
string userPath = PathToString(path);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("userPath: ").Append(userPath).Append(", flag: ").Append((UInt64)sourceType); DEBUG(sb);
|
||||||
|
|
||||||
|
XrInputSourceLocalizedNameGetInfo nameInfo = new XrInputSourceLocalizedNameGetInfo(
|
||||||
|
XrStructureType.XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO,
|
||||||
|
IntPtr.Zero, path, (XrInputSourceLocalizedNameFlags)sourceType);
|
||||||
|
|
||||||
|
UInt32 nameSizeIn = 0;
|
||||||
|
UInt32 nameSizeOut = 0;
|
||||||
|
char[] buffer = new char[0];
|
||||||
|
|
||||||
|
XrResult result = xrGetInputSourceLocalizedName(m_XrSession, ref nameInfo, nameSizeIn, ref nameSizeOut, buffer);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("1.xrGetInputSourceLocalizedName(").Append(userPath).Append(") result: ").Append(result)
|
||||||
|
.Append(", flag: ").Append((UInt64)nameInfo.whichComponents)
|
||||||
|
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
||||||
|
.Append(", bufferCountOutput: ").Append(nameSizeOut);
|
||||||
|
DEBUG(sb);
|
||||||
|
if (result == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (nameSizeOut < 1)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("xrGetInputSourceLocalizedName(").Append(userPath).Append(")")
|
||||||
|
.Append(", flag: ").Append((UInt64)sourceType)
|
||||||
|
.Append("bufferCountOutput size is invalid!");
|
||||||
|
ERROR(sb);
|
||||||
|
return XrResult.XR_ERROR_VALIDATION_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameSizeIn = nameSizeOut;
|
||||||
|
buffer = new char[nameSizeIn];
|
||||||
|
|
||||||
|
result = xrGetInputSourceLocalizedName(m_XrSession, ref nameInfo, nameSizeIn, ref nameSizeOut, buffer);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("2.xrGetInputSourceLocalizedName(").Append(userPath).Append(") result: ").Append(result)
|
||||||
|
.Append(", flag: ").Append((UInt64)nameInfo.whichComponents)
|
||||||
|
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
||||||
|
.Append(", bufferCountOutput: ").Append(nameSizeOut);
|
||||||
|
DEBUG(sb);
|
||||||
|
if (result == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
sourceName = new string(buffer).TrimEnd('\0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG_CODE
|
||||||
|
XrInputSourceLocalizedNameGetInfo nameInfo;
|
||||||
|
private bool updateSerialNumber = true, updateUsage = false;
|
||||||
|
private bool UpdateTrackerMaps(string product, XrPath path, ref Dictionary<string, string> serialMap, ref Dictionary<string, string> roleMap)
|
||||||
|
{
|
||||||
|
string func = "UpdateTrackerMaps() ";
|
||||||
|
string s_path = PathToString(path);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("product: ").Append(product).Append(", path: ").Append(s_path); DEBUG(sb);
|
||||||
|
|
||||||
|
// -------------------- Tracker Serial Number --------------------
|
||||||
|
if (updateSerialNumber)
|
||||||
|
{
|
||||||
|
nameInfo.type = XrStructureType.XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO;
|
||||||
|
nameInfo.next = IntPtr.Zero;
|
||||||
|
nameInfo.sourcePath = path;
|
||||||
|
nameInfo.whichComponents = XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_SERIAL_NUMBER_BIT_HTC;
|
||||||
|
|
||||||
|
XrResult result = OpenXRHelper.GetInputSourceName(
|
||||||
|
xrGetInputSourceLocalizedName,
|
||||||
|
m_XrSession,
|
||||||
|
ref nameInfo,
|
||||||
|
out string sn);
|
||||||
|
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("GetInputSourceName(").Append(s_path).Append(")")
|
||||||
|
.Append(", sourceType: ").Append(nameInfo.whichComponents)
|
||||||
|
.Append(", serial number: ").Append(sn)
|
||||||
|
.Append(", result: ").Append(result);
|
||||||
|
DEBUG(sb);
|
||||||
|
|
||||||
|
/// For sample:
|
||||||
|
/// A user path (e.g. "/user/tracker_htc/index0") has the "sourceName" kUltimateTrackerSN0 ("VIVE_Ultimate_Tracker_0") which means
|
||||||
|
/// the corresponding product (e.g. kProductUltimateTracker0 = "VIVE Ultimate Tracker 0") has the "sourceName" kUltimateTrackerSN0.
|
||||||
|
/// So we have to set the serial of the product name kProductUltimateTracker0 to kUltimateTrackerSN0.
|
||||||
|
if (result == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (!serialMap.ContainsKey(product))
|
||||||
|
serialMap.Add(product, sn);
|
||||||
|
else
|
||||||
|
serialMap[product] = sn;
|
||||||
|
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("Sets product ").Append(product).Append(" with serial number ").Append(serialMap[product]); DEBUG(sb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -------------------- Tracker Role --------------------
|
||||||
|
if (updateUsage)
|
||||||
|
{
|
||||||
|
nameInfo.type = XrStructureType.XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO;
|
||||||
|
nameInfo.next = IntPtr.Zero;
|
||||||
|
nameInfo.sourcePath = path;
|
||||||
|
nameInfo.whichComponents = XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT;
|
||||||
|
|
||||||
|
XrResult result = OpenXRHelper.GetInputSourceName(
|
||||||
|
xrGetInputSourceLocalizedName,
|
||||||
|
m_XrSession,
|
||||||
|
ref nameInfo,
|
||||||
|
out string role);
|
||||||
|
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("GetInputSourceName(").Append(s_path).Append(")")
|
||||||
|
.Append(", sourceType: ").Append(nameInfo.whichComponents)
|
||||||
|
.Append(", role: ").Append(role)
|
||||||
|
.Append(", result: ").Append(result);
|
||||||
|
DEBUG(sb);
|
||||||
|
|
||||||
|
if (result == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
/// For sample:
|
||||||
|
/// A user path (e.g. "/user/tracker_htc/index0") has the "sourceName" kTrackerRoleLeftWrist ("Left Wrist") which means
|
||||||
|
/// the corresponding product (e.g. kProductUltimateTracker0 = "VIVE Ultimate Tracker 0") has the "sourceName" kTrackerRoleLeftWrist.
|
||||||
|
/// So we have to set the usage of the product name kProductUltimateTracker0 to kTrackerRoleLeftWrist.
|
||||||
|
if (!roleMap.ContainsKey(product))
|
||||||
|
roleMap.Add(product, role);
|
||||||
|
else
|
||||||
|
roleMap[product] = role;
|
||||||
|
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("Sets product ").Append(product).Append(" with usage ").Append(roleMap[product]); DEBUG(sb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Due to "ActionMap must be added from within the RegisterActionMapsWithRuntime method", this function is unusable.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="product">A tracker's product name.</param>
|
||||||
|
private void UpdateInputDeviceUltimateTracker(string product)
|
||||||
|
{
|
||||||
|
if (!IsUltimateTracker(product)) { return; }
|
||||||
|
string func = "UpdateInputDeviceUltimateTracker() ";
|
||||||
|
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("product: ").Append(product)
|
||||||
|
.Append(", serial number: ").Append(m_UltimateTrackerSerialMap[product])
|
||||||
|
.Append(", user path: ").Append(m_UltimateTrackerPathMap[product])
|
||||||
|
.Append(", usage: ").Append(m_UltimateTrackerUsageMap[product]);
|
||||||
|
DEBUG(sb);
|
||||||
|
|
||||||
|
XrPath path = StringToPath(m_UltimateTrackerPathMap[product]);
|
||||||
|
/// Updates tracker serial number (m_UltimateTrackerSerialMap) and role (m_UltimateTrackerUsageMap)
|
||||||
|
if (UpdateTrackerMaps(product, path, ref m_UltimateTrackerSerialMap, ref m_UltimateTrackerUsageMap))
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("Maps of ").Append(product)
|
||||||
|
.Append(" with user path ").Append(m_UltimateTrackerPathMap[product]).Append(" are updated.");
|
||||||
|
DEBUG(sb);
|
||||||
|
|
||||||
|
bool foundProduct = false;
|
||||||
|
for (int i = 0; i < InputSystem.devices.Count; i++)
|
||||||
|
{
|
||||||
|
if (InputSystem.devices[i] is XrTrackerDevice &&
|
||||||
|
InputSystem.devices[i].description.product.Equals(product))
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("Removes the XrTrackerDevice product ").Append(product);
|
||||||
|
DEBUG(sb);
|
||||||
|
|
||||||
|
InputSystem.RemoveDevice(InputSystem.devices[i]);
|
||||||
|
foundProduct = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundProduct)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("Adds a XrTrackerDevice product ").Append(product); DEBUG(sb);
|
||||||
|
RegisterActionMap(
|
||||||
|
product: product,
|
||||||
|
in_name: m_UltimateTrackerActionMap[product],
|
||||||
|
in_sn: m_UltimateTrackerSerialMap[product],
|
||||||
|
in_path: m_UltimateTrackerPathMap[product]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,6 +312,15 @@ namespace VIVE.OpenXR
|
|||||||
XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR,
|
XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR,
|
||||||
XR_TYPE_SWAPCHAIN_IMAGE_VULKAN2_KHR = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR,
|
XR_TYPE_SWAPCHAIN_IMAGE_VULKAN2_KHR = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR,
|
||||||
XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR,
|
XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR,
|
||||||
|
XR_TYPE_PLANE_DETECTOR_CREATE_INFO_EXT = 1000429001,
|
||||||
|
XR_TYPE_PLANE_DETECTOR_BEGIN_INFO_EXT = 1000429002,
|
||||||
|
XR_TYPE_PLANE_DETECTOR_GET_INFO_EXT = 1000429003,
|
||||||
|
XR_TYPE_PLANE_DETECTOR_LOCATIONS_EXT = 1000429004,
|
||||||
|
XR_TYPE_PLANE_DETECTOR_LOCATION_EXT = 1000429005,
|
||||||
|
XR_TYPE_PLANE_DETECTOR_POLYGON_BUFFER_EXT = 1000429006,
|
||||||
|
XR_TYPE_SYSTEM_PLANE_DETECTION_PROPERTIES_EXT = 1000429007,
|
||||||
|
XR_TYPE_SYSTEM_ANCHOR_PROPERTIES_HTC = 1000319000,
|
||||||
|
XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC = 1000319001,
|
||||||
XR_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
XR_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -474,7 +483,13 @@ namespace VIVE.OpenXR
|
|||||||
y = in_y;
|
y = in_y;
|
||||||
z = in_z;
|
z = in_z;
|
||||||
}
|
}
|
||||||
}
|
public static XrVector3f Zero => new XrVector3f(0, 0, 0);
|
||||||
|
public static XrVector3f One => new XrVector3f(1, 1, 1);
|
||||||
|
public static XrVector3f Up => new XrVector3f(0, 1, 0);
|
||||||
|
public static XrVector3f Forward => new XrVector3f(0, 0, 1);
|
||||||
|
public static XrVector3f Right => new XrVector3f(1, 0, 0);
|
||||||
|
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rotation is represented by a unit quaternion defined by the XrQuaternionf structure.
|
/// Rotation is represented by a unit quaternion defined by the XrQuaternionf structure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -507,6 +522,7 @@ namespace VIVE.OpenXR
|
|||||||
z = in_z;
|
z = in_z;
|
||||||
w = in_w;
|
w = in_w;
|
||||||
}
|
}
|
||||||
|
public static XrQuaternionf Identity => new XrQuaternionf(0, 0, 0, 1);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unless otherwise specified, colors are encoded as linear (not with sRGB nor other gamma compression) values with individual components being in the range of 0.0 through 1.0, and without the RGB components being premultiplied by the alpha component.
|
/// Unless otherwise specified, colors are encoded as linear (not with sRGB nor other gamma compression) values with individual components being in the range of 0.0 through 1.0, and without the RGB components being premultiplied by the alpha component.
|
||||||
@@ -640,6 +656,12 @@ namespace VIVE.OpenXR
|
|||||||
/// An <see cref="XrVector3f">XrVector3f</see> representing position within a space.
|
/// An <see cref="XrVector3f">XrVector3f</see> representing position within a space.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public XrVector3f position;
|
public XrVector3f position;
|
||||||
|
public XrPosef(XrQuaternionf in_orientation, XrVector3f in_position)
|
||||||
|
{
|
||||||
|
orientation = in_orientation;
|
||||||
|
position = in_position;
|
||||||
|
}
|
||||||
|
public static XrPosef Identity => new XrPosef(XrQuaternionf.Identity, XrVector3f.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1221,6 +1243,15 @@ namespace VIVE.OpenXR
|
|||||||
XR_SPACE_LOCATION_POSITION_TRACKED_BIT = 0x00000008,
|
XR_SPACE_LOCATION_POSITION_TRACKED_BIT = 0x00000008,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct XrSpaceLocation
|
||||||
|
{
|
||||||
|
public XrStructureType type;
|
||||||
|
public System.IntPtr next;
|
||||||
|
public XrSpaceLocationFlags locationFlags;
|
||||||
|
public XrPosef pose;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
public enum XrInputSourceLocalizedNameFlags : UInt64
|
public enum XrInputSourceLocalizedNameFlags : UInt64
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1931,7 +1962,51 @@ namespace VIVE.OpenXR
|
|||||||
public IntPtr next;
|
public IntPtr next;
|
||||||
public XrBool32 isActive;
|
public XrBool32 isActive;
|
||||||
};
|
};
|
||||||
|
/// <summary>
|
||||||
|
/// A structure indicates the frameWaitInfo.
|
||||||
|
/// </summary>
|
||||||
|
public struct XrFrameWaitInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="XrStructureType">XrStructureType</see> of this structure.
|
||||||
|
/// </summary>
|
||||||
|
public XrStructureType type;
|
||||||
|
/// <summary>
|
||||||
|
/// next is NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr next;
|
||||||
|
public XrFrameWaitInfo(IntPtr next_, XrStructureType type_)
|
||||||
|
{
|
||||||
|
next = next_;
|
||||||
|
type = type_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// A structure indicates the frameState.
|
||||||
|
/// </summary>
|
||||||
|
public struct XrFrameState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="XrStructureType">XrStructureType</see> of this structure.
|
||||||
|
/// </summary>
|
||||||
|
public XrStructureType type;
|
||||||
|
/// <summary>
|
||||||
|
/// next is NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr next;
|
||||||
|
/// <summary>
|
||||||
|
/// predictedDisplayTime is the anticipated display XrTime for the next application-generated frame.
|
||||||
|
/// </summary>
|
||||||
|
public Int64 predictedDisplayTime;
|
||||||
|
/// <summary>
|
||||||
|
/// predictedDisplayPeriod is the XrDuration of the display period for the next application-generated frame, for use in predicting display times beyond the next one.
|
||||||
|
/// </summary>
|
||||||
|
public Int64 predictedDisplayPeriod;
|
||||||
|
/// <summary>
|
||||||
|
/// shouldRender is XR_TRUE if the application should render its layers as normal and submit them to xrEndFrame. When this value is XR_FALSE, the application should avoid heavy GPU work where possible, for example by skipping layer rendering and then omitting those layers when calling xrEndFrame.
|
||||||
|
/// </summary>
|
||||||
|
public bool shouldRender;
|
||||||
|
}
|
||||||
public static class OpenXRHelper
|
public static class OpenXRHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -2224,6 +2299,59 @@ namespace VIVE.OpenXR
|
|||||||
ref UInt32 bufferCountOutput,
|
ref UInt32 bufferCountOutput,
|
||||||
[In, Out] char[] buffer);
|
[In, Out] char[] buffer);
|
||||||
|
|
||||||
|
public static XrResult GetInputSourceName(
|
||||||
|
xrGetInputSourceLocalizedNameDelegate xrGetInputSourceLocalizedName,
|
||||||
|
XrSession session,
|
||||||
|
ref XrInputSourceLocalizedNameGetInfo nameInfo,
|
||||||
|
out string sourceName)
|
||||||
|
{
|
||||||
|
string func = "GetInputSourceName() ";
|
||||||
|
|
||||||
|
sourceName = "";
|
||||||
|
if (xrGetInputSourceLocalizedName == null) { return XrResult.XR_ERROR_VALIDATION_FAILURE; }
|
||||||
|
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("path: ").Append(nameInfo.sourcePath).Append(", flag: ").Append((UInt64)nameInfo.whichComponents); DEBUG(sb);
|
||||||
|
|
||||||
|
UInt32 nameSizeIn = 0;
|
||||||
|
UInt32 nameSizeOut = 0;
|
||||||
|
char[] buffer = new char[0];
|
||||||
|
|
||||||
|
XrResult result = xrGetInputSourceLocalizedName(session, ref nameInfo, nameSizeIn, ref nameSizeOut, buffer);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("1.xrGetInputSourceLocalizedName(").Append(nameInfo.sourcePath).Append(") result: ").Append(result)
|
||||||
|
.Append(", flag: ").Append((UInt64)nameInfo.whichComponents)
|
||||||
|
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
||||||
|
.Append(", bufferCountOutput: ").Append(nameSizeOut);
|
||||||
|
DEBUG(sb);
|
||||||
|
if (result == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (nameSizeOut < 1)
|
||||||
|
{
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("xrGetInputSourceLocalizedName(").Append(nameInfo.sourcePath).Append(")")
|
||||||
|
.Append(", flag: ").Append((UInt64)nameInfo.whichComponents)
|
||||||
|
.Append("bufferCountOutput size is invalid!");
|
||||||
|
ERROR(sb);
|
||||||
|
return XrResult.XR_ERROR_VALIDATION_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameSizeIn = nameSizeOut;
|
||||||
|
buffer = new char[nameSizeIn];
|
||||||
|
|
||||||
|
result = xrGetInputSourceLocalizedName(session, ref nameInfo, nameSizeIn, ref nameSizeOut, buffer);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func)
|
||||||
|
.Append("2.xrGetInputSourceLocalizedName(").Append(nameInfo.sourcePath).Append(") result: ").Append(result)
|
||||||
|
.Append(", flag: ").Append((UInt64)nameInfo.whichComponents)
|
||||||
|
.Append(", bufferCapacityInput: ").Append(nameSizeIn)
|
||||||
|
.Append(", bufferCountOutput: ").Append(nameSizeOut);
|
||||||
|
DEBUG(sb);
|
||||||
|
if (result == XrResult.XR_SUCCESS) { sourceName = new string(buffer).TrimEnd('\0'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public delegate XrResult xrEnumerateInstanceExtensionPropertiesDelegate(
|
public delegate XrResult xrEnumerateInstanceExtensionPropertiesDelegate(
|
||||||
[In] char[] layerName,
|
[In] char[] layerName,
|
||||||
UInt32 propertyCapacityInput,
|
UInt32 propertyCapacityInput,
|
||||||
@@ -2313,9 +2441,46 @@ namespace VIVE.OpenXR
|
|||||||
XrSession session,
|
XrSession session,
|
||||||
ref XrActionStateGetInfo getInfo,
|
ref XrActionStateGetInfo getInfo,
|
||||||
ref XrActionStatePose state);
|
ref XrActionStatePose state);
|
||||||
|
/// <summary>
|
||||||
|
/// The function delegate declaration of <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrWaitFrame">xrWaitFrame</see>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">A handle to an XrSession previously created with <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateSession">xrCreateSession</see>.</param>
|
||||||
|
/// <param name="frameWaitInfo">frameWaitInfo exists for extensibility purposes, it is NULL or a pointer to a valid XrFrameWaitInfo.</param>
|
||||||
|
/// <param name="frameState">frameState is a pointer to a valid XrFrameState, an output parameter.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public delegate int xrWaitFrameDelegate(ulong session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Help call xrGetInstanceProcAddr and convert the result to delegate.\
|
||||||
|
/// For example, "OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrGetSystemProperties", out XrGetSystemProperties);"
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="Type">The function's delegate.</typeparam>
|
||||||
|
/// <param name="XrGetInstanceProcAddr">Your xrGetInstanceProcAddr delegate instance.</param>
|
||||||
|
/// <param name="xrInstance">Your xrInstance</param>
|
||||||
|
/// <param name="name">The function name</param>
|
||||||
|
/// <param name="func">The output delegate instance.</param>
|
||||||
|
/// <returns>If return false, the outout delegate instance will be default. Should not use it.</returns>
|
||||||
|
public static bool GetXrFunctionDelegate<Type>(xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr, XrInstance xrInstance, string name, out Type func)
|
||||||
|
{
|
||||||
|
if (XrGetInstanceProcAddr(xrInstance, name, out var funcPtr) == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (funcPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Debug.Log("Get function pointer of " + name);
|
||||||
|
func = Marshal.GetDelegateForFunctionPointer<Type>(funcPtr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError("GetXrFunctionDelegate: fail to get address of function: " + name);
|
||||||
|
}
|
||||||
|
func = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ClientInterface
|
public static class ClientInterface
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the user is presence (near HMD p-sensor < 1cm).
|
/// Checks if the user is presence (near HMD p-sensor < 1cm).
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR
|
||||||
|
{
|
||||||
|
public class XR_HTC_path_enumeration_defs
|
||||||
|
{
|
||||||
|
public virtual XrResult xrEnumeratePathsForInteractionProfileHTC(
|
||||||
|
ref XrPathsForInteractionProfileEnumerateInfoHTC createInfo,
|
||||||
|
UInt32 pathCapacityInput,
|
||||||
|
ref UInt32 pathCountOutput,
|
||||||
|
[In, Out] XrPath[] paths)
|
||||||
|
{
|
||||||
|
paths = null;
|
||||||
|
return XrResult.XR_ERROR_RUNTIME_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool GetUserPaths(string interactionProfileString, out XrPath[] userPaths)
|
||||||
|
{
|
||||||
|
userPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public virtual bool GetInputPathsWithUserPath(string interactionProfileString, XrPath userPath, out XrPath[] inputPaths)
|
||||||
|
{
|
||||||
|
inputPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string PathToString(ulong path) { return null; }
|
||||||
|
public virtual ulong StringToPath(string str) {
|
||||||
|
return 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class XR_HTC_path_enumeration
|
||||||
|
{
|
||||||
|
static XR_HTC_path_enumeration_defs m_Instance = null;
|
||||||
|
public static XR_HTC_path_enumeration_defs Interop
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_Instance == null)
|
||||||
|
{
|
||||||
|
m_Instance = new XR_HTC_path_enumeration_impls();
|
||||||
|
}
|
||||||
|
return m_Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XrResult xrEnumeratePathsForInteractionProfileHTC(
|
||||||
|
ref XrPathsForInteractionProfileEnumerateInfoHTC createInfo,
|
||||||
|
UInt32 pathCapacityInput,
|
||||||
|
ref UInt32 pathCountOutput,
|
||||||
|
[In, Out] XrPath[] paths)
|
||||||
|
{
|
||||||
|
return Interop.xrEnumeratePathsForInteractionProfileHTC(ref createInfo,
|
||||||
|
pathCapacityInput,
|
||||||
|
ref pathCountOutput,
|
||||||
|
paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string PathToString(ulong path) { return Interop.PathToString(path); }
|
||||||
|
public static ulong StringToPath(string str)
|
||||||
|
{
|
||||||
|
return Interop.StringToPath(str); }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: be103afd6857e52418eed1936213db83
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.XR.OpenXR;
|
||||||
|
namespace VIVE.OpenXR
|
||||||
|
{
|
||||||
|
public class XR_HTC_path_enumeration_impls : XR_HTC_path_enumeration_defs
|
||||||
|
{
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.XR_HTC_path_enumeration_impls";
|
||||||
|
void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
|
||||||
|
public XR_HTC_path_enumeration_impls() { DEBUG("XR_HTC_path_enumeration_impls()"); }
|
||||||
|
private VivePathEnumeration feature = null;
|
||||||
|
|
||||||
|
private void ASSERT_FEATURE()
|
||||||
|
{
|
||||||
|
if (feature == null) { feature = OpenXRSettings.Instance.GetFeature<VivePathEnumeration>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override XrResult xrEnumeratePathsForInteractionProfileHTC(
|
||||||
|
ref XrPathsForInteractionProfileEnumerateInfoHTC createInfo,
|
||||||
|
UInt32 pathCapacityInput,
|
||||||
|
ref UInt32 pathCountOutput,
|
||||||
|
[In, Out] XrPath[] paths)
|
||||||
|
{
|
||||||
|
DEBUG("xrEnumeratePathsForInteractionProfileHTC");
|
||||||
|
XrResult result = XrResult.XR_ERROR_VALIDATION_FAILURE;
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
if (feature)
|
||||||
|
{
|
||||||
|
result = feature.EnumeratePathsForInteractionProfileHTC(ref createInfo, pathCapacityInput,
|
||||||
|
ref pathCountOutput, paths);
|
||||||
|
if (result != XrResult.XR_SUCCESS) { paths = null; }
|
||||||
|
}
|
||||||
|
paths = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool GetUserPaths(string interactionProfileString, out XrPath[] userPaths)
|
||||||
|
{
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
if (feature)
|
||||||
|
{
|
||||||
|
if (feature.GetUserPaths(interactionProfileString, out userPaths))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool GetInputPathsWithUserPath(string interactionProfileString, XrPath userPath, out XrPath[] inputPaths)
|
||||||
|
{
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
if (feature)
|
||||||
|
{
|
||||||
|
if (feature.GetInputPathsWithUserPath(interactionProfileString, userPath,out inputPaths))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inputPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inputPaths = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string PathToString(ulong path)
|
||||||
|
{
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
if (feature)
|
||||||
|
return feature.xrPathToString(path);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public override ulong StringToPath(string str)
|
||||||
|
{
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
if (feature)
|
||||||
|
return feature.xrStringToPath(str);
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d0778892173385d4291570f9f47ec08d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Anchor.meta
Normal file
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Anchor.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ade49fa87f3cc404ea1b73cf2066c9a5
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
180
com.htc.upm.vive.openxr/Runtime/Toolkits/Anchor/AnchorManager.cs
Normal file
180
com.htc.upm.vive.openxr/Runtime/Toolkits/Anchor/AnchorManager.cs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.XR.OpenXR;
|
||||||
|
using VIVE.OpenXR.Feature;
|
||||||
|
using VIVE.OpenXR.Anchor;
|
||||||
|
using static VIVE.OpenXR.Anchor.ViveAnchor;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.Anchor
|
||||||
|
{
|
||||||
|
public static class AnchorManager
|
||||||
|
{
|
||||||
|
static ViveAnchor feature = null;
|
||||||
|
static bool isSupported = false;
|
||||||
|
|
||||||
|
static void CheckFeature()
|
||||||
|
{
|
||||||
|
if (feature != null) return;
|
||||||
|
|
||||||
|
feature = OpenXRSettings.Instance.GetFeature<ViveAnchor>();
|
||||||
|
if (feature == null)
|
||||||
|
throw new NotSupportedException("ViveAnchor feature is not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to get the extention feature instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ViveAnchor GetFeature()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CheckFeature();
|
||||||
|
}
|
||||||
|
catch (NotSupportedException)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("ViveAnchor feature is not enabled");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the extension is supported.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsSupported()
|
||||||
|
{
|
||||||
|
if (GetFeature() == null) return false;
|
||||||
|
if (isSupported) return true;
|
||||||
|
|
||||||
|
var ret = false;
|
||||||
|
if (feature.GetProperties(out XrSystemAnchorPropertiesHTC properties) == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
Debug.Log("Anchor: IsSupported() properties.supportedFeatures: " + properties.supportsAnchor);
|
||||||
|
ret = properties.supportsAnchor;
|
||||||
|
isSupported = ret;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Log("Anchor: IsSupported() GetSystemProperties failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a spatial anchor at tracking space (Camera Rig).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pose">The related pose to the tracking space (Camera Rig)</param>
|
||||||
|
/// <returns>Anchor container</returns>
|
||||||
|
public static Anchor CreateAnchor(Pose pose, string name)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CheckFeature();
|
||||||
|
XrSpace baseSpace = feature.GetTrackingSpace();
|
||||||
|
XrSpatialAnchorCreateInfoHTC createInfo = new XrSpatialAnchorCreateInfoHTC();
|
||||||
|
createInfo.type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC;
|
||||||
|
createInfo.poseInSpace = new XrPosef();
|
||||||
|
createInfo.poseInSpace.position = pose.position.ToOpenXRVector();
|
||||||
|
createInfo.poseInSpace.orientation = pose.rotation.ToOpenXRQuaternion();
|
||||||
|
createInfo.name.name = name;
|
||||||
|
createInfo.space = baseSpace;
|
||||||
|
|
||||||
|
if (feature.CreateSpatialAnchor(createInfo, out XrSpace anchor) == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
return new Anchor(anchor, name);
|
||||||
|
}
|
||||||
|
} catch (Exception) { }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetSpatialAnchorName(Anchor anchor, out string name)
|
||||||
|
{
|
||||||
|
return GetSpatialAnchorName(anchor.GetXrSpace(), out name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetSpatialAnchorName(XrSpace anchor, out string name)
|
||||||
|
{
|
||||||
|
name = "";
|
||||||
|
CheckFeature();
|
||||||
|
XrResult ret = feature.GetSpatialAnchorName(anchor, out XrSpatialAnchorNameHTC xrName);
|
||||||
|
if (ret == XrResult.XR_SUCCESS)
|
||||||
|
name = xrName.name;
|
||||||
|
return ret == XrResult.XR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the XrSpace stand for current tracking space.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static XrSpace GetTrackingSpace()
|
||||||
|
{
|
||||||
|
CheckFeature();
|
||||||
|
return feature.GetTrackingSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the pose related to current tracking space. Only when position and orientation are both valid, the pose is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anchor"></param>
|
||||||
|
/// <param name="pose"></param>
|
||||||
|
/// <returns>true if both position and rotation are valid.</returns>
|
||||||
|
public static bool GetTrackingSpacePose(Anchor anchor, out Pose pose)
|
||||||
|
{
|
||||||
|
var sw = SpaceWrapper.Instance;
|
||||||
|
return anchor.GetRelatedPose(feature.GetTrackingSpace(), ViveInterceptors.Instance.GetPredictTime(), out pose);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchor is a named Space. It can be used to create a spatial anchor, or get the anchor's name.
|
||||||
|
/// After use it, you should call Dispose() to release the anchor.
|
||||||
|
/// </summary>
|
||||||
|
public class Anchor : VIVE.OpenXR.Feature.Space
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The anchor's name
|
||||||
|
/// </summary>
|
||||||
|
string name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The anchor's name
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
name = GetSpatialAnchorName();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Anchor(XrSpace anchor, string name) : base(anchor)
|
||||||
|
{
|
||||||
|
// Get the current tracking space.
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Anchor(Anchor other) : base(other.space)
|
||||||
|
{
|
||||||
|
// Get the current tracking space.
|
||||||
|
name = other.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the anchor's name by using this anchor's handle, instead of the anchor's Name. This will update the anchor's Name.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetSpatialAnchorName()
|
||||||
|
{
|
||||||
|
AnchorManager.CheckFeature();
|
||||||
|
if (AnchorManager.GetSpatialAnchorName(this, out string name))
|
||||||
|
return name;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e5dd060e5d33dc942adbd03d6b9dc9bb
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b2e0dce69c4834e47b59f25575269566
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f9a059009a1d2414c94f3b9c7e46dfe9
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR
|
||||||
|
{
|
||||||
|
public class MemoryTools
|
||||||
|
{
|
||||||
|
public static IntPtr ToIntPtr<T>(T[] array) where T : Enum
|
||||||
|
{
|
||||||
|
int size = Marshal.SizeOf(typeof(T)) * array.Length;
|
||||||
|
IntPtr ptr = Marshal.AllocHGlobal(size);
|
||||||
|
int[] intArray = new int[array.Length];
|
||||||
|
for (int i = 0; i < array.Length; i++)
|
||||||
|
intArray[i] = (int)(object)array[i];
|
||||||
|
Marshal.Copy(intArray, 0, ptr, array.Length);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the same size raw buffer from input array.
|
||||||
|
public static IntPtr MakeRawMemory<T>(T[] refArray)
|
||||||
|
{
|
||||||
|
int size = Marshal.SizeOf(typeof(T)) * refArray.Length;
|
||||||
|
return Marshal.AllocHGlobal(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the same size raw buffer from input array.
|
||||||
|
public static void CopyFromRawMemory<T>(T[] array, IntPtr raw)
|
||||||
|
{
|
||||||
|
int step = Marshal.SizeOf(typeof(T));
|
||||||
|
for (int i = 0; i < array.Length; i++)
|
||||||
|
{
|
||||||
|
array[i] = Marshal.PtrToStructure<T>(IntPtr.Add(raw, i * step));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the same size raw buffer from input array. Make sure the raw has enough size.
|
||||||
|
public static void CopyToRawMemory<T>(IntPtr raw, T[] array)
|
||||||
|
{
|
||||||
|
int step = Marshal.SizeOf(typeof(T));
|
||||||
|
for (int i = 0; i < array.Length; i++)
|
||||||
|
{
|
||||||
|
Marshal.StructureToPtr<T>(array[i], IntPtr.Add(raw, i * step), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReleaseRawMemory(IntPtr ptr)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9a887cb158a37cf45b17458a4f27d7ee
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.XR.OpenXR;
|
||||||
|
using VIVE.OpenXR.PlaneDetection;
|
||||||
|
using static VIVE.OpenXR.PlaneDetection.VivePlaneDetection;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The detected plane's data.
|
||||||
|
/// See <see cref="VivePlaneDetection.XrPlaneDetectorLocationEXT"/>
|
||||||
|
/// </summary>
|
||||||
|
public class PlaneDetectorLocation
|
||||||
|
{
|
||||||
|
public ulong planeId;
|
||||||
|
public XrSpaceLocationFlags locationFlags;
|
||||||
|
public Pose pose;
|
||||||
|
public Vector3 size; // Only width(X) and height(Y) are valid, Z is always 0.
|
||||||
|
public XrPlaneDetectorOrientationEXT orientation;
|
||||||
|
public XrPlaneDetectorSemanticTypeEXT semanticType;
|
||||||
|
public uint polygonBufferCount;
|
||||||
|
public XrPlaneDetectorLocationEXT locationRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The information for creating Mesh.
|
||||||
|
/// Plane's normal is facing +Z in Unity's coordination.
|
||||||
|
/// </summary>
|
||||||
|
public class Plane
|
||||||
|
{
|
||||||
|
public Vector3 scale; // Only width(X) and height(Y) are valid, Z is always 1.
|
||||||
|
public Vector3 center; // Should always be Vector3.Zero.
|
||||||
|
public Vector3[] verticesRaw; // The original vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/>
|
||||||
|
public Vector3[] verticesGenerated; // generated vertices for creating Mesh.
|
||||||
|
public Vector2[] uvsGenerated;
|
||||||
|
public int[] indicesGenerated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// According to the input vertices, calculate the rectangle, and create the plane.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vertices">The vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/></param>
|
||||||
|
public static Plane CreateFromVertices(Vector2[] vertices)
|
||||||
|
{
|
||||||
|
// Assume the polygon is a rectangle.
|
||||||
|
if (vertices.Length != 4)
|
||||||
|
return null;
|
||||||
|
// Check the size from vertices.
|
||||||
|
Vector2 min, max;
|
||||||
|
min = max = new Vector2(vertices[0].x, vertices[0].y);
|
||||||
|
for (int i = 1; i < vertices.Length; i++)
|
||||||
|
{
|
||||||
|
min.x = Mathf.Min(min.x, vertices[i].x);
|
||||||
|
min.y = Mathf.Min(min.y, vertices[i].y);
|
||||||
|
max.x = Mathf.Max(max.x, vertices[i].x);
|
||||||
|
max.y = Mathf.Max(max.y, vertices[i].y);
|
||||||
|
}
|
||||||
|
var verticesRaw = new Vector3[vertices.Length];
|
||||||
|
for (int i = 0; i < vertices.Length; i++)
|
||||||
|
verticesRaw[i] = new Vector3(vertices[i].x, 0, vertices[i].y);
|
||||||
|
|
||||||
|
var verticesGenerated = new Vector3[4];
|
||||||
|
verticesGenerated[0] = new Vector3(min.x, min.y, 0);
|
||||||
|
verticesGenerated[1] = new Vector3(max.x, min.y, 0);
|
||||||
|
verticesGenerated[2] = new Vector3(min.x, max.y, 0);
|
||||||
|
verticesGenerated[3] = new Vector3(max.x, max.y, 0);
|
||||||
|
|
||||||
|
var indicesGenerated = new int[] { 0, 3, 2, 0, 1, 3 };
|
||||||
|
|
||||||
|
var uvsGenerated = new Vector2[4];
|
||||||
|
uvsGenerated[0] = new Vector2(0, 0);
|
||||||
|
uvsGenerated[1] = new Vector2(1, 0);
|
||||||
|
uvsGenerated[2] = new Vector2(0, 1);
|
||||||
|
uvsGenerated[3] = new Vector2(1, 1);
|
||||||
|
|
||||||
|
return new Plane()
|
||||||
|
{
|
||||||
|
scale = max - min,
|
||||||
|
center = (max + min) / 2,
|
||||||
|
verticesRaw = verticesRaw,
|
||||||
|
verticesGenerated = verticesGenerated,
|
||||||
|
indicesGenerated = indicesGenerated,
|
||||||
|
uvsGenerated = uvsGenerated
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The PlaneDetector is created by <see cref="PlaneDetectionManager.CreatePlaneDetector" />.
|
||||||
|
/// </summary>
|
||||||
|
public class PlaneDetector
|
||||||
|
{
|
||||||
|
IntPtr planeDetector = IntPtr.Zero;
|
||||||
|
VivePlaneDetection feature = null;
|
||||||
|
|
||||||
|
public PlaneDetector(IntPtr pd, VivePlaneDetection f)
|
||||||
|
{
|
||||||
|
feature = f;
|
||||||
|
planeDetector = pd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the raw plane detector handle. See <see cref="VivePlaneDetection.CreatePlaneDetector"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The raw plane detector handle</returns>
|
||||||
|
public IntPtr GetDetectorRaw()
|
||||||
|
{
|
||||||
|
return planeDetector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begin detect plane. In VIVE's implementation, planes are predefined in Room Setup.
|
||||||
|
/// However you have to call this function to get the plane information.
|
||||||
|
/// See <see cref="VivePlaneDetection.BeginPlaneDetection"/>
|
||||||
|
/// </summary>
|
||||||
|
public XrResult BeginPlaneDetection()
|
||||||
|
{
|
||||||
|
if (feature == null)
|
||||||
|
return XrResult.XR_ERROR_FEATURE_UNSUPPORTED;
|
||||||
|
Debug.Log("BeginPlaneDetection()");
|
||||||
|
var beginInfo = feature.MakeGetAllXrPlaneDetectorBeginInfoEXT();
|
||||||
|
return feature.BeginPlaneDetection(planeDetector, beginInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the state of plane detection.
|
||||||
|
/// See <see cref="VivePlaneDetection.GetPlaneDetectionState"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrPlaneDetectionStateEXT GetPlaneDetectionState()
|
||||||
|
{
|
||||||
|
if (feature == null)
|
||||||
|
return XrPlaneDetectionStateEXT.NONE_EXT;
|
||||||
|
Debug.Log("GetPlaneDetectionState()");
|
||||||
|
XrPlaneDetectionStateEXT state = XrPlaneDetectionStateEXT.NONE_EXT;
|
||||||
|
feature.GetPlaneDetectionState(planeDetector, ref state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get result of plane detection.
|
||||||
|
/// See <see cref="VivePlaneDetection.GetPlaneDetections"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="locations">The detected planes.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrResult GetPlaneDetections(out List<PlaneDetectorLocation> locations)
|
||||||
|
{
|
||||||
|
locations = null;
|
||||||
|
if (feature == null)
|
||||||
|
return XrResult.XR_ERROR_FEATURE_UNSUPPORTED;
|
||||||
|
Debug.Log("GetPlaneDetections()");
|
||||||
|
XrPlaneDetectorGetInfoEXT info = new XrPlaneDetectorGetInfoEXT
|
||||||
|
{
|
||||||
|
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_GET_INFO_EXT,
|
||||||
|
baseSpace = feature.GetTrackingSpace(),
|
||||||
|
time = feature.GetPredictTime(),
|
||||||
|
};
|
||||||
|
XrPlaneDetectorLocationsEXT locationsRaw = new XrPlaneDetectorLocationsEXT
|
||||||
|
{
|
||||||
|
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_LOCATIONS_EXT,
|
||||||
|
planeLocationCapacityInput = 0,
|
||||||
|
planeLocationCountOutput = 0,
|
||||||
|
planeLocations = IntPtr.Zero,
|
||||||
|
};
|
||||||
|
var ret = feature.GetPlaneDetections(planeDetector, ref info, ref locationsRaw);
|
||||||
|
if (ret != XrResult.XR_SUCCESS || locationsRaw.planeLocationCountOutput == 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
Debug.Log("GetPlaneDetections() locations.planeLocationCountOutput: " + locationsRaw.planeLocationCountOutput);
|
||||||
|
var locationsArray = new XrPlaneDetectorLocationEXT[locationsRaw.planeLocationCountOutput];
|
||||||
|
var locationsPtr = MemoryTools.MakeRawMemory(locationsArray);
|
||||||
|
locationsRaw.planeLocationCapacityInput = locationsRaw.planeLocationCountOutput;
|
||||||
|
locationsRaw.planeLocationCountOutput = 0;
|
||||||
|
locationsRaw.planeLocations = locationsPtr;
|
||||||
|
|
||||||
|
ret = feature.GetPlaneDetections(planeDetector, ref info, ref locationsRaw);
|
||||||
|
if (ret != XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
MemoryTools.ReleaseRawMemory(locationsPtr);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
MemoryTools.CopyFromRawMemory(locationsArray, locationsPtr);
|
||||||
|
|
||||||
|
locations = new List<PlaneDetectorLocation>();
|
||||||
|
|
||||||
|
// The plane's neutral pose is horizontal, and not like the plane pose in unity which is vertical.
|
||||||
|
// Therefore, we wil perform a rotation to convert from the OpenXR's forward to unity's forward.
|
||||||
|
// In Unity, the rotation applied order is in ZXY order.
|
||||||
|
Quaternion forward = Quaternion.Euler(-90, 180, 0);
|
||||||
|
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
|
||||||
|
{
|
||||||
|
XrPlaneDetectorLocationEXT location = locationsArray[i];
|
||||||
|
PlaneDetectorLocation pdl = new PlaneDetectorLocation
|
||||||
|
{
|
||||||
|
planeId = location.planeId,
|
||||||
|
locationFlags = location.locationFlags,
|
||||||
|
pose = new Pose(OpenXRHelper.ToUnityVector(location.pose.position), OpenXRHelper.ToUnityQuaternion(location.pose.orientation) * forward),
|
||||||
|
// Because the pose is converted, we will apply extent to X and Y.
|
||||||
|
size = new Vector3(location.extents.width, location.extents.height, 0),
|
||||||
|
orientation = location.orientation,
|
||||||
|
semanticType = location.semanticType,
|
||||||
|
polygonBufferCount = location.polygonBufferCount,
|
||||||
|
locationRaw = location,
|
||||||
|
};
|
||||||
|
locations.Add(pdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryTools.ReleaseRawMemory(locationsPtr);
|
||||||
|
|
||||||
|
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
|
||||||
|
{
|
||||||
|
var location = locations[i];
|
||||||
|
Debug.Log("GetPlaneDetections() location.planeId: " + location.planeId);
|
||||||
|
Debug.Log("GetPlaneDetections() location.locationFlags: " + location.locationFlags);
|
||||||
|
Debug.Log("GetPlaneDetections() location.pose.position: " + location.pose.position);
|
||||||
|
Debug.Log("GetPlaneDetections() location.pose.rotation: " + location.pose.rotation);
|
||||||
|
Debug.Log("GetPlaneDetections() location.pose.rotation.eulerAngles: " + location.pose.rotation.eulerAngles);
|
||||||
|
var rot = location.locationRaw.pose.orientation;
|
||||||
|
Debug.Log($"GetPlaneDetections() locationRaw.pose.rotation: {rot.x}, {rot.y}, {rot.z}, {rot.w}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the vertices of the plane from extension. Because there is no triangle
|
||||||
|
/// information from extension, it is hard to generate a mesh from only these vertices.
|
||||||
|
/// However VIVE only have rectangle plane. In this function, it will return the
|
||||||
|
/// <see cref="Plane"/> class which contains generated information for creating Mesh.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="planeId">The planeId from <see cref="PlaneDetectorLocation"/></param>
|
||||||
|
/// <returns>The information for creating Mesh.</returns>
|
||||||
|
public Plane GetPlane(ulong planeId)
|
||||||
|
{
|
||||||
|
if (feature == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
XrPlaneDetectorPolygonBufferEXT polygonBuffer = new XrPlaneDetectorPolygonBufferEXT
|
||||||
|
{
|
||||||
|
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_POLYGON_BUFFER_EXT,
|
||||||
|
vertexCapacityInput = 0,
|
||||||
|
vertexCountOutput = 0,
|
||||||
|
vertices = IntPtr.Zero,
|
||||||
|
};
|
||||||
|
|
||||||
|
var ret = feature.GetPlanePolygonBuffer(planeDetector, planeId, 0, ref polygonBuffer);
|
||||||
|
Debug.Log("GetPlane() polygonBuffer.vertexCountOutput: " + polygonBuffer.vertexCountOutput);
|
||||||
|
if (ret != XrResult.XR_SUCCESS || polygonBuffer.vertexCountOutput == 0)
|
||||||
|
return null;
|
||||||
|
var verticesArray = new Vector2[polygonBuffer.vertexCountOutput];
|
||||||
|
var verticesPtr = MemoryTools.MakeRawMemory(verticesArray);
|
||||||
|
polygonBuffer.vertexCapacityInput = polygonBuffer.vertexCountOutput;
|
||||||
|
polygonBuffer.vertexCountOutput = 0;
|
||||||
|
polygonBuffer.vertices = verticesPtr;
|
||||||
|
if (feature.GetPlanePolygonBuffer(planeDetector, planeId, 0, ref polygonBuffer) != XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
MemoryTools.ReleaseRawMemory(verticesPtr);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MemoryTools.CopyFromRawMemory(verticesArray, verticesPtr);
|
||||||
|
MemoryTools.ReleaseRawMemory(verticesPtr);
|
||||||
|
|
||||||
|
for (int j = 0; j < verticesArray.Length; j++)
|
||||||
|
{
|
||||||
|
var v = verticesArray[j];
|
||||||
|
Debug.Log($"GetPlane() verticesArray[{j}]: ({v.x}, {v.y})");
|
||||||
|
}
|
||||||
|
return Plane.CreateFromVertices(verticesArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlaneDetectionManager
|
||||||
|
{
|
||||||
|
static VivePlaneDetection feature = null;
|
||||||
|
static bool isSupported = false;
|
||||||
|
|
||||||
|
static void CheckFeature()
|
||||||
|
{
|
||||||
|
if (feature != null) return;
|
||||||
|
feature = OpenXRSettings.Instance.GetFeature<VivePlaneDetection>();
|
||||||
|
if (feature == null)
|
||||||
|
throw new NotSupportedException("PlaneDetection feature is not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to get the extention feature instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static VivePlaneDetection GetFeature()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CheckFeature();
|
||||||
|
}
|
||||||
|
catch (NotSupportedException)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("PlaneDetection feature is not enabled");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the extension is supported.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsSupported()
|
||||||
|
{
|
||||||
|
if (GetFeature() == null) return false;
|
||||||
|
if (isSupported) return true;
|
||||||
|
if (feature == null) return false;
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
if (feature.GetProperties(out var properties) == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
Debug.Log("PlaneDetection: IsSupported() properties.supportedFeatures: " + properties.supportedFeatures);
|
||||||
|
ret = (properties.supportedFeatures & CAPABILITY_PLANE_DETECTION_BIT_EXT) > 0;
|
||||||
|
isSupported = ret;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Log("PlaneDetection: IsSupported() GetSystemProperties failed.");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is a helper function. Currently only one createInfo is available. Developepr should create their own
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static XrPlaneDetectorCreateInfoEXT MakeXrPlaneDetectorCreateInfoEXT()
|
||||||
|
{
|
||||||
|
return new XrPlaneDetectorCreateInfoEXT
|
||||||
|
{
|
||||||
|
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_CREATE_INFO_EXT,
|
||||||
|
flags = XR_PLANE_DETECTOR_ENABLE_CONTOUR_BIT_EXT,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plane detector is a session of detect plane. You don't need to create multiple plane detector in VIVE's implemention. You need destroy it.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>PlaneDetector's handle</returns>
|
||||||
|
public static PlaneDetector CreatePlaneDetector()
|
||||||
|
{
|
||||||
|
CheckFeature();
|
||||||
|
if (feature == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var createInfo = MakeXrPlaneDetectorCreateInfoEXT();
|
||||||
|
var ret = feature.CreatePlaneDetector(createInfo, out var planeDetector);
|
||||||
|
if (ret != XrResult.XR_SUCCESS)
|
||||||
|
return null;
|
||||||
|
return new PlaneDetector(planeDetector, feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroy the plane detector to release resource.
|
||||||
|
/// </summary>
|
||||||
|
public static void DestroyPlaneDetector(PlaneDetector pd)
|
||||||
|
{
|
||||||
|
if (pd == null)
|
||||||
|
return;
|
||||||
|
CheckFeature();
|
||||||
|
if (feature == null)
|
||||||
|
return;
|
||||||
|
feature.DestroyPlaneDetector(pd.GetDetectorRaw());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f0a3b1ff9cb22f4293ff346f84a541e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fa3c636a6aff8234b9c9e019b5396109
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ff1e9b9f321387c4e86e6a5fa6cd65c9
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(CustomGrabPose))]
|
||||||
|
public class CustomGrabPoseEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private CustomGrabPose m_GrabPoseDesigner;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
m_GrabPoseDesigner = target as CustomGrabPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
base.OnInspectorGUI();
|
||||||
|
GUILayout.Space(10f);
|
||||||
|
ShowGrabPosesMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowGrabPosesMenu()
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("Save HandGrab Pose"))
|
||||||
|
{
|
||||||
|
m_GrabPoseDesigner.FindNearInteractable();
|
||||||
|
m_GrabPoseDesigner.SavePoseWithCandidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c01f88bc88bb27849a723888721c96f4
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditorInternal;
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(HandGrabInteractable))]
|
||||||
|
public class HandGrabInteractableEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private SerializedProperty m_IsGrabbable, m_ForceMovable, m_FingerRequirement, m_GrabPoses, m_ShowAllIndicator;
|
||||||
|
private SerializedProperty grabPoseName, gestureThumbPose, gestureIndexPose, gestureMiddlePose, gestureRingPose, gesturePinkyPose,
|
||||||
|
recordedGrabRotations, isLeft, enableIndicator, autoIndicator, indicatorObject, grabOffset;
|
||||||
|
private ReorderableList grabPoses;
|
||||||
|
private bool showGrabPoses = false;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
m_IsGrabbable = serializedObject.FindProperty("m_IsGrabbable");
|
||||||
|
m_ForceMovable = serializedObject.FindProperty("m_ForceMovable");
|
||||||
|
m_FingerRequirement = serializedObject.FindProperty("m_FingerRequirement");
|
||||||
|
m_GrabPoses = serializedObject.FindProperty("m_GrabPoses");
|
||||||
|
m_ShowAllIndicator = serializedObject.FindProperty("m_ShowAllIndicator");
|
||||||
|
|
||||||
|
#region ReorderableList
|
||||||
|
grabPoses = new ReorderableList(serializedObject, m_GrabPoses, true, true, true, true);
|
||||||
|
grabPoses.drawHeaderCallback = (Rect rect) =>
|
||||||
|
{
|
||||||
|
EditorGUI.LabelField(rect, "Grab Pose List");
|
||||||
|
};
|
||||||
|
grabPoses.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
|
||||||
|
{
|
||||||
|
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return; }
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(grabPoseName.stringValue))
|
||||||
|
{
|
||||||
|
grabPoseName.stringValue = $"Grab Pose {index + 1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect gestureRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
|
||||||
|
grabPoseName.stringValue = EditorGUI.TextField(gestureRect, grabPoseName.stringValue);
|
||||||
|
if (recordedGrabRotations.arraySize == 0)
|
||||||
|
{
|
||||||
|
// Draw GrabGesture fields
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
EditorGUI.PropertyField(gestureRect, gestureThumbPose);
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
EditorGUI.PropertyField(gestureRect, gestureIndexPose);
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
EditorGUI.PropertyField(gestureRect, gestureMiddlePose);
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
EditorGUI.PropertyField(gestureRect, gestureRingPose);
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
EditorGUI.PropertyField(gestureRect, gesturePinkyPose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw Handness fields
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
bool isToggle = EditorGUI.Toggle(gestureRect, "Is Left", isLeft.boolValue);
|
||||||
|
if (isToggle != isLeft.boolValue)
|
||||||
|
{
|
||||||
|
isLeft.boolValue = isToggle;
|
||||||
|
SwitchRotations(ref recordedGrabRotations);
|
||||||
|
}
|
||||||
|
// Draw Indicator fields
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
enableIndicator.boolValue = EditorGUI.Toggle(gestureRect, "Show Indicator", enableIndicator.boolValue);
|
||||||
|
if (enableIndicator.boolValue)
|
||||||
|
{
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
autoIndicator.boolValue = EditorGUI.Toggle(gestureRect, "Auto Generator Indicator", autoIndicator.boolValue);
|
||||||
|
if (!autoIndicator.boolValue)
|
||||||
|
{
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
indicatorObject.objectReferenceValue = (GameObject)EditorGUI.ObjectField(gestureRect, "Indicator", (GameObject)indicatorObject.objectReferenceValue, typeof(GameObject), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_ShowAllIndicator.boolValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw Mirror Pose fields
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
Rect labelRect = new Rect(gestureRect.x, gestureRect.y, EditorGUIUtility.labelWidth, gestureRect.height);
|
||||||
|
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Mirror Pose"));
|
||||||
|
|
||||||
|
Rect buttonRect1 = new Rect(gestureRect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, (gestureRect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 3, gestureRect.height);
|
||||||
|
Rect buttonRect2 = new Rect(buttonRect1.x + buttonRect1.width + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, buttonRect1.width, gestureRect.height);
|
||||||
|
Rect buttonRect3 = new Rect(buttonRect2.x + buttonRect2.width + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, buttonRect1.width, gestureRect.height);
|
||||||
|
if (GUI.Button(buttonRect1, "Align X axis"))
|
||||||
|
{
|
||||||
|
MirrorPose(ref grabOffset, Vector3.right);
|
||||||
|
}
|
||||||
|
if (GUI.Button(buttonRect2, "Align Y axis"))
|
||||||
|
{
|
||||||
|
MirrorPose(ref grabOffset, Vector3.up);
|
||||||
|
}
|
||||||
|
if (GUI.Button(buttonRect3, "Align Z axis"))
|
||||||
|
{
|
||||||
|
MirrorPose(ref grabOffset, Vector3.forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw Position fields
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
EditorGUI.PropertyField(gestureRect, grabOffset.FindPropertyRelative("position"));
|
||||||
|
|
||||||
|
// Draw Rotation fields
|
||||||
|
SerializedProperty rotationProperty = grabOffset.FindPropertyRelative("rotation");
|
||||||
|
Vector4 rotationVector = new Vector4(rotationProperty.quaternionValue.x, rotationProperty.quaternionValue.y, rotationProperty.quaternionValue.z, rotationProperty.quaternionValue.w);
|
||||||
|
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
rotationVector = EditorGUI.Vector4Field(gestureRect, "Rotation", rotationVector);
|
||||||
|
rotationProperty.quaternionValue = new Quaternion(rotationVector.x, rotationVector.y, rotationVector.z, rotationVector.w);
|
||||||
|
};
|
||||||
|
grabPoses.elementHeightCallback = (int index) =>
|
||||||
|
{
|
||||||
|
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return EditorGUIUtility.singleLineHeight; }
|
||||||
|
|
||||||
|
// Including Title, Handness, Show Indicator, Mirror Pose, Position, Rotation
|
||||||
|
int minHeight = 6;
|
||||||
|
// To Show GrabGesture
|
||||||
|
if (recordedGrabRotations.arraySize == 0)
|
||||||
|
{
|
||||||
|
minHeight += 5;
|
||||||
|
}
|
||||||
|
if (enableIndicator.boolValue)
|
||||||
|
{
|
||||||
|
// To Show Auto Indicator
|
||||||
|
minHeight += 1;
|
||||||
|
// To Show Indicator Gameobject
|
||||||
|
if (!autoIndicator.boolValue)
|
||||||
|
{
|
||||||
|
minHeight += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EditorGUIUtility.singleLineHeight * minHeight + EditorGUIUtility.standardVerticalSpacing * (minHeight + 2);
|
||||||
|
};
|
||||||
|
grabPoses.onAddCallback = (ReorderableList list) =>
|
||||||
|
{
|
||||||
|
m_GrabPoses.arraySize++;
|
||||||
|
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(list.count - 1)))
|
||||||
|
{
|
||||||
|
grabPoseName.stringValue = $"Grab Pose {list.count}";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
EditorGUILayout.PropertyField(m_IsGrabbable);
|
||||||
|
EditorGUILayout.PropertyField(m_ForceMovable);
|
||||||
|
EditorGUILayout.PropertyField(m_FingerRequirement);
|
||||||
|
showGrabPoses = EditorGUILayout.Foldout(showGrabPoses, "Grab Pose Settings");
|
||||||
|
if (showGrabPoses)
|
||||||
|
{
|
||||||
|
if(m_GrabPoses.arraySize == 0)
|
||||||
|
{
|
||||||
|
grabPoses.elementHeight = EditorGUIUtility.singleLineHeight;
|
||||||
|
}
|
||||||
|
grabPoses.DoLayoutList();
|
||||||
|
|
||||||
|
bool isToggle = EditorGUILayout.Toggle("Show All Indicator", m_ShowAllIndicator.boolValue);
|
||||||
|
if (isToggle != m_ShowAllIndicator.boolValue)
|
||||||
|
{
|
||||||
|
m_ShowAllIndicator.boolValue = isToggle;
|
||||||
|
for (int i = 0; i < m_GrabPoses.arraySize; i++)
|
||||||
|
{
|
||||||
|
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(i)))
|
||||||
|
{
|
||||||
|
enableIndicator.boolValue = m_ShowAllIndicator.boolValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool UpdateGrabPose(SerializedProperty grabPose)
|
||||||
|
{
|
||||||
|
SerializedProperty indicator = grabPose.FindPropertyRelative("indicator");
|
||||||
|
if (grabPose == null || indicator == null) { return false; }
|
||||||
|
|
||||||
|
grabPoseName = grabPose.FindPropertyRelative("grabPoseName");
|
||||||
|
gestureThumbPose = grabPose.FindPropertyRelative("handGrabGesture.thumbPose");
|
||||||
|
gestureIndexPose = grabPose.FindPropertyRelative("handGrabGesture.indexPose");
|
||||||
|
gestureMiddlePose = grabPose.FindPropertyRelative("handGrabGesture.middlePose");
|
||||||
|
gestureRingPose = grabPose.FindPropertyRelative("handGrabGesture.ringPose");
|
||||||
|
gesturePinkyPose = grabPose.FindPropertyRelative("handGrabGesture.pinkyPose");
|
||||||
|
recordedGrabRotations = grabPose.FindPropertyRelative("recordedGrabRotations");
|
||||||
|
isLeft = grabPose.FindPropertyRelative("isLeft");
|
||||||
|
enableIndicator = indicator.FindPropertyRelative("enableIndicator");
|
||||||
|
autoIndicator = indicator.FindPropertyRelative("autoIndicator");
|
||||||
|
indicatorObject = indicator.FindPropertyRelative("target");
|
||||||
|
grabOffset = grabPose.FindPropertyRelative("grabOffset");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert the rotation of joints of the current hand into those of another hand.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rotations">Rotation of joints of the current hand.</param>
|
||||||
|
private void SwitchRotations(ref SerializedProperty rotations)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < rotations.arraySize; i++)
|
||||||
|
{
|
||||||
|
Quaternion rotation = rotations.GetArrayElementAtIndex(i).quaternionValue;
|
||||||
|
Quaternion newRotation = Quaternion.Euler(rotation.eulerAngles.x, -rotation.eulerAngles.y, -rotation.eulerAngles.z);
|
||||||
|
rotations.GetArrayElementAtIndex(i).quaternionValue = newRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the pose properties (position and rotation) of a serialized object along a specified mirror axis.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pose">The serialized property representing the pose to be mirrored.</param>
|
||||||
|
/// <param name="mirrorAxis">The axis along which the mirroring should occur.</param>
|
||||||
|
private void MirrorPose(ref SerializedProperty pose, Vector3 mirrorAxis)
|
||||||
|
{
|
||||||
|
Vector3 sourcePos = pose.FindPropertyRelative("position").vector3Value;
|
||||||
|
Quaternion sourceRot = pose.FindPropertyRelative("rotation").quaternionValue;
|
||||||
|
Vector3 sourceFwd = sourceRot * Vector3.forward;
|
||||||
|
Vector3 sourceUp = sourceRot * Vector3.up;
|
||||||
|
|
||||||
|
// Calculate the mirrored position using Vector3.Reflect along the specified mirror axis.
|
||||||
|
Vector3 mirroredPosition = Vector3.Reflect(sourcePos, mirrorAxis);
|
||||||
|
// Calculate the mirrored rotation using Quaternion.LookRotation and Vector3.Reflect for the forward and up vectors.
|
||||||
|
Quaternion mirroredRotation = Quaternion.LookRotation(Vector3.Reflect(sourceFwd, mirrorAxis), Vector3.Reflect(sourceUp, mirrorAxis));
|
||||||
|
|
||||||
|
pose.FindPropertyRelative("position").vector3Value = mirroredPosition;
|
||||||
|
pose.FindPropertyRelative("rotation").quaternionValue = mirroredRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7ba007edb5befc349a95aa1cfa7cb6c9
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(HandMeshManager))]
|
||||||
|
public class HandMeshManagerEditor : Editor
|
||||||
|
{
|
||||||
|
private HandMeshManager m_HandJointManager;
|
||||||
|
private SerializedProperty m_Handedness, m_HandGrabber, m_RootJointType, m_HandRootJoint, m_HandJoints;
|
||||||
|
|
||||||
|
private bool showJoints = false;
|
||||||
|
public static readonly GUIContent findJoints = EditorGUIUtility.TrTextContent("Find Joints");
|
||||||
|
public static readonly GUIContent clearJoints = EditorGUIUtility.TrTextContent("Clear");
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
m_Handedness = serializedObject.FindProperty("m_Handedness");
|
||||||
|
m_HandGrabber = serializedObject.FindProperty("m_HandGrabber");
|
||||||
|
m_RootJointType = serializedObject.FindProperty("m_RootJointType");
|
||||||
|
m_HandRootJoint = serializedObject.FindProperty("m_HandRootJoint");
|
||||||
|
m_HandJoints = serializedObject.FindProperty("m_HandJoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
EditorGUILayout.PropertyField(m_Handedness);
|
||||||
|
EditorGUILayout.PropertyField(m_HandGrabber);
|
||||||
|
EditorGUILayout.HelpBox("Without HandGrabber, it still works but won't stop when colliding with Immovable objects.", MessageType.Info);
|
||||||
|
EditorGUILayout.PropertyField(m_RootJointType);
|
||||||
|
EditorGUILayout.PropertyField(m_HandRootJoint);
|
||||||
|
showJoints = EditorGUILayout.Foldout(showJoints, "Hand Joints");
|
||||||
|
if (showJoints)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_HandJoints.arraySize; i++)
|
||||||
|
{
|
||||||
|
SerializedProperty joint = m_HandJoints.GetArrayElementAtIndex(i);
|
||||||
|
JointType jointType = (JointType)i;
|
||||||
|
EditorGUILayout.PropertyField(joint, new GUIContent(jointType.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
|
{
|
||||||
|
m_HandJointManager = target as HandMeshManager;
|
||||||
|
using (new EditorGUI.DisabledScope())
|
||||||
|
{
|
||||||
|
if (GUILayout.Button(findJoints))
|
||||||
|
{
|
||||||
|
m_HandJointManager.FindJoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (new EditorGUI.DisabledScope())
|
||||||
|
{
|
||||||
|
if (GUILayout.Button(clearJoints))
|
||||||
|
{
|
||||||
|
m_HandJointManager.ClearJoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d606d68d81c647241b90f1060786476c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e312e5246287a2546944bb77519270c4
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: cb1dc53a6d599b74c87e1c691fe77d21
|
guid: 32007394341a17c44859030ef5b809ee
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: a875c1780d640dd41a6eac1b6b8f27bb
|
guid: 010c0098d232cb0428f42a48488a6255
|
||||||
PrefabImporter:
|
PrefabImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: d521c5c5e79afd943a590248701d29b2
|
guid: 9731229184277b54ba66bffd1633169b
|
||||||
PrefabImporter:
|
PrefabImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9b7f1f69b8b23e9459efda715941fbee
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is designed to automatically generate indicators.
|
||||||
|
/// </summary>
|
||||||
|
public class AutoGenIndicator : MonoBehaviour
|
||||||
|
{
|
||||||
|
private MeshRenderer meshRenderer;
|
||||||
|
private MeshFilter meshFilter;
|
||||||
|
|
||||||
|
private readonly Color indicatorColor = new Color(1f, 0.7960785f, 0.09411766f, 1f);
|
||||||
|
private const float k_Length = 0.05f;
|
||||||
|
private const float k_Width = 0.05f;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
meshRenderer = transform.gameObject.AddComponent<MeshRenderer>();
|
||||||
|
meshFilter = transform.gameObject.AddComponent<MeshFilter>();
|
||||||
|
MeshInitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the mesh for the indicator.
|
||||||
|
/// </summary>
|
||||||
|
private void MeshInitialize()
|
||||||
|
{
|
||||||
|
Shader shader = Shader.Find("Sprites/Default");
|
||||||
|
meshRenderer.material = new Material(shader);
|
||||||
|
meshRenderer.material.SetColor("_Color", indicatorColor);
|
||||||
|
meshRenderer.sortingOrder = 1;
|
||||||
|
|
||||||
|
Mesh arrowMesh = new Mesh();
|
||||||
|
Vector3[] vertices = new Vector3[4];
|
||||||
|
int[] triangles = new int[3 * 2];
|
||||||
|
|
||||||
|
vertices[0] = new Vector3(0, 0f, 0f);
|
||||||
|
vertices[1] = new Vector3(0f, k_Length * 0.8f, 0f);
|
||||||
|
vertices[2] = new Vector3(-k_Width * 0.5f, k_Length, 0f);
|
||||||
|
vertices[3] = new Vector3(k_Width * 0.5f, k_Length, 0f);
|
||||||
|
|
||||||
|
triangles[0] = 0;
|
||||||
|
triangles[1] = 2;
|
||||||
|
triangles[2] = 1;
|
||||||
|
|
||||||
|
triangles[3] = 0;
|
||||||
|
triangles[4] = 1;
|
||||||
|
triangles[5] = 3;
|
||||||
|
|
||||||
|
arrowMesh.vertices = vertices;
|
||||||
|
arrowMesh.triangles = triangles;
|
||||||
|
|
||||||
|
arrowMesh.RecalculateNormals();
|
||||||
|
|
||||||
|
meshFilter.mesh = arrowMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the pose of the indicator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The position vector to set.</param>
|
||||||
|
/// <param name="direction">The direction vector to set.</param>
|
||||||
|
public void SetPose(Vector3 position, Vector3 direction)
|
||||||
|
{
|
||||||
|
transform.position = position;
|
||||||
|
transform.up = direction.normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4694bdada589dce4c9b7096c7169833f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is designed to edit grab gestures.
|
||||||
|
/// </summary>
|
||||||
|
[RequireComponent(typeof(HandMeshManager))]
|
||||||
|
public class CustomGrabPose : MonoBehaviour
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
[SerializeField]
|
||||||
|
private HandGrabGesture m_GrabGesture;
|
||||||
|
|
||||||
|
private HandMeshManager jointManager;
|
||||||
|
private Transform palmTransform;
|
||||||
|
private HandGrabGesture currentGesture;
|
||||||
|
private HandGrabInteractable candidate = null;
|
||||||
|
private readonly float k_GrabDistance = 0.1f;
|
||||||
|
|
||||||
|
private Pose[] fingerTipPoses => new Pose[5];
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (jointManager == null)
|
||||||
|
{
|
||||||
|
jointManager = transform.GetComponent<HandMeshManager>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
// Non-DirectPreview mode.
|
||||||
|
if (m_GrabGesture != currentGesture)
|
||||||
|
{
|
||||||
|
currentGesture = m_GrabGesture;
|
||||||
|
jointManager.SetJointsFromGrabGesture(currentGesture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the nearest interactable object to the hand.
|
||||||
|
/// </summary>
|
||||||
|
public void FindNearInteractable()
|
||||||
|
{
|
||||||
|
if (jointManager.GetJointTransform(JointType.Palm, out palmTransform))
|
||||||
|
{
|
||||||
|
float maxScore = 0;
|
||||||
|
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
||||||
|
{
|
||||||
|
float distanceScore = interactable.CalculateDistanceScore(palmTransform.position, k_GrabDistance);
|
||||||
|
if (distanceScore > maxScore)
|
||||||
|
{
|
||||||
|
maxScore = distanceScore;
|
||||||
|
candidate = interactable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the position and rotation offset with the candidate.
|
||||||
|
/// </summary>
|
||||||
|
public void SavePoseWithCandidate()
|
||||||
|
{
|
||||||
|
if (candidate != null &&
|
||||||
|
jointManager.GetJointTransform(JointType.Palm, out palmTransform))
|
||||||
|
{
|
||||||
|
Vector3 posOffset = candidate.transform.position - palmTransform.position;
|
||||||
|
Quaternion rotOffset = palmTransform.rotation;
|
||||||
|
GrabPose grabPose = GrabPose.Identity;
|
||||||
|
grabPose.Update($"Grab Pose {candidate.grabPoses.Count + 1}", currentGesture, jointManager.IsLeft);
|
||||||
|
grabPose.grabOffset = new GrabOffset(candidate.transform.position, candidate.transform.rotation, posOffset, rotOffset);
|
||||||
|
if (!candidate.grabPoses.Contains(grabPose))
|
||||||
|
{
|
||||||
|
candidate.grabPoses.Add(grabPose);
|
||||||
|
}
|
||||||
|
GrabbablePoseRecorder.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fd5957dc7b39bd249885b5bb53749b7a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is designed to generate appropriately sized colliders for each joint.
|
||||||
|
/// </summary>
|
||||||
|
public class GrabCollider : MonoBehaviour
|
||||||
|
{
|
||||||
|
public enum CollisionState
|
||||||
|
{
|
||||||
|
start = 0,
|
||||||
|
keep = 1,
|
||||||
|
end = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
private CapsuleCollider m_Collider = null;
|
||||||
|
public Collider Collider => m_Collider;
|
||||||
|
|
||||||
|
private bool m_IsCollision = false;
|
||||||
|
public bool IsCollision { get { return m_IsCollision; } set { m_IsCollision = value; } }
|
||||||
|
|
||||||
|
private const float k_ColliderRadius = 0.01f;
|
||||||
|
private const float k_ColliderHeight = 0.01f;
|
||||||
|
private JointType jointType = JointType.Count;
|
||||||
|
|
||||||
|
public delegate void CollisionHandler(JointType joint, Collision collision, CollisionState state);
|
||||||
|
private CollisionHandler m_CollisionHandler;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
m_Collider = transform.GetComponent<CapsuleCollider>();
|
||||||
|
if (m_Collider == null)
|
||||||
|
{
|
||||||
|
m_Collider = transform.gameObject.AddComponent<CapsuleCollider>();
|
||||||
|
}
|
||||||
|
m_Collider.radius = k_ColliderRadius;
|
||||||
|
m_Collider.height = k_ColliderHeight;
|
||||||
|
m_Collider.direction = 2;
|
||||||
|
|
||||||
|
Rigidbody rigidbody = transform.GetComponent<Rigidbody>();
|
||||||
|
if (rigidbody == null)
|
||||||
|
{
|
||||||
|
rigidbody = transform.gameObject.AddComponent<Rigidbody>();
|
||||||
|
}
|
||||||
|
rigidbody.useGravity = false;
|
||||||
|
rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||||
|
rigidbody.constraints = RigidbodyConstraints.FreezeAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the joint id and adjust collider size..
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">JointType of joint.</param>
|
||||||
|
public void SetJointId(int id)
|
||||||
|
{
|
||||||
|
jointType = (JointType)id;
|
||||||
|
if (m_Collider)
|
||||||
|
{
|
||||||
|
// Adjust the size and position of the collider based on jointId.
|
||||||
|
switch (jointType)
|
||||||
|
{
|
||||||
|
case JointType.Thumb_Joint0:
|
||||||
|
case JointType.Thumb_Joint1:
|
||||||
|
m_Collider.height = 0.03f;
|
||||||
|
break;
|
||||||
|
case JointType.Index_Joint0:
|
||||||
|
case JointType.Middle_Joint0:
|
||||||
|
case JointType.Ring_Joint0:
|
||||||
|
case JointType.Pinky_Joint0:
|
||||||
|
m_Collider.height = 0.08f;
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.02f);
|
||||||
|
break;
|
||||||
|
case JointType.Index_Joint1:
|
||||||
|
case JointType.Middle_Joint1:
|
||||||
|
case JointType.Ring_Joint1:
|
||||||
|
case JointType.Pinky_Joint1:
|
||||||
|
m_Collider.height = 0.05f;
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.02f);
|
||||||
|
break;
|
||||||
|
case JointType.Index_Tip:
|
||||||
|
case JointType.Middle_Tip:
|
||||||
|
case JointType.Ring_Tip:
|
||||||
|
case JointType.Pinky_Tip:
|
||||||
|
m_Collider.radius = 0.005f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddListener(CollisionHandler handler)
|
||||||
|
{
|
||||||
|
m_CollisionHandler += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveListener(CollisionHandler handler)
|
||||||
|
{
|
||||||
|
m_CollisionHandler -= handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollisionEnter(Collision collision)
|
||||||
|
{
|
||||||
|
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
|
||||||
|
{
|
||||||
|
m_CollisionHandler.Invoke(jointType, collision, CollisionState.start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollisionStay(Collision collision)
|
||||||
|
{
|
||||||
|
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
|
||||||
|
{
|
||||||
|
m_CollisionHandler.Invoke(jointType, collision, CollisionState.keep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollisionExit(Collision collision)
|
||||||
|
{
|
||||||
|
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
|
||||||
|
{
|
||||||
|
m_CollisionHandler.Invoke(jointType, collision, CollisionState.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsGrabCollider(Collider collider)
|
||||||
|
{
|
||||||
|
GrabCollider grabCollider = collider.gameObject.GetComponent<GrabCollider>();
|
||||||
|
return grabCollider != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ba0bbb9482b5a5d479cf11f2253cdc3e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,486 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public class GrabColliderManager : MonoBehaviour
|
||||||
|
{
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.GrabColliderManager";
|
||||||
|
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The struct is designed to record movable colliders,
|
||||||
|
/// including which grabbable they belong to, which joints collisioned with, and whether they have been grabbed.
|
||||||
|
/// </summary>
|
||||||
|
private struct MovableHitInfo
|
||||||
|
{
|
||||||
|
public struct JointHitInfo
|
||||||
|
{
|
||||||
|
public JointType joint;
|
||||||
|
public Vector3 hitOffset;
|
||||||
|
public int hitTime { get; private set; }
|
||||||
|
|
||||||
|
public JointHitInfo(JointType in_JointType, Vector3 in_HitOffset)
|
||||||
|
{
|
||||||
|
joint = in_JointType;
|
||||||
|
hitOffset = in_HitOffset;
|
||||||
|
hitTime = Time.frameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
hitTime = Time.frameCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HandGrabInteractable grabbable;
|
||||||
|
public List<JointHitInfo> jointHitInfos;
|
||||||
|
public bool grabbed;
|
||||||
|
public bool stopMove;
|
||||||
|
|
||||||
|
public MovableHitInfo(HandGrabInteractable in_Grabbable, JointType in_Joint, Vector3 in_Offset)
|
||||||
|
{
|
||||||
|
grabbable = in_Grabbable;
|
||||||
|
jointHitInfos = new List<JointHitInfo>()
|
||||||
|
{
|
||||||
|
new JointHitInfo(in_Joint, in_Offset)
|
||||||
|
};
|
||||||
|
grabbed = false;
|
||||||
|
stopMove = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(bool in_Grabbed, bool in_StopMove)
|
||||||
|
{
|
||||||
|
grabbed = in_Grabbed;
|
||||||
|
stopMove = in_StopMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
grabbed = false;
|
||||||
|
stopMove = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a JointType. If it's already in the dictionary, update the time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="joint">The joint which needs to be added.</param>
|
||||||
|
public void AddJoint(JointType joint, Vector3 offset)
|
||||||
|
{
|
||||||
|
int hitId = jointHitInfos.FindIndex(x => x.joint == joint);
|
||||||
|
if (hitId == -1)
|
||||||
|
{
|
||||||
|
jointHitInfos.Add(new JointHitInfo(joint, offset));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JointHitInfo jointHitInfo = jointHitInfos[hitId];
|
||||||
|
jointHitInfo.Update();
|
||||||
|
jointHitInfos[hitId] = jointHitInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a JointType and check if it has been grabbed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="joint">The joint which needs to be removed.</param>
|
||||||
|
public void RemoveJoint(JointType joint)
|
||||||
|
{
|
||||||
|
int hitId = jointHitInfos.FindIndex(x => x.joint == joint);
|
||||||
|
if (hitId != -1)
|
||||||
|
{
|
||||||
|
jointHitInfos.RemoveAt(hitId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private HandMeshManager jointManager;
|
||||||
|
|
||||||
|
private GrabCollider[] jointsCollider = new GrabCollider[(int)JointType.Count];
|
||||||
|
private Pose[] jointsPrevFramePose = new Pose[(int)JointType.Count];
|
||||||
|
private bool isImmovableCollision = false;
|
||||||
|
private bool isGrabbing = false;
|
||||||
|
private List<MovableHitInfo> movableHits = new List<MovableHitInfo>();
|
||||||
|
private Dictionary<GrabCollider, Collider> immovableHits = new Dictionary<GrabCollider, Collider>();
|
||||||
|
public delegate void OnImmovableCollision(bool enable);
|
||||||
|
private OnImmovableCollision immovableCollisionHandler;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (jointManager == null)
|
||||||
|
{
|
||||||
|
jointManager = transform.GetComponent<HandMeshManager>();
|
||||||
|
if (jointManager == null)
|
||||||
|
{
|
||||||
|
ERROR("Failed to find HandJointManager.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (jointManager != null)
|
||||||
|
{
|
||||||
|
jointManager.HandGrabber.AddBeginGrabListener(OnGrabberBeginGrab);
|
||||||
|
jointManager.HandGrabber.AddEndGrabListener(OnGrabberEndGrab);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateJointsCollider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (jointManager != null)
|
||||||
|
{
|
||||||
|
jointManager.HandGrabber.RemoveBeginGrabListener(OnGrabberBeginGrab);
|
||||||
|
jointManager.HandGrabber.RemoveEndGrabListener(OnGrabberEndGrab);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var collider in jointsCollider)
|
||||||
|
{
|
||||||
|
collider.RemoveListener(CollisionEvent);
|
||||||
|
Destroy(collider);
|
||||||
|
}
|
||||||
|
Array.Clear(jointsCollider, 0, jointsCollider.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (jointManager == null) { return; }
|
||||||
|
|
||||||
|
UpdateColliderPose();
|
||||||
|
if (!isGrabbing)
|
||||||
|
{
|
||||||
|
UpdateImmovable();
|
||||||
|
UpdateMovable();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < jointsCollider.Length; i++)
|
||||||
|
{
|
||||||
|
jointsPrevFramePose[i] = jointsCollider[i] == null ? Pose.identity : new Pose(jointsCollider[i].transform.position, jointsCollider[i].transform.rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create colliders for each joint and set them do not collide with each other.
|
||||||
|
/// </summary>
|
||||||
|
private void CreateJointsCollider()
|
||||||
|
{
|
||||||
|
if (jointManager != null)
|
||||||
|
{
|
||||||
|
var cloneRoot = Instantiate(jointManager.HandRootJoint, jointManager.HandRootJoint.parent);
|
||||||
|
cloneRoot.name = jointManager.HandRootJoint.name;
|
||||||
|
List<GameObject> children = new List<GameObject>() { cloneRoot.gameObject };
|
||||||
|
GetChildren(cloneRoot, children);
|
||||||
|
|
||||||
|
foreach (var child in children)
|
||||||
|
{
|
||||||
|
Transform target = jointManager.HandJoints.FirstOrDefault(x => x.name == child.name);
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
int index = Array.IndexOf(jointManager.HandJoints, target);
|
||||||
|
|
||||||
|
GrabCollider grabCollider = child.AddComponent<GrabCollider>();
|
||||||
|
grabCollider.AddListener(CollisionEvent);
|
||||||
|
grabCollider.SetJointId(index);
|
||||||
|
jointsCollider[index] = grabCollider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < jointsCollider.Length; i++)
|
||||||
|
{
|
||||||
|
if (jointsCollider[i] == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = i + 1; j < jointsCollider.Length; j++)
|
||||||
|
{
|
||||||
|
if (jointsCollider[j] != null)
|
||||||
|
{
|
||||||
|
Physics.IgnoreCollision(jointsCollider[i].Collider, jointsCollider[j].Collider, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetChildren(Transform parent, List<GameObject> children)
|
||||||
|
{
|
||||||
|
foreach (Transform child in parent)
|
||||||
|
{
|
||||||
|
children.Add(child.gameObject);
|
||||||
|
GetChildren(child, children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the position of the collider using the position of the joint.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateColliderPose()
|
||||||
|
{
|
||||||
|
HandData hand = CachedHand.Get(jointManager.IsLeft);
|
||||||
|
bool isTracked = hand.isTracked;
|
||||||
|
if (!isTracked)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentTransform = jointManager.HandRootJoint.parent;
|
||||||
|
var parentRotation = Matrix4x4.Rotate(parentTransform.rotation);
|
||||||
|
Vector3 jointPosition = Vector3.zero;
|
||||||
|
Quaternion jointRotation = Quaternion.identity;
|
||||||
|
for (int i = 0; i < jointsCollider.Length; i++)
|
||||||
|
{
|
||||||
|
if (jointsCollider[i] == null) { continue; }
|
||||||
|
|
||||||
|
hand.GetJointPosition((JointType)i, ref jointPosition);
|
||||||
|
hand.GetJointRotation((JointType)i, ref jointRotation);
|
||||||
|
|
||||||
|
if ((JointType)i == JointType.Wrist)
|
||||||
|
{
|
||||||
|
jointsCollider[i].transform.localPosition = jointPosition;
|
||||||
|
jointsCollider[i].transform.localRotation = jointRotation;
|
||||||
|
}
|
||||||
|
jointsCollider[i].transform.rotation = (parentRotation * Matrix4x4.Rotate(jointRotation)).rotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the hand pose if a collision has already occurred with a joint.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateImmovable()
|
||||||
|
{
|
||||||
|
bool isCollision = jointsCollider.Any(x => x != null && x.IsCollision);
|
||||||
|
foreach (var jointCollider in jointsCollider)
|
||||||
|
{
|
||||||
|
jointCollider.Collider.enabled = isCollision ? jointCollider.IsCollision : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isImmovableCollision != isCollision)
|
||||||
|
{
|
||||||
|
isImmovableCollision = isCollision;
|
||||||
|
immovableCollisionHandler?.Invoke(isImmovableCollision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check all movableHits and move the object relative to the movement of the collisioned joint.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateMovable()
|
||||||
|
{
|
||||||
|
if (isImmovableCollision) { return; }
|
||||||
|
|
||||||
|
const int k_MinCollisionTimeDiff = 5;
|
||||||
|
const int k_MaxCollisionTimeDiff = 50;
|
||||||
|
|
||||||
|
for (int i = movableHits.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
MovableHitInfo hit = movableHits[i];
|
||||||
|
if (hit.stopMove) { continue; }
|
||||||
|
|
||||||
|
Vector3 totalPosition = Vector3.zero;
|
||||||
|
Vector3 totalOffset = Vector3.zero;
|
||||||
|
int validCount = 0;
|
||||||
|
for (int j = hit.jointHitInfos.Count - 1; j >= 0; j--)
|
||||||
|
{
|
||||||
|
MovableHitInfo.JointHitInfo jointHit = hit.jointHitInfos[j];
|
||||||
|
int frameCountDiff = Time.frameCount - jointHit.hitTime;
|
||||||
|
if (frameCountDiff > k_MinCollisionTimeDiff)
|
||||||
|
{
|
||||||
|
if (frameCountDiff > k_MaxCollisionTimeDiff)
|
||||||
|
{
|
||||||
|
hit.RemoveJoint(jointHit.joint);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int jointId = (int)jointHit.joint;
|
||||||
|
Vector3 currentPose = jointsCollider[jointId].transform.position;
|
||||||
|
Vector3 prevPose = jointsPrevFramePose[jointId].position;
|
||||||
|
|
||||||
|
// Condition 1: Calculate the displacement between consecutive frames of joints, it should greater than 1E-6f as significant.
|
||||||
|
// Condition 2: Calculate distance score relative to grabbable; the score of current pose should be greater than the previous pose.
|
||||||
|
// Condition 3: The dot product of the vector between the current pose and the grabbable object,
|
||||||
|
// and the vector representing finger movement direction should be less than 0.
|
||||||
|
if (Vector3.Distance(prevPose, currentPose) > 1E-6f &&
|
||||||
|
movableHits[i].grabbable.CalculateDistanceScore(currentPose) >= movableHits[i].grabbable.CalculateDistanceScore(prevPose) &&
|
||||||
|
Vector3.Dot((currentPose - prevPose).normalized, (movableHits[i].grabbable.transform.position - prevPose).normalized) > 0)
|
||||||
|
{
|
||||||
|
validCount++;
|
||||||
|
totalPosition += currentPose;
|
||||||
|
totalOffset += jointHit.hitOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validCount > 0)
|
||||||
|
{
|
||||||
|
movableHits[i].grabbable.transform.position = (totalPosition - totalOffset) / validCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hit.jointHitInfos.Count == 0)
|
||||||
|
{
|
||||||
|
movableHits.RemoveAt(i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
movableHits[i] = hit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable the collider of joints.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enable">Enable (true) or disable (false) the colliders.</param>
|
||||||
|
public void EnableCollider(bool enable)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < jointsCollider.Length; i++)
|
||||||
|
{
|
||||||
|
if (jointsCollider[i] != null)
|
||||||
|
{
|
||||||
|
jointsCollider[i].gameObject.SetActive(enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Collision Event
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a listener for immovable collision events.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be called when an immovable collision occurs.</param>
|
||||||
|
public void AddImmovableCollisionListener(OnImmovableCollision handler)
|
||||||
|
{
|
||||||
|
immovableCollisionHandler += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a listener for immovable collision events.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be removed from the immovable collision event listeners.</param>
|
||||||
|
public void RemoveImmovableCollisionListener(OnImmovableCollision handler)
|
||||||
|
{
|
||||||
|
immovableCollisionHandler -= handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event handler for when the grabber begins grabbing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabber">The grabber of IGrabber.</param>
|
||||||
|
private void OnGrabberBeginGrab(IGrabber grabber)
|
||||||
|
{
|
||||||
|
isGrabbing = true;
|
||||||
|
for (int i = 0; i < movableHits.Count; i++)
|
||||||
|
{
|
||||||
|
if (grabber.grabbable is HandGrabInteractable &&
|
||||||
|
(HandGrabInteractable)grabber.grabbable == movableHits[i].grabbable)
|
||||||
|
{
|
||||||
|
MovableHitInfo movableHit = movableHits[i];
|
||||||
|
movableHit.Update(true, true);
|
||||||
|
movableHits[i] = movableHit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGrabberEndGrab(IGrabber grabber)
|
||||||
|
{
|
||||||
|
isGrabbing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filter all collision events, check for grabbables, and update collision data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="joint">The joint which has been collision.</param>
|
||||||
|
/// <param name="collision">The data of Collision.</param>
|
||||||
|
/// <param name="isColliding">True when the collision event is OnCollisionEnter or OnCollisionStay.</param>
|
||||||
|
private void CollisionEvent(JointType joint, Collision collision, GrabCollider.CollisionState state)
|
||||||
|
{
|
||||||
|
bool isCollision = state != GrabCollider.CollisionState.end;
|
||||||
|
Rigidbody rigidbody = collision.rigidbody;
|
||||||
|
GrabManager.GetFirstHandGrabbableFromParent(collision.collider.gameObject, out HandGrabInteractable grabbable);
|
||||||
|
if (collision.rigidbody == null && (grabbable == null || grabbable != null && !grabbable.enabled)) { return; }
|
||||||
|
|
||||||
|
if ((rigidbody == null || rigidbody.isKinematic) && grabbable != null && grabbable.forceMovable)
|
||||||
|
{
|
||||||
|
if (isCollision)
|
||||||
|
{
|
||||||
|
UpdateMovableHits(joint, grabbable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemoveMovableHits(joint, grabbable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((rigidbody != null && rigidbody.isKinematic) || (grabbable != null && !grabbable.forceMovable))
|
||||||
|
{
|
||||||
|
UpdateImmovableHIts(joint, collision.collider, isCollision);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMovableHits(JointType joint, HandGrabInteractable grabbable)
|
||||||
|
{
|
||||||
|
int index = movableHits.FindIndex(x => x.grabbable == grabbable);
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
MovableHitInfo moveable = movableHits[index];
|
||||||
|
moveable.AddJoint(joint, jointsCollider[(int)joint].transform.position - grabbable.transform.position);
|
||||||
|
movableHits[index] = moveable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MovableHitInfo moveable = new MovableHitInfo(grabbable, joint, jointsCollider[(int)joint].transform.position - grabbable.transform.position);
|
||||||
|
movableHits.Add(moveable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveMovableHits(JointType joint, HandGrabInteractable grabbable)
|
||||||
|
{
|
||||||
|
int index = movableHits.FindIndex(x => x.grabbable == grabbable);
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
MovableHitInfo movable = movableHits[index];
|
||||||
|
movable.RemoveJoint(joint);
|
||||||
|
if (movable.jointHitInfos.Count == 0)
|
||||||
|
{
|
||||||
|
movableHits.Remove(movable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
movableHits[index] = movable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateImmovableHIts(JointType joint, Collider collider, bool isCollision)
|
||||||
|
{
|
||||||
|
GrabCollider grabCollider = jointsCollider[(int)joint];
|
||||||
|
grabCollider.IsCollision = isCollision;
|
||||||
|
|
||||||
|
if (isCollision && !immovableHits.ContainsKey(grabCollider))
|
||||||
|
{
|
||||||
|
immovableHits.Add(grabCollider, collider);
|
||||||
|
}
|
||||||
|
else if (!isCollision && immovableHits.ContainsKey(grabCollider))
|
||||||
|
{
|
||||||
|
immovableHits.Remove(grabCollider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b9b8d210c92da6a49ac85755f7b15cbb
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is designed to manage all Grabbers and Grabbables.
|
||||||
|
/// </summary>
|
||||||
|
public static class GrabManager
|
||||||
|
{
|
||||||
|
private static List<IGrabber> m_GrabberRegistry = new List<IGrabber>();
|
||||||
|
public static IReadOnlyList<HandGrabInteractor> handGrabbers => m_GrabberRegistry.OfType<HandGrabInteractor>().ToList().AsReadOnly();
|
||||||
|
|
||||||
|
private static List<IGrabbable> m_GrabbableRegistry = new List<IGrabbable>();
|
||||||
|
public static IReadOnlyList<HandGrabInteractable> handGrabbables => m_GrabbableRegistry.OfType<HandGrabInteractable>().ToList().AsReadOnly();
|
||||||
|
|
||||||
|
#region IGrabber
|
||||||
|
/// <summary>
|
||||||
|
/// Register the grabber in the grabber registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabber">The grabber to register.</param>
|
||||||
|
/// <returns>True if the grabber is successfully registered; otherwise, false.</returns>
|
||||||
|
public static bool RegisterGrabber(IGrabber grabber)
|
||||||
|
{
|
||||||
|
if (!m_GrabberRegistry.Contains(grabber))
|
||||||
|
{
|
||||||
|
m_GrabberRegistry.Add(grabber);
|
||||||
|
}
|
||||||
|
return m_GrabberRegistry.Contains(grabber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove the grabber from the grabber registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabber">The grabber to remove.</param>
|
||||||
|
/// <returns>True if the grabber is successfully removed; otherwise, false.</returns>
|
||||||
|
public static bool UnregisterGrabber(IGrabber grabber)
|
||||||
|
{
|
||||||
|
if (m_GrabberRegistry.Contains(grabber))
|
||||||
|
{
|
||||||
|
m_GrabberRegistry.Remove(grabber);
|
||||||
|
}
|
||||||
|
return !m_GrabberRegistry.Contains(grabber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the first hand grabber component found in the child hierarchy of the GameObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target whose child hierarchy to search.</param>
|
||||||
|
/// <param name="grabber">The output parameter to store the first hand grabber component found.</param>
|
||||||
|
/// <returns>True if a hand grabber component is found; otherwise, false.</returns>
|
||||||
|
public static bool GetFirstHandGrabberFromChild(GameObject target, out HandGrabInteractor grabber)
|
||||||
|
{
|
||||||
|
grabber = TopDownFind<HandGrabInteractor>(target.transform);
|
||||||
|
return grabber != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the first hand grabber component found in the parent hierarchy of the GameObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target whose parent hierarchy to search.</param>
|
||||||
|
/// <param name="grabber">The output parameter to store the first hand grabber component found.</param>
|
||||||
|
/// <returns>True if a hand grabber component is found; otherwise, false.</returns>
|
||||||
|
public static bool GetFirstHandGrabberFromParent(GameObject target, out HandGrabInteractor grabber)
|
||||||
|
{
|
||||||
|
grabber = BottomUpFind<HandGrabInteractor>(target.transform);
|
||||||
|
return grabber != null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region GrabInteractable
|
||||||
|
/// <summary>
|
||||||
|
/// Register the grabbable in the grabbable registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabbable">The grabbable to register.</param>
|
||||||
|
/// <returns>True if the grabbable is successfully registered; otherwise, false.</returns>
|
||||||
|
public static bool RegisterGrabbable(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
if (!m_GrabbableRegistry.Contains(grabbable))
|
||||||
|
{
|
||||||
|
m_GrabbableRegistry.Add(grabbable);
|
||||||
|
}
|
||||||
|
return m_GrabbableRegistry.Contains(grabbable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove the grabbable from the grabbable registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabbable">The grabbable to remove.</param>
|
||||||
|
/// <returns>True if the grabbable is successfully removed; otherwise, false.</returns>
|
||||||
|
public static bool UnregisterGrabbable(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
if (m_GrabbableRegistry.Contains(grabbable))
|
||||||
|
{
|
||||||
|
m_GrabbableRegistry.Remove(grabbable);
|
||||||
|
}
|
||||||
|
return !m_GrabbableRegistry.Contains(grabbable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the first hand grabbable component found in the child hierarchy of the GameObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target whose child hierarchy to search.</param>
|
||||||
|
/// <param name="grabbable">The output parameter to store the first hand grabbable component found.</param>
|
||||||
|
/// <returns>True if a hand grabbable component is found; otherwise, false.</returns>
|
||||||
|
public static bool GetFirstHandGrabbableFromChild(GameObject target, out HandGrabInteractable grabbable)
|
||||||
|
{
|
||||||
|
grabbable = TopDownFind<HandGrabInteractable>(target.transform);
|
||||||
|
return grabbable != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the first hand grabbable component found in the parent hierarchy of the GameObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target whose parent hierarchy to search.</param>
|
||||||
|
/// <param name="grabbable">The output parameter to store the first hand grabbable component found.</param>
|
||||||
|
/// <returns>True if a hand grabbable component is found; otherwise, false.</returns>
|
||||||
|
public static bool GetFirstHandGrabbableFromParent(GameObject target, out HandGrabInteractable grabbable)
|
||||||
|
{
|
||||||
|
grabbable = BottomUpFind<HandGrabInteractable>(target.transform);
|
||||||
|
return grabbable != null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find available components from self to children nodes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="transform">The transform of the gameobject.</param>
|
||||||
|
/// <returns>Value for available component.</returns>
|
||||||
|
private static T TopDownFind<T>(Transform transform) where T : Component
|
||||||
|
{
|
||||||
|
T component = transform.GetComponent<T>();
|
||||||
|
if (component != null)
|
||||||
|
{
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transform.childCount > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < transform.childCount; i++)
|
||||||
|
{
|
||||||
|
T childComponent = TopDownFind<T>(transform.GetChild(i));
|
||||||
|
if (childComponent != null)
|
||||||
|
{
|
||||||
|
return childComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find available components from self to parent node.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="transform">The transform of the gameobject.</param>
|
||||||
|
/// <returns>Value for available component.</returns>
|
||||||
|
private static T BottomUpFind<T>(Transform transform) where T : Component
|
||||||
|
{
|
||||||
|
T component = transform.GetComponent<T>();
|
||||||
|
if (component != null)
|
||||||
|
{
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transform.parent != null)
|
||||||
|
{
|
||||||
|
return BottomUpFind<T>(transform.parent);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 252950eac28fb1f4cb1eae8e653f92ce
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the WaveVR SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
/// <summary>
|
||||||
|
/// The class is designed to record all grab poses for all hand Grabbables.
|
||||||
|
/// </summary>
|
||||||
|
public class GrabPoseBinder : ScriptableObject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This struct records the grab pose for grabbable object.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
private struct GrabPoseBindFormat
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
public string grabbableName;
|
||||||
|
[SerializeField]
|
||||||
|
public List<GrabPose> grabPoses;
|
||||||
|
|
||||||
|
public GrabPoseBindFormat(string in_GrabbableName, List<GrabPose> in_GrabPoses)
|
||||||
|
{
|
||||||
|
grabbableName = in_GrabbableName;
|
||||||
|
grabPoses = in_GrabPoses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GrabPoseBindFormat Identity => new GrabPoseBindFormat(string.Empty, new List<GrabPose>());
|
||||||
|
|
||||||
|
public void Update(List<GrabPose> grabPoses)
|
||||||
|
{
|
||||||
|
this.grabPoses.Clear();
|
||||||
|
this.grabPoses.AddRange(grabPoses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
grabbableName = string.Empty;
|
||||||
|
grabPoses.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is GrabPoseBindFormat grabPoseBindFormat &&
|
||||||
|
grabbableName == grabPoseBindFormat.grabbableName &&
|
||||||
|
grabPoses == grabPoseBindFormat.grabPoses;
|
||||||
|
}
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return grabbableName.GetHashCode() ^ grabPoses.GetHashCode();
|
||||||
|
}
|
||||||
|
public static bool operator ==(GrabPoseBindFormat source, GrabPoseBindFormat target) => source.Equals(target);
|
||||||
|
public static bool operator !=(GrabPoseBindFormat source, GrabPoseBindFormat target) => !(source == target);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private List<GrabPoseBindFormat> m_BindingInfos = new List<GrabPoseBindFormat>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the binding information for each hand grabbable object.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateBindingInfos()
|
||||||
|
{
|
||||||
|
m_BindingInfos.Clear();
|
||||||
|
foreach (HandGrabInteractable grabbable in GrabManager.handGrabbables)
|
||||||
|
{
|
||||||
|
m_BindingInfos.Add(new GrabPoseBindFormat(grabbable.name, grabbable.grabPoses));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the binding information.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if storage is successful; otherwise, false.</returns>
|
||||||
|
public bool StorageData()
|
||||||
|
{
|
||||||
|
if (m_BindingInfos.Count == 0) { return false; }
|
||||||
|
|
||||||
|
EditorApplication.delayCall += () =>
|
||||||
|
{
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
EditorUtility.SetDirty(this);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds grab poses associated with the specified hand grabbable object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabbable">The hand grabbable object to search for.</param>
|
||||||
|
/// <param name="grabPoses">The output parameter to store the found grab poses.</param>
|
||||||
|
/// <returns>True if grab poses are found for the grabbable object; otherwise, false.</returns>
|
||||||
|
public bool FindGrabPosesWithGrabbable(HandGrabInteractable grabbable, out List<GrabPose> grabPoses)
|
||||||
|
{
|
||||||
|
grabPoses = new List<GrabPose>();
|
||||||
|
GrabPoseBindFormat bindingInfo = m_BindingInfos.Find(x => x.grabbableName == grabbable.name);
|
||||||
|
if (bindingInfo != null)
|
||||||
|
{
|
||||||
|
grabPoses = bindingInfo.grabPoses;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 53503199deedf444e84f1714b700737d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the WaveVR SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
/// <summary>
|
||||||
|
/// The class is designed to update the grab poses of all hand Grabbables.
|
||||||
|
/// </summary>
|
||||||
|
[InitializeOnLoad]
|
||||||
|
public class GrabbablePoseRecorder
|
||||||
|
{
|
||||||
|
private static readonly string filepath = "Assets/GrabablePoseRecording.asset";
|
||||||
|
private static readonly string metaFilepath = filepath + ".meta";
|
||||||
|
private static bool IsFileExist => File.Exists(filepath);
|
||||||
|
|
||||||
|
static GrabbablePoseRecorder()
|
||||||
|
{
|
||||||
|
EditorApplication.playModeStateChanged += ApplyChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply changes to grab poses when entering edit mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">The state of the play mode.</param>
|
||||||
|
private static void ApplyChanges(PlayModeStateChange state)
|
||||||
|
{
|
||||||
|
if (IsFileExist && state == PlayModeStateChange.EnteredEditMode)
|
||||||
|
{
|
||||||
|
GrabPoseBinder binder = AssetDatabase.LoadAssetAtPath<GrabPoseBinder>(filepath);
|
||||||
|
if (binder != null)
|
||||||
|
{
|
||||||
|
HandGrabInteractable[] grabbables = Object.FindObjectsOfType<HandGrabInteractable>();
|
||||||
|
foreach (var grabbable in grabbables)
|
||||||
|
{
|
||||||
|
if (binder.FindGrabPosesWithGrabbable(grabbable, out List<GrabPose> updatedGrabPose))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < updatedGrabPose.Count; i++)
|
||||||
|
{
|
||||||
|
GrabPose grabPose = updatedGrabPose[i];
|
||||||
|
GrabPose oldGrabPose = grabbable.grabPoses.Find(x => x.grabPoseName == grabPose.grabPoseName);
|
||||||
|
if (oldGrabPose != null)
|
||||||
|
{
|
||||||
|
grabPose.indicator.target = oldGrabPose.indicator.target;
|
||||||
|
}
|
||||||
|
updatedGrabPose[i] = grabPose;
|
||||||
|
}
|
||||||
|
grabbable.grabPoses.Clear();
|
||||||
|
grabbable.grabPoses.AddRange(updatedGrabPose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File.Delete(filepath);
|
||||||
|
File.Delete(metaFilepath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves changes to grab pose bindings.
|
||||||
|
/// </summary>
|
||||||
|
public static void SaveChanges()
|
||||||
|
{
|
||||||
|
if (!IsFileExist)
|
||||||
|
{
|
||||||
|
GenerateAsset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GrabPoseBinder binder = AssetDatabase.LoadAssetAtPath<GrabPoseBinder>(filepath);
|
||||||
|
if (binder != null)
|
||||||
|
{
|
||||||
|
binder.UpdateBindingInfos();
|
||||||
|
binder.StorageData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a new asset for storing grab pose bindings.
|
||||||
|
/// </summary>
|
||||||
|
private static void GenerateAsset()
|
||||||
|
{
|
||||||
|
GrabPoseBinder binder = ScriptableObject.CreateInstance<GrabPoseBinder>();
|
||||||
|
binder.UpdateBindingInfos();
|
||||||
|
AssetDatabase.CreateAsset(binder, filepath);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9119682127e09314ca250470f13db0f2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is designed to implement IHandGrabbable, allowing objects to be grabbed.
|
||||||
|
/// </summary>
|
||||||
|
public class HandGrabInteractable : MonoBehaviour, IHandGrabbable
|
||||||
|
{
|
||||||
|
#region Interface Implement
|
||||||
|
private HandGrabInteractor m_Grabber = null;
|
||||||
|
public IGrabber grabber => m_Grabber;
|
||||||
|
|
||||||
|
public bool isGrabbed => m_Grabber != null;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private bool m_IsGrabbable = true;
|
||||||
|
public bool isGrabbable { get { return m_IsGrabbable; } set { m_IsGrabbable = value; } }
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private bool m_ForceMovable = true;
|
||||||
|
public bool forceMovable { get { return m_ForceMovable; } set { m_ForceMovable = value; } }
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private FingerRequirement m_FingerRequirement;
|
||||||
|
public FingerRequirement fingerRequirement => m_FingerRequirement;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public State
|
||||||
|
[SerializeField]
|
||||||
|
private List<GrabPose> m_GrabPoses = new List<GrabPose>();
|
||||||
|
public List<GrabPose> grabPoses => m_GrabPoses;
|
||||||
|
|
||||||
|
public GrabPose bestGrabPose => bestGrabPoseId != -1 ? m_GrabPoses[bestGrabPoseId] : GrabPose.Identity;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private bool m_ShowAllIndicator = false;
|
||||||
|
private List<Collider> allColliders = new List<Collider>();
|
||||||
|
private HandGrabInteractor closestGrabber = null;
|
||||||
|
private int bestGrabPoseId = -1;
|
||||||
|
private OnBeginGrabbed onBeginGrabbed;
|
||||||
|
private OnEndGrabbed onEndGrabbed;
|
||||||
|
|
||||||
|
#region MonoBehaviour
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
allColliders.AddRange(transform.GetComponentsInChildren<Collider>(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
GrabManager.RegisterGrabbable(this);
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
GrabManager.UnregisterGrabbable(this);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Interface
|
||||||
|
/// <summary>
|
||||||
|
/// Set the grabber for the hand grabbable object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabber">The grabber to set.</param>
|
||||||
|
public void SetGrabber(IGrabber grabber)
|
||||||
|
{
|
||||||
|
if (grabber is HandGrabInteractor)
|
||||||
|
{
|
||||||
|
HandGrabInteractor handGrabber = grabber as HandGrabInteractor;
|
||||||
|
m_Grabber = handGrabber;
|
||||||
|
UpdateBestGrabPose(handGrabber.isLeft, handGrabber.handGrabState.GetJointPose(JointType.Palm));
|
||||||
|
onBeginGrabbed?.Invoke(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Grabber = null;
|
||||||
|
bestGrabPoseId = -1;
|
||||||
|
onEndGrabbed?.Invoke(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable/Disable indicators. If enabled, display the closest indicator based on grabber position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enable">True to show the indicator, false to hide it.</param>
|
||||||
|
/// <param name="grabber">The grabber for which to show or hide this indicator.</param>
|
||||||
|
public void ShowIndicator(bool enable, HandGrabInteractor grabber)
|
||||||
|
{
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
closestGrabber = grabber;
|
||||||
|
if (m_ShowAllIndicator)
|
||||||
|
{
|
||||||
|
ShowAllIndicator(grabber.isLeft);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int index = FindBestGrabPose(grabber.isLeft, grabber.handGrabState.GetJointPose((int)JointType.Palm));
|
||||||
|
ShowIndicatorByIndex(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (closestGrabber == grabber)
|
||||||
|
{
|
||||||
|
closestGrabber = null;
|
||||||
|
ShowIndicatorByIndex(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the shortest distance between the grabber and the grabbable and convert it into a score based on grabDistance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabberPos">The current pose of grabber.</param>
|
||||||
|
/// <param name="grabDistance">The maximum grab distance between the grabber and the grabbable object.</param>
|
||||||
|
/// <returns>The score represents the distance between the grabber and the grabbable.</returns>
|
||||||
|
public float CalculateDistanceScore(Vector3 grabberPos, float grabDistance = 0.03f)
|
||||||
|
{
|
||||||
|
if (!isGrabbable || isGrabbed) { return 0; }
|
||||||
|
Vector3 closestPoint = GetClosestPoint(grabberPos);
|
||||||
|
float distacne = Vector3.Distance(grabberPos, closestPoint);
|
||||||
|
return distacne > grabDistance ? 0 : 1 - (distacne / grabDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a listener for the event triggered when the grabbable object is grabbed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be called when the grabbable object is grabbed.</param>
|
||||||
|
public void AddBeginGrabbedListener(OnBeginGrabbed handler)
|
||||||
|
{
|
||||||
|
onBeginGrabbed += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a listener for the event triggered when the grabbable object is grabbed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||||
|
public void RemoveBeginGrabbedListener(OnBeginGrabbed handler)
|
||||||
|
{
|
||||||
|
onBeginGrabbed -= handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a listener for the event triggered when the grabbable object is released.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be called when the grabbable object is released.</param>
|
||||||
|
public void AddEndGrabbedListener(OnEndGrabbed handler)
|
||||||
|
{
|
||||||
|
onEndGrabbed += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a listener for the event triggered when the grabbable object is released.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||||
|
public void RemoveEndGrabbedListener(OnEndGrabbed handler)
|
||||||
|
{
|
||||||
|
onEndGrabbed -= handler;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate all indicators and calculate grab offsets.
|
||||||
|
/// </summary>
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_GrabPoses.Count; i++)
|
||||||
|
{
|
||||||
|
if (m_GrabPoses[i].indicator.enableIndicator || m_ShowAllIndicator)
|
||||||
|
{
|
||||||
|
if (m_GrabPoses[i].indicator.NeedGenerateIndicator())
|
||||||
|
{
|
||||||
|
AutoGenerateIndicator(i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GrabPose grabPose = m_GrabPoses[i];
|
||||||
|
grabPose.indicator.CalculateGrabOffset(transform);
|
||||||
|
m_GrabPoses[i] = grabPose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShowIndicatorByIndex(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Automatically generate an indicator by the index of the grab pose.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the grab pose.</param>
|
||||||
|
private void AutoGenerateIndicator(int index)
|
||||||
|
{
|
||||||
|
AutoGenIndicator autoGenIndicator = new GameObject($"Indicator {index}", typeof(AutoGenIndicator)).GetComponent<AutoGenIndicator>();
|
||||||
|
|
||||||
|
GrabPose grabPose = m_GrabPoses[index];
|
||||||
|
// The grabPose.grabOffset was calculated as the position of the object minus the position of the hand,
|
||||||
|
// so inverse calculation is needed here to infer the position of the hand.
|
||||||
|
Vector3 offset = transform.rotation * Quaternion.Inverse(grabPose.grabOffset.targetRotation) * -grabPose.grabOffset.position;
|
||||||
|
Vector3 defaultPosition = transform.position + offset;
|
||||||
|
Vector3 closestPoint = GetClosestPoint(defaultPosition);
|
||||||
|
autoGenIndicator.SetPose(closestPoint, offset.normalized);
|
||||||
|
grabPose.indicator.Update(true, true, autoGenIndicator.gameObject);
|
||||||
|
grabPose.indicator.CalculateGrabOffset(transform);
|
||||||
|
m_GrabPoses[index] = grabPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the point closest to the source position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourcePos">The position of source.</param>
|
||||||
|
/// <returns>The position which closest to the source position.</returns>
|
||||||
|
private Vector3 GetClosestPoint(Vector3 sourcePos)
|
||||||
|
{
|
||||||
|
Vector3 closestPoint = Vector3.zero;
|
||||||
|
float shortDistance = float.MaxValue;
|
||||||
|
foreach (var collider in allColliders)
|
||||||
|
{
|
||||||
|
Vector3 closePoint = collider.ClosestPointOnBounds(sourcePos);
|
||||||
|
float distance = Vector3.Distance(sourcePos, closePoint);
|
||||||
|
if (collider.bounds.Contains(closePoint))
|
||||||
|
{
|
||||||
|
Vector3 direction = (closePoint - sourcePos).normalized;
|
||||||
|
RaycastHit[] hits = Physics.RaycastAll(sourcePos, direction, distance);
|
||||||
|
foreach (var hit in hits)
|
||||||
|
{
|
||||||
|
if (hit.collider == collider)
|
||||||
|
{
|
||||||
|
float hitDistnace = Vector3.Distance(sourcePos, hit.point);
|
||||||
|
if (distance > hitDistnace)
|
||||||
|
{
|
||||||
|
distance = hitDistnace;
|
||||||
|
closePoint = hit.point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortDistance > distance)
|
||||||
|
{
|
||||||
|
shortDistance = distance;
|
||||||
|
closestPoint = closePoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the best grab pose for the grabber and updates the bestGrabPoseId.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isLeft">Whether the grabber is the left hand.</param>
|
||||||
|
/// <param name="grabberPose">The pose of the grabber.</param>
|
||||||
|
/// <returns>True if a best grab pose is found; otherwise, false.</returns>
|
||||||
|
private bool UpdateBestGrabPose(bool isLeft, Pose grabberPose)
|
||||||
|
{
|
||||||
|
int index = FindBestGrabPose(isLeft, grabberPose);
|
||||||
|
if (index != -1 && index < m_GrabPoses.Count)
|
||||||
|
{
|
||||||
|
bestGrabPoseId = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
index = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the best grab pose for the grabber.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isLeft">Whether the grabber is the left hand.</param>
|
||||||
|
/// <param name="grabberPose">The pose of the grabber.</param>
|
||||||
|
/// <returns>The index of the best grab pose among the grab poses.</returns>
|
||||||
|
private int FindBestGrabPose(bool isLeft, Pose grabberPose)
|
||||||
|
{
|
||||||
|
int index = -1;
|
||||||
|
float maxDot = float.MinValue;
|
||||||
|
Vector3 currentDirection = grabberPose.position - transform.position;
|
||||||
|
for (int i = 0; i < m_GrabPoses.Count; i++)
|
||||||
|
{
|
||||||
|
if (m_GrabPoses[i].isLeft == isLeft)
|
||||||
|
{
|
||||||
|
Vector3 grabDirection = transform.rotation * Quaternion.Inverse(m_GrabPoses[i].grabOffset.targetRotation) * -m_GrabPoses[i].grabOffset.position;
|
||||||
|
float dot = Vector3.Dot(currentDirection.normalized, grabDirection.normalized);
|
||||||
|
if (dot > maxDot)
|
||||||
|
{
|
||||||
|
maxDot = dot;
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show the indicator corresponding to the specified index and hides others.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the indicator to show.</param>
|
||||||
|
private void ShowIndicatorByIndex(int index)
|
||||||
|
{
|
||||||
|
foreach (var grabPose in m_GrabPoses)
|
||||||
|
{
|
||||||
|
grabPose.indicator.SetActive(false);
|
||||||
|
}
|
||||||
|
if (index >= 0 && index < m_GrabPoses.Count &&
|
||||||
|
m_GrabPoses[index].indicator.enableIndicator)
|
||||||
|
{
|
||||||
|
m_GrabPoses[index].indicator.UpdatePositionAndRotation(transform);
|
||||||
|
m_GrabPoses[index].indicator.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show all indicators corresponding to the specified hand side and hides others.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isLeft">Whether the hand side is left.</param>
|
||||||
|
private void ShowAllIndicator(bool isLeft)
|
||||||
|
{
|
||||||
|
foreach (var grabPose in m_GrabPoses)
|
||||||
|
{
|
||||||
|
grabPose.indicator.SetActive(false);
|
||||||
|
}
|
||||||
|
foreach (var grabPose in m_GrabPoses)
|
||||||
|
{
|
||||||
|
if (grabPose.isLeft == isLeft)
|
||||||
|
{
|
||||||
|
grabPose.indicator.UpdatePositionAndRotation(transform);
|
||||||
|
grabPose.indicator.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b1c0e40da1ab9014c89d359be00fffb1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is designed to implement IHandGrabber, allowing objects to grab grabbable objects.
|
||||||
|
/// </summary>
|
||||||
|
public class HandGrabInteractor : MonoBehaviour, IHandGrabber
|
||||||
|
{
|
||||||
|
private enum GrabState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Hover,
|
||||||
|
Grabbing,
|
||||||
|
};
|
||||||
|
|
||||||
|
#region Public States
|
||||||
|
private HandGrabInteractable m_Grabbable = null;
|
||||||
|
public IGrabbable grabbable => m_Grabbable;
|
||||||
|
public bool isGrabbing => m_Grabbable != null;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private Handedness m_Handedness = Handedness.Left;
|
||||||
|
public Handedness handedness => m_Handedness;
|
||||||
|
|
||||||
|
private HandGrabState m_HandGrabState = null;
|
||||||
|
public HandGrabState handGrabState => m_HandGrabState;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private float m_GrabDistance = 0.03f;
|
||||||
|
public float grabDistance { get { return m_GrabDistance; } set { m_GrabDistance = value; } }
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private bool m_EnableCollider = true;
|
||||||
|
public bool enableCollider
|
||||||
|
{
|
||||||
|
get { return m_EnableCollider; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_EnableCollider = value;
|
||||||
|
if (colliderManager != null)
|
||||||
|
{
|
||||||
|
colliderManager.EnableCollider(m_EnableCollider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool isLeft => handedness == Handedness.Left;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private GrabColliderManager colliderManager;
|
||||||
|
|
||||||
|
private readonly float MinGrabScore = 0.25f;
|
||||||
|
private readonly float MinDistanceScore = 0.25f;
|
||||||
|
private HandGrabInteractable currentCaidate = null;
|
||||||
|
private GrabPose grabPose = GrabPose.Identity;
|
||||||
|
private GrabState m_State = GrabState.None;
|
||||||
|
private Pose[] fingerTipPoses => new Pose[]
|
||||||
|
{
|
||||||
|
m_HandGrabState.GetJointPose(JointType.Thumb_Tip),
|
||||||
|
m_HandGrabState.GetJointPose(JointType.Index_Tip),
|
||||||
|
m_HandGrabState.GetJointPose(JointType.Middle_Tip),
|
||||||
|
m_HandGrabState.GetJointPose(JointType.Ring_Tip),
|
||||||
|
m_HandGrabState.GetJointPose(JointType.Pinky_Tip),
|
||||||
|
};
|
||||||
|
private Pose palmPose => m_HandGrabState.GetJointPose(JointType.Palm);
|
||||||
|
private Pose[] frozenPoses = new Pose[(int)JointType.Count];
|
||||||
|
private bool isFrozen = false;
|
||||||
|
private OnBeginGrab beginGrabHandler;
|
||||||
|
private OnEndGrab endGrabHandler;
|
||||||
|
|
||||||
|
#region MonoBehaviour
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
m_HandGrabState = new HandGrabState(isLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (colliderManager != null)
|
||||||
|
{
|
||||||
|
colliderManager.EnableCollider(m_EnableCollider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
GrabManager.RegisterGrabber(this);
|
||||||
|
if (colliderManager != null)
|
||||||
|
{
|
||||||
|
colliderManager.AddImmovableCollisionListener(FreezeHandPose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
GrabManager.UnregisterGrabber(this);
|
||||||
|
if (colliderManager != null)
|
||||||
|
{
|
||||||
|
colliderManager.RemoveImmovableCollisionListener(FreezeHandPose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
m_HandGrabState.UpdateState();
|
||||||
|
if (isFrozen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_State != GrabState.Grabbing)
|
||||||
|
{
|
||||||
|
FindCandidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (m_State)
|
||||||
|
{
|
||||||
|
case GrabState.None:
|
||||||
|
NoneUpdate();
|
||||||
|
break;
|
||||||
|
case GrabState.Hover:
|
||||||
|
HoverUpdate();
|
||||||
|
break;
|
||||||
|
case GrabState.Grabbing:
|
||||||
|
GrabbingUpdate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Interface
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current joint pose of the grabber.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jointId">The id of the joint for which to get the pose.</param>
|
||||||
|
/// <returns>The current pose of the specified joint.</returns>
|
||||||
|
public Pose GetCurrentJointPose(int jointId)
|
||||||
|
{
|
||||||
|
if (isFrozen)
|
||||||
|
{
|
||||||
|
return frozenPoses[jointId];
|
||||||
|
}
|
||||||
|
return m_HandGrabState.GetJointPose(jointId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the rotation of the joint in the grab pose.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jointId">The id of the joint for which to get the rotation.</param>
|
||||||
|
/// <returns>The rotation of the joint in the grab pose.</returns>
|
||||||
|
public bool GetGrabPoseJointRotation(int jointId, out Quaternion rotation)
|
||||||
|
{
|
||||||
|
rotation = Quaternion.identity;
|
||||||
|
if (m_Grabbable == null) { return false; }
|
||||||
|
if (jointId >= 0 && grabPose.recordedGrabRotations.Length > jointId)
|
||||||
|
{
|
||||||
|
rotation = grabPose.recordedGrabRotations[jointId];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (grabPose.handGrabGesture != HandGrabGesture.Identity)
|
||||||
|
{
|
||||||
|
rotation = m_HandGrabState.GetDefaultJointRotationInGesture(grabPose.handGrabGesture, jointId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the specific joint is necessary for grabbing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="joint">JointType of the specified joint.</param>
|
||||||
|
/// <returns>Return true if this joint is needed for grabbing, otherwise false.</returns>
|
||||||
|
public bool IsRequiredJoint(JointType joint)
|
||||||
|
{
|
||||||
|
if (m_Grabbable != null)
|
||||||
|
{
|
||||||
|
GetJointIndex(joint, out int group, out _);
|
||||||
|
switch (group)
|
||||||
|
{
|
||||||
|
case 2: return m_Grabbable.fingerRequirement.thumb == GrabRequirement.Required;
|
||||||
|
case 3: return m_Grabbable.fingerRequirement.index == GrabRequirement.Required;
|
||||||
|
case 4: return m_Grabbable.fingerRequirement.middle == GrabRequirement.Required;
|
||||||
|
case 5: return m_Grabbable.fingerRequirement.ring == GrabRequirement.Required;
|
||||||
|
case 6: return m_Grabbable.fingerRequirement.pinky == GrabRequirement.Required;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a listener for the event triggered when the grabber begins grabbing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be called when the grabber begins grabbing.</param>
|
||||||
|
public void AddBeginGrabListener(OnBeginGrab handler)
|
||||||
|
{
|
||||||
|
beginGrabHandler += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a listener for the event triggered when the grabber begins grabbing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||||
|
public void RemoveBeginGrabListener(OnBeginGrab handler)
|
||||||
|
{
|
||||||
|
beginGrabHandler -= handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a listener for the event triggered when the grabber ends grabbing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be called when the grabber ends grabbing.</param>
|
||||||
|
public void AddEndGrabListener(OnEndGrab handler)
|
||||||
|
{
|
||||||
|
endGrabHandler += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a listener for the event triggered when the grabber ends grabbing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||||
|
public void RemoveEndGrabListener(OnEndGrab handler)
|
||||||
|
{
|
||||||
|
endGrabHandler -= handler;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the candidate grabbable object for grabber.
|
||||||
|
/// </summary>
|
||||||
|
private void FindCandidate()
|
||||||
|
{
|
||||||
|
float distanceScore = float.MinValue;
|
||||||
|
if (GetClosestGrabbable(m_GrabDistance, out HandGrabInteractable grabbable, out float score) && score > distanceScore)
|
||||||
|
{
|
||||||
|
distanceScore = score;
|
||||||
|
currentCaidate = grabbable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentCaidate != null)
|
||||||
|
{
|
||||||
|
float grabScore = Grab.CalculateHandGrabScore(this, currentCaidate);
|
||||||
|
if (distanceScore < MinDistanceScore || grabScore < MinGrabScore)
|
||||||
|
{
|
||||||
|
currentCaidate = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the closest grabbable object for grabber.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabDistance">The maximum grab distance between the grabber and the grabbable object.</param>
|
||||||
|
/// <param name="grabbable">The closest grabbable object.</param>
|
||||||
|
/// <param name="maxScore">The maximum score indicating the closeness of the grabbable object.</param>
|
||||||
|
/// <returns>True if a grabbable object is found within the grab distance; otherwise, false.</returns>
|
||||||
|
private bool GetClosestGrabbable(float grabDistance, out HandGrabInteractable grabbable, out float maxScore)
|
||||||
|
{
|
||||||
|
grabbable = null;
|
||||||
|
maxScore = 0f;
|
||||||
|
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
||||||
|
{
|
||||||
|
interactable.ShowIndicator(false, this);
|
||||||
|
foreach (Pose fingerTipPose in fingerTipPoses)
|
||||||
|
{
|
||||||
|
float distanceScore = interactable.CalculateDistanceScore(fingerTipPose.position, grabDistance);
|
||||||
|
if (distanceScore > maxScore)
|
||||||
|
{
|
||||||
|
maxScore = distanceScore;
|
||||||
|
grabbable = interactable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (grabbable != null)
|
||||||
|
{
|
||||||
|
grabbable.ShowIndicator(true, this);
|
||||||
|
}
|
||||||
|
return grabbable != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the state to GrabState.Hover if a candidate is found.
|
||||||
|
/// </summary>
|
||||||
|
private void NoneUpdate()
|
||||||
|
{
|
||||||
|
if (currentCaidate != null)
|
||||||
|
{
|
||||||
|
m_State = GrabState.Hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the state and related information when the grabber begins grabbing the grabbable.
|
||||||
|
/// </summary>
|
||||||
|
private void HoverUpdate()
|
||||||
|
{
|
||||||
|
if (currentCaidate == null)
|
||||||
|
{
|
||||||
|
m_State = GrabState.None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Grab.HandBeginGrab(this, currentCaidate))
|
||||||
|
{
|
||||||
|
m_State = GrabState.Grabbing;
|
||||||
|
|
||||||
|
m_Grabbable = currentCaidate;
|
||||||
|
m_Grabbable.SetGrabber(this);
|
||||||
|
m_Grabbable.ShowIndicator(false, this);
|
||||||
|
grabPose = m_Grabbable.bestGrabPose;
|
||||||
|
if (grabPose == GrabPose.Identity)
|
||||||
|
{
|
||||||
|
Vector3 posOffset = m_Grabbable.transform.position - palmPose.position;
|
||||||
|
Quaternion rotOffset = palmPose.rotation;
|
||||||
|
grabPose.grabOffset = new GrabOffset(m_Grabbable.transform.position, m_Grabbable.transform.rotation, posOffset, rotOffset);
|
||||||
|
}
|
||||||
|
beginGrabHandler?.Invoke(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the position of grabbable object according to the movement of the grabber.
|
||||||
|
/// </summary>
|
||||||
|
private void GrabbingUpdate()
|
||||||
|
{
|
||||||
|
if (Grab.HandDoneGrab(this, m_Grabbable) || !Grab.HandIsGrabbing(this, m_Grabbable))
|
||||||
|
{
|
||||||
|
m_Grabbable.SetGrabber(null);
|
||||||
|
m_Grabbable = null;
|
||||||
|
m_State = GrabState.Hover;
|
||||||
|
endGrabHandler?.Invoke(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quaternion handRotOffset = palmPose.rotation * Quaternion.Inverse(grabPose.grabOffset.rotation);
|
||||||
|
Vector3 currentPos = palmPose.position + handRotOffset * grabPose.grabOffset.position;
|
||||||
|
Quaternion currentRot = handRotOffset * grabPose.grabOffset.targetRotation;
|
||||||
|
m_Grabbable.transform.SetPositionAndRotation(currentPos, currentRot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Freezes or unfreezes the hand pose.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enable">True to freeze the hand pose; False to unfreeze.</param>
|
||||||
|
private void FreezeHandPose(bool enable)
|
||||||
|
{
|
||||||
|
isFrozen = enable;
|
||||||
|
if (isFrozen)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < frozenPoses.Length; i++)
|
||||||
|
{
|
||||||
|
frozenPoses[i] = m_HandGrabState.GetJointPose(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the position of a specific joint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="joint">The type of joint to get.</param>
|
||||||
|
/// <param name="position">The reference to store the position of the joint.</param>
|
||||||
|
/// <returns>True if the joint position is successfully retrieved; otherwise, false.</returns>
|
||||||
|
private void GetJointIndex(JointType joint, out int group, out int index)
|
||||||
|
{
|
||||||
|
int jointId = (int)joint + 1;
|
||||||
|
group = 0;
|
||||||
|
index = jointId;
|
||||||
|
|
||||||
|
// palm, wrist, thumb, index, middle, ring, pinky
|
||||||
|
int[] fingerGroup = { 1, 1, 4, 5, 5, 5, 5 };
|
||||||
|
while (index > fingerGroup[group])
|
||||||
|
{
|
||||||
|
index -= fingerGroup[group];
|
||||||
|
group += 1;
|
||||||
|
}
|
||||||
|
index -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a3155365f073fdb45ba7a61887f8cf06
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 35c93bc2a4bb4334e9fc24c4dbac1b63
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is designed to manage the positions of various joint nodes in the hand model.
|
||||||
|
/// </summary>
|
||||||
|
public class HandMeshManager : MonoBehaviour
|
||||||
|
{
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager";
|
||||||
|
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||||
|
|
||||||
|
private enum RootType
|
||||||
|
{
|
||||||
|
Palm = JointType.Palm,
|
||||||
|
Wrist = JointType.Wrist,
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private Handedness m_Handedness;
|
||||||
|
public bool IsLeft { get { return m_Handedness == Handedness.Left; } }
|
||||||
|
[SerializeField]
|
||||||
|
private HandGrabInteractor m_HandGrabber;
|
||||||
|
public HandGrabInteractor HandGrabber { get { return m_HandGrabber; } }
|
||||||
|
[SerializeField]
|
||||||
|
private RootType m_RootJointType;
|
||||||
|
public JointType RootJointType { get { return (JointType)m_RootJointType; } }
|
||||||
|
[SerializeField]
|
||||||
|
private Transform m_HandRootJoint;
|
||||||
|
public Transform HandRootJoint { get { return m_HandRootJoint; } }
|
||||||
|
[SerializeField]
|
||||||
|
private Transform[] m_HandJoints = new Transform[(int)JointType.Count];
|
||||||
|
public Transform[] HandJoints { get { return m_HandJoints; } }
|
||||||
|
|
||||||
|
private const int k_JointCount = 26;
|
||||||
|
private const int k_JointChildCount = 6;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (m_HandGrabber == null)
|
||||||
|
{
|
||||||
|
WARNING("Not to set HandGrabInteractor so it won't stop when colliding with Immovable objects.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
HandData hand = CachedHand.Get(IsLeft);
|
||||||
|
if (!hand.isTracked)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentTransform = m_HandRootJoint.parent;
|
||||||
|
|
||||||
|
int rootId = m_RootJointType == RootType.Palm ? 0 : 1;
|
||||||
|
Pose jointPose = GetJointPose(hand, rootId);
|
||||||
|
m_HandJoints[rootId].rotation = parentTransform.rotation * jointPose.rotation;
|
||||||
|
m_HandJoints[rootId].localPosition = jointPose.position;
|
||||||
|
m_HandJoints[rootId].localRotation = jointPose.rotation;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_HandJoints.Length; i++)
|
||||||
|
{
|
||||||
|
if (m_HandJoints[i] == null || i == rootId) { continue; }
|
||||||
|
|
||||||
|
jointPose = GetJointPose(hand, i);
|
||||||
|
m_HandJoints[i].rotation = parentTransform.rotation * jointPose.rotation;
|
||||||
|
if (m_HandGrabber != null && m_HandGrabber.isGrabbing &&
|
||||||
|
m_HandGrabber.GetGrabPoseJointRotation(i, out Quaternion localStaticRot))
|
||||||
|
{
|
||||||
|
Quaternion currentRotation = m_HandJoints[i].rotation;
|
||||||
|
Quaternion maxRotation = m_HandJoints[i].parent.rotation * localStaticRot;
|
||||||
|
if (m_HandGrabber.IsRequiredJoint((JointType)i) ||
|
||||||
|
OverFlex(currentRotation, maxRotation) >= 0 ||
|
||||||
|
FlexAngle(currentRotation, maxRotation) >= 100)
|
||||||
|
{
|
||||||
|
m_HandJoints[i].rotation = maxRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate whether the current rotation exceeds the maximum rotation.
|
||||||
|
/// If the product is greater than 0, it exceeds.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentRot">Current rotation</param>
|
||||||
|
/// <param name="maxRot">Maximum rotation</param>
|
||||||
|
/// <returns>The return value represents the dot product between the cross product of two rotations and the -x axis direction of the current rotation.</returns>
|
||||||
|
private float OverFlex(Quaternion currentRot, Quaternion maxRot)
|
||||||
|
{
|
||||||
|
Vector3 currFwd = currentRot * Vector3.forward;
|
||||||
|
Vector3 maxFwd = maxRot * Vector3.forward;
|
||||||
|
return Vector3.Dot(currentRot * Vector3.left, Vector3.Cross(currFwd, maxFwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the angle between the y-axis directions of two rotations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentRot">Current rotation</param>
|
||||||
|
/// <param name="maxRot">Maximum rotation</param>
|
||||||
|
/// <returns>The return value represents the angle between the up directions of the two rotation</returns>
|
||||||
|
private float FlexAngle(Quaternion currentRot, Quaternion maxRot)
|
||||||
|
{
|
||||||
|
Vector3 currFwd = currentRot * Vector3.up;
|
||||||
|
Vector3 maxFwd = maxRot * Vector3.up;
|
||||||
|
return Mathf.Acos(Vector3.Dot(currFwd, maxFwd) / (currFwd.magnitude * maxFwd.magnitude)) * Mathf.Rad2Deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the pose of the joint based on the joint ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hand">The current result of hand tracking.</param>
|
||||||
|
/// <param name="jointId">ID of the specified joint.</param>
|
||||||
|
/// <returns>Return the pose of the specified joint.</returns>
|
||||||
|
private Pose GetJointPose(HandData hand, int jointId)
|
||||||
|
{
|
||||||
|
if (m_HandGrabber != null)
|
||||||
|
{
|
||||||
|
return m_HandGrabber.GetCurrentJointPose(jointId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 jointPosition = Vector3.zero;
|
||||||
|
Quaternion jointRotation = Quaternion.identity;
|
||||||
|
hand.GetJointPosition((JointType)jointId, ref jointPosition);
|
||||||
|
hand.GetJointRotation((JointType)jointId, ref jointRotation);
|
||||||
|
return new Pose(jointPosition, jointRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the transform of the joint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="joint">JointType of the specified joint.</param>
|
||||||
|
/// <param name="jointTransform">Output the transform of the joint.</param>
|
||||||
|
/// <returns>Return true if successfully get the transform, otherwise false.</returns>
|
||||||
|
public bool GetJointTransform(JointType joint, out Transform jointTransform)
|
||||||
|
{
|
||||||
|
jointTransform = null;
|
||||||
|
int id = (int)joint;
|
||||||
|
if (id >= 0 && id < m_HandJoints.Length)
|
||||||
|
{
|
||||||
|
if (m_HandJoints[id] != null)
|
||||||
|
{
|
||||||
|
jointTransform = m_HandJoints[id];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set all joints through gesture.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handGrabGesture">The gesture of grabbing.</param>
|
||||||
|
/// <returns>Return true if successfully set the gesture, otherwise false.</returns>
|
||||||
|
public bool SetJointsFromGrabGesture(HandGrabGesture handGrabGesture)
|
||||||
|
{
|
||||||
|
if (m_HandGrabber == null) { return false; }
|
||||||
|
|
||||||
|
for (int i = 0; i < m_HandJoints.Length; i++)
|
||||||
|
{
|
||||||
|
m_HandJoints[i].localRotation = m_HandGrabber.handGrabState.GetDefaultJointRotationInGesture(handGrabGesture, i);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
|
||||||
|
public void FindJoints()
|
||||||
|
{
|
||||||
|
if (m_HandRootJoint != null)
|
||||||
|
{
|
||||||
|
int fingerJoint = (int)JointType.Thumb_Joint0;
|
||||||
|
if (m_HandRootJoint.childCount == k_JointChildCount)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_HandRootJoint.childCount; i++)
|
||||||
|
{
|
||||||
|
Transform child = m_HandRootJoint.GetChild(i);
|
||||||
|
switch (child.childCount)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (m_RootJointType == RootType.Palm)
|
||||||
|
{
|
||||||
|
m_HandJoints[(int)JointType.Palm] = m_HandRootJoint;
|
||||||
|
m_HandJoints[(int)JointType.Wrist] = child;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_HandJoints[(int)JointType.Palm] = child;
|
||||||
|
m_HandJoints[(int)JointType.Wrist] = m_HandRootJoint;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
for (int j = 0; j < child.childCount; j++)
|
||||||
|
{
|
||||||
|
m_HandJoints[fingerJoint] = child.GetChild(j);
|
||||||
|
fingerJoint++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fingerJoint = RecursiveFind(fingerJoint, child);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_HandRootJoint.childCount == k_JointCount - 1)
|
||||||
|
{
|
||||||
|
Transform child = m_HandRootJoint.GetChild(0);
|
||||||
|
if (m_RootJointType == RootType.Palm)
|
||||||
|
{
|
||||||
|
m_HandJoints[(int)JointType.Palm] = m_HandRootJoint;
|
||||||
|
m_HandJoints[(int)JointType.Wrist] = child;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_HandJoints[(int)JointType.Palm] = child;
|
||||||
|
m_HandJoints[(int)JointType.Wrist] = m_HandRootJoint;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < m_HandRootJoint.childCount; i++)
|
||||||
|
{
|
||||||
|
m_HandJoints[i + 1] = m_HandRootJoint.GetChild(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int RecursiveFind(int jointId, Transform transform)
|
||||||
|
{
|
||||||
|
m_HandJoints[jointId] = transform;
|
||||||
|
jointId++;
|
||||||
|
if (transform.childCount > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < transform.childCount; i++)
|
||||||
|
{
|
||||||
|
jointId = RecursiveFind(jointId, transform.GetChild(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jointId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearJoints()
|
||||||
|
{
|
||||||
|
Array.Clear(m_HandJoints, 0, m_HandJoints.Length);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 062de99b5e677f34b8f4f6429d5178cc
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
"rootNamespace": "",
|
"rootNamespace": "",
|
||||||
"references": [
|
"references": [
|
||||||
"Unity.XR.OpenXR",
|
"Unity.XR.OpenXR",
|
||||||
"Unity.InputSystem"
|
"Unity.InputSystem",
|
||||||
|
"Unity.XR.Hands"
|
||||||
],
|
],
|
||||||
"includePlatforms": [
|
"includePlatforms": [
|
||||||
"Android",
|
"Android",
|
||||||
@@ -17,6 +18,12 @@
|
|||||||
"precompiledReferences": [],
|
"precompiledReferences": [],
|
||||||
"autoReferenced": true,
|
"autoReferenced": true,
|
||||||
"defineConstraints": [],
|
"defineConstraints": [],
|
||||||
"versionDefines": [],
|
"versionDefines": [
|
||||||
|
{
|
||||||
|
"name": "com.unity.xr.hands",
|
||||||
|
"expression": "",
|
||||||
|
"define": "UNITY_XR_HANDS"
|
||||||
|
}
|
||||||
|
],
|
||||||
"noEngineReferences": false
|
"noEngineReferences": false
|
||||||
}
|
}
|
||||||
@@ -801,8 +801,6 @@ namespace VIVE.OpenXR
|
|||||||
#region OpenXR function delegates
|
#region OpenXR function delegates
|
||||||
/// xrGetInstanceProcAddr
|
/// xrGetInstanceProcAddr
|
||||||
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
||||||
/// xrEnumeratePathsForInteractionProfileHTC
|
|
||||||
VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate xrEnumeratePathsForInteractionProfileHTC = null;
|
|
||||||
/// xrEnumerateDisplayRefreshRatesFB
|
/// xrEnumerateDisplayRefreshRatesFB
|
||||||
OpenXRHelper.xrGetInputSourceLocalizedNameDelegate xrGetInputSourceLocalizedName = null;
|
OpenXRHelper.xrGetInputSourceLocalizedNameDelegate xrGetInputSourceLocalizedName = null;
|
||||||
private bool GetXrFunctionDelegates(XrInstance xrInstance)
|
private bool GetXrFunctionDelegates(XrInstance xrInstance)
|
||||||
@@ -823,28 +821,6 @@ namespace VIVE.OpenXR
|
|||||||
|
|
||||||
IntPtr funcPtr = IntPtr.Zero;
|
IntPtr funcPtr = IntPtr.Zero;
|
||||||
|
|
||||||
/// xrEnumeratePathsForInteractionProfileHTC
|
|
||||||
if (XrGetInstanceProcAddr(xrInstance, "xrEnumeratePathsForInteractionProfileHTC", out funcPtr) == XrResult.XR_SUCCESS)
|
|
||||||
{
|
|
||||||
if (funcPtr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append("Get function pointer of xrEnumeratePathsForInteractionProfileHTC."); DEBUG(sb);
|
|
||||||
xrEnumeratePathsForInteractionProfileHTC = Marshal.GetDelegateForFunctionPointer(
|
|
||||||
funcPtr,
|
|
||||||
typeof(VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate)) as VivePathEnumerationHelper.xrEnumeratePathsForInteractionProfileHTCDelegate;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC.");
|
|
||||||
ERROR(sb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Clear().Append(LOG_TAG).Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC");
|
|
||||||
ERROR(sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// xrGetInputSourceLocalizedName
|
/// xrGetInputSourceLocalizedName
|
||||||
if (XrGetInstanceProcAddr(xrInstance, "xrGetInputSourceLocalizedName", out funcPtr) == XrResult.XR_SUCCESS)
|
if (XrGetInstanceProcAddr(xrInstance, "xrGetInputSourceLocalizedName", out funcPtr) == XrResult.XR_SUCCESS)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e3c169c19659ca048933749805243022
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 84e0f68aa23e3574f9facd580aaa5977
|
guid: 546612dc59d9db6429a1e86ce0baeddd
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 8
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Blue
|
||||||
|
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_ValidKeywords: []
|
||||||
|
m_InvalidKeywords: []
|
||||||
|
m_LightmapFlags: 4
|
||||||
|
m_EnableInstancingVariants: 0
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: -1
|
||||||
|
stringTagMap: {}
|
||||||
|
disabledShaderPasses: []
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _BumpMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailAlbedoMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailMask:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailNormalMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _EmissionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MainTex:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MetallicGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _OcclusionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _ParallaxMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Ints: []
|
||||||
|
m_Floats:
|
||||||
|
- _BumpScale: 1
|
||||||
|
- _Cutoff: 0.5
|
||||||
|
- _DetailNormalMapScale: 1
|
||||||
|
- _DstBlend: 0
|
||||||
|
- _GlossMapScale: 1
|
||||||
|
- _Glossiness: 0.5
|
||||||
|
- _GlossyReflections: 1
|
||||||
|
- _Metallic: 0
|
||||||
|
- _Mode: 0
|
||||||
|
- _OcclusionStrength: 1
|
||||||
|
- _Parallax: 0.02
|
||||||
|
- _SmoothnessTextureChannel: 0
|
||||||
|
- _SpecularHighlights: 1
|
||||||
|
- _SrcBlend: 1
|
||||||
|
- _UVSec: 0
|
||||||
|
- _ZWrite: 1
|
||||||
|
m_Colors:
|
||||||
|
- _Color: {r: 0, g: 1, b: 0.98279834, a: 1}
|
||||||
|
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||||
|
m_BuildTextureStacks: []
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 8e3e60c830ee65942b82d26b93bd1657
|
guid: bb0aba741af9506469fd96c2850b2e21
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
mainObjectFileID: 2100000
|
mainObjectFileID: 2100000
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 8
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Red
|
||||||
|
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_ValidKeywords: []
|
||||||
|
m_InvalidKeywords: []
|
||||||
|
m_LightmapFlags: 4
|
||||||
|
m_EnableInstancingVariants: 0
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: -1
|
||||||
|
stringTagMap: {}
|
||||||
|
disabledShaderPasses: []
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _BumpMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailAlbedoMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailMask:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailNormalMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _EmissionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MainTex:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MetallicGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _OcclusionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _ParallaxMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Ints: []
|
||||||
|
m_Floats:
|
||||||
|
- _BumpScale: 1
|
||||||
|
- _Cutoff: 0.5
|
||||||
|
- _DetailNormalMapScale: 1
|
||||||
|
- _DstBlend: 0
|
||||||
|
- _GlossMapScale: 1
|
||||||
|
- _Glossiness: 0.5
|
||||||
|
- _GlossyReflections: 1
|
||||||
|
- _Metallic: 0
|
||||||
|
- _Mode: 0
|
||||||
|
- _OcclusionStrength: 1
|
||||||
|
- _Parallax: 0.02
|
||||||
|
- _SmoothnessTextureChannel: 0
|
||||||
|
- _SpecularHighlights: 1
|
||||||
|
- _SrcBlend: 1
|
||||||
|
- _UVSec: 0
|
||||||
|
- _ZWrite: 1
|
||||||
|
m_Colors:
|
||||||
|
- _Color: {r: 1, g: 0, b: 0, a: 1}
|
||||||
|
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||||
|
m_BuildTextureStacks: []
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0a46af8cba986a041b02f47bb7731942
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 2100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3e393bf23d0131e4dab03b3278865659
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f3b8c6ad724ff5b48ba73f5847c67f27
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 4300000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b992aa7559c518429cc37201eece12a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 6bae71f48391bf444a2489a7329f3456
|
guid: 16ef4499b3cb6b645b262cd98234bad1
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 197033d17ca734640b4ca8225d6c3ece
|
guid: 80d1cc30b0e730b45bdc75cbc0df513a
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1c9fbbeb53d7c0e43b8b9e1d6989de4c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user