version 2.4.0
This commit is contained in:
@@ -67,6 +67,18 @@ namespace VIVE.OpenXR.CompositionLayer.Editor
|
|||||||
static GUIContent Label_AngleOfArc = new GUIContent("Arc Angle", "Central angle of arc of Cylinder Layer");
|
static GUIContent Label_AngleOfArc = new GUIContent("Arc Angle", "Central angle of arc of Cylinder Layer");
|
||||||
SerializedProperty Property_AngleOfArc;
|
SerializedProperty Property_AngleOfArc;
|
||||||
|
|
||||||
|
static string PropertyName_isExternalSurface = "isExternalSurface";
|
||||||
|
static GUIContent Label_isExternalSurface = new GUIContent("External Surface", "Specify using external surface or not.");
|
||||||
|
SerializedProperty Property_isExternalSurface;
|
||||||
|
|
||||||
|
static string PropertyName_ExternalSurfaceWidth = "externalSurfaceWidth";
|
||||||
|
static GUIContent Label_ExternalSurfaceWidth = new GUIContent("Width");
|
||||||
|
SerializedProperty Property_ExternalSurfaceWidth;
|
||||||
|
|
||||||
|
static string PropertyName_ExternalSurfaceHeight = "externalSurfaceHeight";
|
||||||
|
static GUIContent Label_ExternalSurfaceHeight = new GUIContent("Height");
|
||||||
|
SerializedProperty Property_ExternalSurfaceHeight;
|
||||||
|
|
||||||
static string PropertyName_IsDynamicLayer = "isDynamicLayer";
|
static string PropertyName_IsDynamicLayer = "isDynamicLayer";
|
||||||
static GUIContent Label_IsDynamicLayer = new GUIContent("Dynamic Layer", "Specify whether Layer needs to be updated each frame or not.");
|
static GUIContent Label_IsDynamicLayer = new GUIContent("Dynamic Layer", "Specify whether Layer needs to be updated each frame or not.");
|
||||||
SerializedProperty Property_IsDynamicLayer;
|
SerializedProperty Property_IsDynamicLayer;
|
||||||
@@ -101,9 +113,10 @@ namespace VIVE.OpenXR.CompositionLayer.Editor
|
|||||||
SerializedProperty Property_TrackingOrigin;
|
SerializedProperty Property_TrackingOrigin;
|
||||||
|
|
||||||
private bool showLayerParams = true, showColorScaleBiasParams = true;
|
private bool showLayerParams = true, showColorScaleBiasParams = true;
|
||||||
#pragma warning disable
|
|
||||||
private bool showExternalSurfaceParams = false;
|
private bool showExternalSurfaceParams = false;
|
||||||
#pragma warning restore
|
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
if (Property_LayerType == null) Property_LayerType = serializedObject.FindProperty(PropertyName_LayerType);
|
if (Property_LayerType == null) Property_LayerType = serializedObject.FindProperty(PropertyName_LayerType);
|
||||||
@@ -117,6 +130,9 @@ namespace VIVE.OpenXR.CompositionLayer.Editor
|
|||||||
if (Property_CylinderArcLength == null) Property_CylinderArcLength = serializedObject.FindProperty(PropertyName_CylinderArcLength);
|
if (Property_CylinderArcLength == null) Property_CylinderArcLength = serializedObject.FindProperty(PropertyName_CylinderArcLength);
|
||||||
if (Property_CylinderRadius == null) Property_CylinderRadius = serializedObject.FindProperty(PropertyName_CylinderRadius);
|
if (Property_CylinderRadius == null) Property_CylinderRadius = serializedObject.FindProperty(PropertyName_CylinderRadius);
|
||||||
if (Property_AngleOfArc == null) Property_AngleOfArc = serializedObject.FindProperty(PropertyName_AngleOfArc);
|
if (Property_AngleOfArc == null) Property_AngleOfArc = serializedObject.FindProperty(PropertyName_AngleOfArc);
|
||||||
|
if (Property_isExternalSurface == null) Property_isExternalSurface = serializedObject.FindProperty(PropertyName_isExternalSurface);
|
||||||
|
if (Property_ExternalSurfaceWidth == null) Property_ExternalSurfaceWidth = serializedObject.FindProperty(PropertyName_ExternalSurfaceWidth);
|
||||||
|
if (Property_ExternalSurfaceHeight == null) Property_ExternalSurfaceHeight = serializedObject.FindProperty(PropertyName_ExternalSurfaceHeight);
|
||||||
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_SolidEffect == null) Property_SolidEffect = serializedObject.FindProperty(PropertyName_SolidEffect);
|
||||||
@@ -436,6 +452,25 @@ namespace VIVE.OpenXR.CompositionLayer.Editor
|
|||||||
EditorGUILayout.PropertyField(Property_IsDynamicLayer, Label_IsDynamicLayer);
|
EditorGUILayout.PropertyField(Property_IsDynamicLayer, Label_IsDynamicLayer);
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
|
//EditorGUILayout.PropertyField(Property_isExternalSurface, Label_isExternalSurface);
|
||||||
|
//serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
|
//if (targetCompositionLayer.isExternalSurface)
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
showExternalSurfaceParams = EditorGUILayout.Foldout(showExternalSurfaceParams, "External Surface Parameters");
|
||||||
|
if (showExternalSurfaceParams)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(Property_ExternalSurfaceWidth, Label_ExternalSurfaceWidth);
|
||||||
|
EditorGUILayout.PropertyField(Property_ExternalSurfaceHeight, Label_ExternalSurfaceHeight);
|
||||||
|
EditorGUILayout.PropertyField(Property_IsProtectedSurface, Label_IsProtectedSurface);
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(Property_ApplyColorScaleBias, Label_ApplyColorScaleBias);
|
EditorGUILayout.PropertyField(Property_ApplyColorScaleBias, Label_ApplyColorScaleBias);
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -96,6 +96,17 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
public bool isDynamicLayer = false;
|
public bool isDynamicLayer = false;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
public bool isExternalSurface = false;
|
||||||
|
|
||||||
|
[Tooltip("Width of external surface in pixels.")]
|
||||||
|
[SerializeField]
|
||||||
|
public uint externalSurfaceWidth = 1280;
|
||||||
|
|
||||||
|
[Tooltip("Height of external surface in pixels.")]
|
||||||
|
[SerializeField]
|
||||||
|
public uint externalSurfaceHeight = 720;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
public bool applyColorScaleBias = false;
|
public bool applyColorScaleBias = false;
|
||||||
|
|
||||||
@@ -187,6 +198,58 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isExternalSurface)
|
||||||
|
{
|
||||||
|
CompositionLayerRenderThreadSyncObject SetupExternalAndroidSurfaceSyncObjects = new CompositionLayerRenderThreadSyncObject(
|
||||||
|
(taskQueue) =>
|
||||||
|
{
|
||||||
|
lock (taskQueue)
|
||||||
|
{
|
||||||
|
CompositionLayerRenderThreadTask task = (CompositionLayerRenderThreadTask)taskQueue.Dequeue();
|
||||||
|
|
||||||
|
//Enumerate Swapchain formats
|
||||||
|
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||||||
|
|
||||||
|
uint imageCount;
|
||||||
|
|
||||||
|
GraphicsAPI graphicsAPI = GraphicsAPI.GLES3;
|
||||||
|
|
||||||
|
switch (SystemInfo.graphicsDeviceType)
|
||||||
|
{
|
||||||
|
case UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3:
|
||||||
|
graphicsAPI = GraphicsAPI.GLES3;
|
||||||
|
break;
|
||||||
|
case UnityEngine.Rendering.GraphicsDeviceType.Vulkan:
|
||||||
|
graphicsAPI = GraphicsAPI.Vulkan;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ERROR("Unsupported Graphics API, aborting init process.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
layerID = compositionLayerFeature.CompositionLayer_Init(externalSurfaceWidth, externalSurfaceHeight, graphicsAPI, isDynamicLayer, isProtectedSurface, out imageCount, true);
|
||||||
|
|
||||||
|
if (layerID != 0)
|
||||||
|
{
|
||||||
|
DEBUG("Init completed, ID: " + layerID);
|
||||||
|
layerTextures = new LayerTextures(imageCount);
|
||||||
|
InitStatus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
taskQueue.Release(task);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CompositionLayerRenderThreadTask.IssueObtainSwapchainEvent(SetupExternalAndroidSurfaceSyncObjects);
|
||||||
|
|
||||||
|
texture = new Texture2D((int)externalSurfaceWidth, (int)externalSurfaceHeight, TextureFormat.RGBA32, false, isLinear);
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG("CompositionLayerInit Ext Surf");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
ERROR("CompositionLayerInit: Source Texture not found, abort init.");
|
ERROR("CompositionLayerInit: Source Texture not found, abort init.");
|
||||||
@@ -262,6 +325,22 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
{
|
{
|
||||||
if (!isInitializationComplete || !isSynchronized) return false;
|
if (!isInitializationComplete || !isSynchronized) return false;
|
||||||
|
|
||||||
|
if (isExternalSurface)
|
||||||
|
{
|
||||||
|
//Set Texture Layout
|
||||||
|
offset.x = 0;
|
||||||
|
offset.y = (int)externalSurfaceHeight;
|
||||||
|
extent.width = (int)externalSurfaceWidth;
|
||||||
|
extent.height = (int)-externalSurfaceHeight;
|
||||||
|
rect.offset = offset;
|
||||||
|
rect.extent = extent;
|
||||||
|
|
||||||
|
layerTextures.textureLayout = rect;
|
||||||
|
|
||||||
|
return true; //No need to process texture queues
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (texture != null) //check for texture size change
|
if (texture != null) //check for texture size change
|
||||||
{
|
{
|
||||||
if (TextureParamsChanged())
|
if (TextureParamsChanged())
|
||||||
@@ -805,6 +884,14 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IntPtr GetExternalSurfaceObj()
|
||||||
|
{
|
||||||
|
IntPtr value = compositionLayerFeature.Compositionlayer_GetExternalSurfaceObj2(layerID);
|
||||||
|
//DEBUG("GetExternalSurfaceObj layerID " + layerID + " SurfaceObj " + value);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public bool RenderAsLayer()
|
public bool RenderAsLayer()
|
||||||
{
|
{
|
||||||
if (placeholderGenerated && compositionLayerPlaceholderPrefabGO != null)
|
if (placeholderGenerated && compositionLayerPlaceholderPrefabGO != null)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright HTC Corporation All Rights Reserved.
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -26,7 +27,13 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
private static Dictionary<int, XrCompositionLayerPassthroughHTC> passthrough2Layer = new Dictionary<int, XrCompositionLayerPassthroughHTC>();
|
||||||
|
private static Dictionary<int, IntPtr> passthrough2LayerPtr = new Dictionary<int, IntPtr>();
|
||||||
|
private static Dictionary<int, bool> passthrough2IsUnderLay= new Dictionary<int, bool>();
|
||||||
|
private static Dictionary<int, XrPassthroughMeshTransformInfoHTC> passthrough2meshTransform = new Dictionary<int, XrPassthroughMeshTransformInfoHTC>();
|
||||||
|
private static Dictionary<int, IntPtr> passthrough2meshTransformInfoPtr = new Dictionary<int, IntPtr>();
|
||||||
|
#endif
|
||||||
#region Public APIs
|
#region Public APIs
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -62,9 +69,41 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("HTC_Passthrough feature instance not found.");
|
ERROR("HTC_Passthrough feature instance not found.");
|
||||||
return passthroughID;
|
return passthroughID;
|
||||||
}
|
}
|
||||||
|
#if UNITY_ANDROID
|
||||||
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Planar, onDestroyPassthroughSessionHandler, compositionDepth);
|
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Planar, onDestroyPassthroughSessionHandler, compositionDepth);
|
||||||
|
#endif
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
XrPassthroughHTC passthrough = 0;
|
||||||
|
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
|
||||||
|
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
|
||||||
|
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
|
||||||
|
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PLANAR_HTC
|
||||||
|
);
|
||||||
|
XrResult res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
|
||||||
|
if(res == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
ulong passthrough_ulong = passthrough;
|
||||||
|
passthroughID = (int)passthrough_ulong;
|
||||||
|
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
|
||||||
|
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
|
||||||
|
in_next: IntPtr.Zero,
|
||||||
|
in_alpha: alpha);
|
||||||
|
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
|
||||||
|
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
|
||||||
|
in_next: IntPtr.Zero,
|
||||||
|
in_layerFlags: ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
|
||||||
|
in_space: 0,
|
||||||
|
in_passthrough: passthrough,
|
||||||
|
in_color: passthroughColor);
|
||||||
|
passthrough2Layer.Add(passthroughID, compositionLayerPassthrough);
|
||||||
|
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
|
||||||
|
passthrough2LayerPtr.Add(passthroughID, layerPtr);
|
||||||
|
if (layerType == LayerType.Underlay)
|
||||||
|
passthrough2IsUnderLay.Add(passthroughID, true);
|
||||||
|
if (layerType == LayerType.Overlay)
|
||||||
|
passthrough2IsUnderLay.Add(passthroughID, false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (passthroughID == 0)
|
if (passthroughID == 0)
|
||||||
{
|
{
|
||||||
ERROR("Failed to create projected pasthrough");
|
ERROR("Failed to create projected pasthrough");
|
||||||
@@ -146,9 +185,57 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("Mesh data invalid.");
|
ERROR("Mesh data invalid.");
|
||||||
return passthroughID;
|
return passthroughID;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
XrPassthroughHTC passthrough = 0;
|
||||||
|
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
|
||||||
|
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
|
||||||
|
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
|
||||||
|
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PROJECTED_HTC
|
||||||
|
);
|
||||||
|
XrResult res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
|
||||||
|
if (res == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
ulong passthrough_ulong = passthrough;
|
||||||
|
passthroughID = (int)passthrough_ulong;
|
||||||
|
XrPassthroughMeshTransformInfoHTC PassthroughMeshTransformInfo = new XrPassthroughMeshTransformInfoHTC(
|
||||||
|
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC,
|
||||||
|
in_next: IntPtr.Zero,
|
||||||
|
in_vertexCount: 0,
|
||||||
|
in_vertices: new XrVector3f[0],
|
||||||
|
in_indexCount: 0,
|
||||||
|
in_indices: new UInt32[0],
|
||||||
|
in_baseSpace: XR_HTC_passthrough.Interop.GetTrackingSpace(),
|
||||||
|
in_time: XR_HTC_passthrough.Interop.GetFrameState().predictedDisplayTime,
|
||||||
|
in_pose: new XrPosef(),
|
||||||
|
in_scale: new XrVector3f()
|
||||||
|
);
|
||||||
|
IntPtr meshTransformInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrPassthroughMeshTransformInfoHTC)));
|
||||||
|
Marshal.StructureToPtr(PassthroughMeshTransformInfo, meshTransformInfoPtr, false);
|
||||||
|
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
|
||||||
|
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
|
||||||
|
in_next: IntPtr.Zero,
|
||||||
|
in_alpha: alpha);
|
||||||
|
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
|
||||||
|
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
|
||||||
|
in_next: meshTransformInfoPtr,
|
||||||
|
in_layerFlags: ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
|
||||||
|
in_space: 0,
|
||||||
|
in_passthrough: passthrough,
|
||||||
|
in_color: passthroughColor);
|
||||||
|
passthrough2meshTransform.Add(passthroughID, PassthroughMeshTransformInfo);
|
||||||
|
passthrough2meshTransformInfoPtr.Add(passthroughID, meshTransformInfoPtr);
|
||||||
|
passthrough2Layer.Add(passthroughID, compositionLayerPassthrough);
|
||||||
|
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
|
||||||
|
passthrough2LayerPtr.Add(passthroughID, layerPtr);
|
||||||
|
if (layerType == LayerType.Underlay)
|
||||||
|
passthrough2IsUnderLay.Add(passthroughID, true);
|
||||||
|
if (layerType == LayerType.Overlay)
|
||||||
|
passthrough2IsUnderLay.Add(passthroughID, false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Projected, onDestroyPassthroughSessionHandler, compositionDepth);
|
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Projected, onDestroyPassthroughSessionHandler, compositionDepth);
|
||||||
|
#endif
|
||||||
if (passthroughID == 0)
|
if (passthroughID == 0)
|
||||||
{
|
{
|
||||||
ERROR("Failed to create projected pasthrough");
|
ERROR("Failed to create projected pasthrough");
|
||||||
@@ -207,9 +294,57 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("HTC_Passthrough feature instance not found.");
|
ERROR("HTC_Passthrough feature instance not found.");
|
||||||
return passthroughID;
|
return passthroughID;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
XrPassthroughHTC passthrough = 0;
|
||||||
|
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
|
||||||
|
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
|
||||||
|
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
|
||||||
|
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PROJECTED_HTC
|
||||||
|
);
|
||||||
|
XrResult res = XR_HTC_passthrough.xrCreatePassthroughHTC(createInfo, out passthrough);
|
||||||
|
if (res == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
ulong passthrough_ulong = passthrough;
|
||||||
|
passthroughID = (int)passthrough_ulong;
|
||||||
|
XrPassthroughMeshTransformInfoHTC PassthroughMeshTransformInfo = new XrPassthroughMeshTransformInfoHTC(
|
||||||
|
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC,
|
||||||
|
in_next: IntPtr.Zero,
|
||||||
|
in_vertexCount: 0,
|
||||||
|
in_vertices: new XrVector3f[0],
|
||||||
|
in_indexCount: 0,
|
||||||
|
in_indices: new UInt32[0],
|
||||||
|
in_baseSpace: XR_HTC_passthrough.Interop.GetTrackingSpace(),
|
||||||
|
in_time: XR_HTC_passthrough.Interop.GetFrameState().predictedDisplayTime,
|
||||||
|
in_pose: new XrPosef(),
|
||||||
|
in_scale: new XrVector3f()
|
||||||
|
);
|
||||||
|
IntPtr meshTransformInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrPassthroughMeshTransformInfoHTC)));
|
||||||
|
Marshal.StructureToPtr(PassthroughMeshTransformInfo, meshTransformInfoPtr, false);
|
||||||
|
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
|
||||||
|
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
|
||||||
|
in_next: IntPtr.Zero,
|
||||||
|
in_alpha: alpha);
|
||||||
|
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
|
||||||
|
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
|
||||||
|
in_next: meshTransformInfoPtr,
|
||||||
|
in_layerFlags: ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
|
||||||
|
in_space: 0,
|
||||||
|
in_passthrough: passthrough,
|
||||||
|
in_color: passthroughColor);
|
||||||
|
passthrough2meshTransform.Add(passthroughID, PassthroughMeshTransformInfo);
|
||||||
|
passthrough2meshTransformInfoPtr.Add(passthroughID, meshTransformInfoPtr);
|
||||||
|
passthrough2Layer.Add(passthroughID, compositionLayerPassthrough);
|
||||||
|
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
|
||||||
|
passthrough2LayerPtr.Add(passthroughID, layerPtr);
|
||||||
|
if (layerType == LayerType.Underlay)
|
||||||
|
passthrough2IsUnderLay.Add(passthroughID, true);
|
||||||
|
if (layerType == LayerType.Overlay)
|
||||||
|
passthrough2IsUnderLay.Add(passthroughID, false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Projected, onDestroyPassthroughSessionHandler);
|
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Projected, onDestroyPassthroughSessionHandler);
|
||||||
|
#endif
|
||||||
if (passthroughID == 0)
|
if (passthroughID == 0)
|
||||||
{
|
{
|
||||||
ERROR("Failed to create projected pasthrough");
|
ERROR("Failed to create projected pasthrough");
|
||||||
@@ -221,6 +356,25 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
return passthroughID;
|
return passthroughID;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
private static void SubmitLayer()
|
||||||
|
{
|
||||||
|
foreach(var passthrough in passthrough2IsUnderLay.Keys)
|
||||||
|
{
|
||||||
|
//Get and submit layer list
|
||||||
|
XR_HTC_passthrough.Interop.GetOriginEndFrameLayerList(out List<IntPtr> layerList);//GetOriginEndFrameLayers
|
||||||
|
if (layerList.Count != 0)
|
||||||
|
{
|
||||||
|
Marshal.StructureToPtr(passthrough2Layer[passthrough], passthrough2LayerPtr[passthrough], false);
|
||||||
|
if (passthrough2IsUnderLay[passthrough])
|
||||||
|
layerList.Insert(0, passthrough2LayerPtr[passthrough]);
|
||||||
|
else
|
||||||
|
layerList.Insert(1, passthrough2LayerPtr[passthrough]);
|
||||||
|
XR_HTC_passthrough.Interop.SubmitLayers(layerList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For destroying a passthrough created previously.
|
/// For destroying a passthrough created previously.
|
||||||
@@ -245,8 +399,20 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("Passthrough to be destroyed not found");
|
ERROR("Passthrough to be destroyed not found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
XrPassthroughHTC passthrough = (XrPassthroughHTC)(ulong)passthroughID;
|
||||||
|
passthrough2Layer.Remove(passthroughID);
|
||||||
|
Marshal.FreeHGlobal(passthrough2LayerPtr[passthroughID]);
|
||||||
|
passthrough2LayerPtr.Remove(passthroughID);
|
||||||
|
passthrough2IsUnderLay.Remove(passthroughID);
|
||||||
|
Marshal.FreeHGlobal(passthrough2meshTransformInfoPtr[passthroughID]);
|
||||||
|
passthrough2meshTransformInfoPtr.Remove(passthroughID);
|
||||||
|
passthrough2meshTransform.Remove(passthroughID);
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
return passthroughFeature.HTCPassthrough_DestroyPassthrough(passthroughID);
|
return passthroughFeature.HTCPassthrough_DestroyPassthrough(passthroughID);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -277,7 +443,7 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("HTC_Passthrough feature instance not found.");
|
ERROR("HTC_Passthrough feature instance not found.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if UNITY_ANDROID
|
||||||
if (autoClamp)
|
if (autoClamp)
|
||||||
{
|
{
|
||||||
return passthroughFeature.HTCPassthrough_SetAlpha(passthroughID, Mathf.Clamp01(alpha));
|
return passthroughFeature.HTCPassthrough_SetAlpha(passthroughID, Mathf.Clamp01(alpha));
|
||||||
@@ -292,6 +458,19 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
return passthroughFeature.HTCPassthrough_SetAlpha(passthroughID, alpha);
|
return passthroughFeature.HTCPassthrough_SetAlpha(passthroughID, alpha);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (passthrough2Layer.ContainsKey(passthroughID))
|
||||||
|
{
|
||||||
|
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
|
||||||
|
layer.color.alpha = alpha;
|
||||||
|
passthrough2Layer[passthroughID] = layer;
|
||||||
|
SubmitLayer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -340,10 +519,29 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
{
|
{
|
||||||
indexBufferUint[i] = (uint)indexBuffer[i];
|
indexBufferUint[i] = (uint)indexBuffer[i];
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
|
||||||
|
{
|
||||||
|
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
|
||||||
|
MeshTransformInfo.vertexCount = (uint)vertexBuffer.Length;
|
||||||
|
MeshTransformInfo.vertices = vertexBufferXrVector;
|
||||||
|
MeshTransformInfo.indexCount = (uint)indexBuffer.Length;
|
||||||
|
MeshTransformInfo.indices = indexBufferUint;
|
||||||
|
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
|
||||||
|
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
|
||||||
|
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
|
||||||
|
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
|
||||||
|
passthrough2Layer[passthroughID] = layer;
|
||||||
|
SubmitLayer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
//Note: Ignore Clock-Wise definition of index buffer for now as passthrough extension does not have back-face culling
|
//Note: Ignore Clock-Wise definition of index buffer for now as passthrough extension does not have back-face culling
|
||||||
|
#if UNITY_ANDROID
|
||||||
return passthroughFeature.HTCPassthrough_SetMesh(passthroughID, (uint)vertexBuffer.Length, vertexBufferXrVector, (uint)indexBuffer.Length, indexBufferUint); ;
|
return passthroughFeature.HTCPassthrough_SetMesh(passthroughID, (uint)vertexBuffer.Length, vertexBufferXrVector, (uint)indexBuffer.Length, indexBufferUint); ;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -402,8 +600,26 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
meshXrPose.orientation = OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR);
|
meshXrPose.orientation = OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR);
|
||||||
|
|
||||||
XrVector3f meshXrScale = OpenXRHelper.ToOpenXRVector(meshScale, false);
|
XrVector3f meshXrScale = OpenXRHelper.ToOpenXRVector(meshScale, false);
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
|
||||||
|
{
|
||||||
|
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
|
||||||
|
MeshTransformInfo.pose = meshXrPose;
|
||||||
|
MeshTransformInfo.scale = meshXrScale;
|
||||||
|
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
|
||||||
|
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
|
||||||
|
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
|
||||||
|
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
|
||||||
|
passthrough2Layer[passthroughID] = layer;
|
||||||
|
SubmitLayer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
return passthroughFeature.HTCPassthrough_SetMeshTransform(passthroughID, passthroughFeature.GetXrSpaceFromSpaceType(spaceType), meshXrPose, meshXrScale);
|
return passthroughFeature.HTCPassthrough_SetMeshTransform(passthroughID, passthroughFeature.GetXrSpaceFromSpaceType(spaceType), meshXrPose, meshXrScale);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -429,8 +645,20 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("HTC_Passthrough feature instance not found.");
|
ERROR("HTC_Passthrough feature instance not found.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (passthrough2IsUnderLay.ContainsKey(passthroughID))
|
||||||
|
{
|
||||||
|
passthrough2IsUnderLay[passthroughID] = layerType == LayerType.Underlay ? true : false;
|
||||||
|
SubmitLayer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
return passthroughFeature.HTCPassthrough_SetLayerType(passthroughID, layerType, compositionDepth);
|
return passthroughFeature.HTCPassthrough_SetLayerType(passthroughID, layerType, compositionDepth);
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -452,8 +680,25 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("HTC_Passthrough feature instance not found.");
|
ERROR("HTC_Passthrough feature instance not found.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
|
||||||
|
{
|
||||||
|
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
|
||||||
|
MeshTransformInfo.baseSpace = passthroughFeature.GetXrSpaceFromSpaceType(spaceType);
|
||||||
|
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
|
||||||
|
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
|
||||||
|
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
|
||||||
|
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
|
||||||
|
passthrough2Layer[passthroughID] = layer;
|
||||||
|
SubmitLayer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
return passthroughFeature.HTCPassthrough_SetMeshTransformSpace(passthroughID, passthroughFeature.GetXrSpaceFromSpaceType(spaceType));
|
return passthroughFeature.HTCPassthrough_SetMeshTransformSpace(passthroughID, passthroughFeature.GetXrSpaceFromSpaceType(spaceType));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -495,8 +740,27 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
trackingSpaceMeshPosition = trackingSpaceLayerPoseTRS.GetColumn(3); //4th Column of TRS Matrix is the position
|
trackingSpaceMeshPosition = trackingSpaceLayerPoseTRS.GetColumn(3); //4th Column of TRS Matrix is the position
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
|
||||||
|
{
|
||||||
|
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
|
||||||
|
XrPosef meshXrPose = MeshTransformInfo.pose;
|
||||||
|
meshXrPose.position = OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR); ;
|
||||||
|
MeshTransformInfo.pose = meshXrPose;
|
||||||
|
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
|
||||||
|
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
|
||||||
|
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
|
||||||
|
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
|
||||||
|
passthrough2Layer[passthroughID] = layer;
|
||||||
|
SubmitLayer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
return passthroughFeature.HTCPassthrough_SetMeshTransformPosition(passthroughID, OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR));
|
return passthroughFeature.HTCPassthrough_SetMeshTransformPosition(passthroughID, OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -538,8 +802,27 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
trackingSpaceMeshRotation = Quaternion.LookRotation(trackingSpaceLayerPoseTRS.GetColumn(2), trackingSpaceLayerPoseTRS.GetColumn(1));
|
trackingSpaceMeshRotation = Quaternion.LookRotation(trackingSpaceLayerPoseTRS.GetColumn(2), trackingSpaceLayerPoseTRS.GetColumn(1));
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
|
||||||
|
{
|
||||||
|
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
|
||||||
|
XrPosef meshXrPose = MeshTransformInfo.pose;
|
||||||
|
meshXrPose.orientation = OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR);
|
||||||
|
MeshTransformInfo.pose = meshXrPose;
|
||||||
|
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
|
||||||
|
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
|
||||||
|
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
|
||||||
|
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
|
||||||
|
passthrough2Layer[passthroughID] = layer;
|
||||||
|
SubmitLayer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
return passthroughFeature.HTCPassthrough_SetMeshTransformOrientation(passthroughID, OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR));
|
return passthroughFeature.HTCPassthrough_SetMeshTransformOrientation(passthroughID, OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -561,8 +844,26 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("HTC_Passthrough feature instance not found.");
|
ERROR("HTC_Passthrough feature instance not found.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
|
||||||
|
{
|
||||||
|
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
|
||||||
|
MeshTransformInfo.scale = OpenXRHelper.ToOpenXRVector(meshScale, false);
|
||||||
|
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
|
||||||
|
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
|
||||||
|
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
|
||||||
|
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
|
||||||
|
passthrough2Layer[passthroughID] = layer;
|
||||||
|
SubmitLayer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if UNITY_ANDROID
|
||||||
return passthroughFeature.HTCPassthrough_SetMeshTransformScale(passthroughID, OpenXRHelper.ToOpenXRVector(meshScale, false));
|
return passthroughFeature.HTCPassthrough_SetMeshTransformScale(passthroughID, OpenXRHelper.ToOpenXRVector(meshScale, false));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -581,6 +882,6 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
return passthroughFeature.PassthroughIDList;
|
return passthroughFeature.PassthroughIDList;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright HTC Corporation All Rights Reserved.
|
// Copyright HTC Corporation All Rights Reserved.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
@@ -48,6 +48,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
#region OpenXR Life Cycle
|
#region OpenXR Life Cycle
|
||||||
private bool m_XrInstanceCreated = false;
|
private bool m_XrInstanceCreated = false;
|
||||||
|
/// <summary>
|
||||||
|
/// The XR instance is created or not.
|
||||||
|
/// </summary>
|
||||||
public bool XrInstanceCreated
|
public bool XrInstanceCreated
|
||||||
{
|
{
|
||||||
get { return m_XrInstanceCreated; }
|
get { return m_XrInstanceCreated; }
|
||||||
@@ -55,13 +58,13 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
private XrInstance m_XrInstance = 0;
|
private XrInstance m_XrInstance = 0;
|
||||||
protected override bool OnInstanceCreate(ulong xrInstance)
|
protected override bool OnInstanceCreate(ulong xrInstance)
|
||||||
{
|
{
|
||||||
//foreach (string kOpenxrExtensionString in kOpenxrExtensionStrings.Split(' '))
|
foreach (string kOpenxrExtensionString in kOpenxrExtensionStrings.Split(' '))
|
||||||
//{
|
{
|
||||||
// if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
|
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
|
||||||
// {
|
{
|
||||||
// WARNING("OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled.");
|
WARNING("OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled.");
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
|
|
||||||
m_XrInstanceCreated = true;
|
m_XrInstanceCreated = true;
|
||||||
m_XrInstance = xrInstance;
|
m_XrInstance = xrInstance;
|
||||||
@@ -84,6 +87,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool m_XrSessionCreated = false;
|
private bool m_XrSessionCreated = false;
|
||||||
|
/// <summary>
|
||||||
|
/// The XR session is created or not.
|
||||||
|
/// </summary>
|
||||||
public bool XrSessionCreated
|
public bool XrSessionCreated
|
||||||
{
|
{
|
||||||
get { return m_XrSessionCreated; }
|
get { return m_XrSessionCreated; }
|
||||||
@@ -97,20 +103,32 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool m_XrSessionEnding = false;
|
private bool m_XrSessionEnding = false;
|
||||||
|
/// <summary>
|
||||||
|
/// The XR session is ending or not.
|
||||||
|
/// </summary>
|
||||||
public bool XrSessionEnding
|
public bool XrSessionEnding
|
||||||
{
|
{
|
||||||
get { return m_XrSessionEnding; }
|
get { return m_XrSessionEnding; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private XrSpace m_WorldLockSpaceOriginOnHead = 0, m_WorldLockSpaceOriginOnFloor = 0, m_HeadLockSpace = 0;
|
private XrSpace m_WorldLockSpaceOriginOnHead = 0, m_WorldLockSpaceOriginOnFloor = 0, m_HeadLockSpace = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrSpace of world lock space origin on head.
|
||||||
|
/// </summary>
|
||||||
public XrSpace WorldLockSpaceOriginOnHead
|
public XrSpace WorldLockSpaceOriginOnHead
|
||||||
{
|
{
|
||||||
get { return m_WorldLockSpaceOriginOnHead; }
|
get { return m_WorldLockSpaceOriginOnHead; }
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// The XrSpace of world lock space origin on floor.
|
||||||
|
/// </summary>
|
||||||
public XrSpace WorldLockSpaceOriginOnFloor
|
public XrSpace WorldLockSpaceOriginOnFloor
|
||||||
{
|
{
|
||||||
get { return m_WorldLockSpaceOriginOnFloor; }
|
get { return m_WorldLockSpaceOriginOnFloor; }
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// The XrSpace of head lock space.
|
||||||
|
/// </summary>
|
||||||
public XrSpace HeadLockSpace
|
public XrSpace HeadLockSpace
|
||||||
{
|
{
|
||||||
get { return m_HeadLockSpace; }
|
get { return m_HeadLockSpace; }
|
||||||
@@ -252,6 +270,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current XR Session state.
|
||||||
|
/// </summary>
|
||||||
public XrSessionState XrSessionCurrentState
|
public XrSessionState XrSessionCurrentState
|
||||||
{
|
{
|
||||||
get { return m_XrSessionNewState; }
|
get { return m_XrSessionNewState; }
|
||||||
@@ -289,6 +310,10 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
/// xrGetSystemProperties
|
/// xrGetSystemProperties
|
||||||
OpenXRHelper.xrGetSystemPropertiesDelegate xrGetSystemProperties;
|
OpenXRHelper.xrGetSystemPropertiesDelegate xrGetSystemProperties;
|
||||||
|
/// <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 GetSystemProperties(ref XrSystemProperties properties)
|
public XrResult GetSystemProperties(ref XrSystemProperties properties)
|
||||||
{
|
{
|
||||||
if (m_XrInstanceCreated)
|
if (m_XrInstanceCreated)
|
||||||
@@ -301,6 +326,10 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
/// xrEnumerateReferenceSpaces
|
/// xrEnumerateReferenceSpaces
|
||||||
OpenXRHelper.xrEnumerateReferenceSpacesDelegate xrEnumerateReferenceSpaces;
|
OpenXRHelper.xrEnumerateReferenceSpacesDelegate xrEnumerateReferenceSpaces;
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerate available reference spaces
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/man/html/xrEnumerateReferenceSpaces.html">xrEnumerateReferenceSpaces</see>
|
||||||
|
/// </summary>
|
||||||
public XrResult EnumerateReferenceSpaces(UInt32 spaceCapacityInput, out UInt32 spaceCountOutput, out XrReferenceSpaceType spaces)
|
public XrResult EnumerateReferenceSpaces(UInt32 spaceCapacityInput, out UInt32 spaceCountOutput, out XrReferenceSpaceType spaces)
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated)
|
if (!m_XrSessionCreated)
|
||||||
@@ -315,6 +344,10 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
/// xrCreateReferenceSpace
|
/// xrCreateReferenceSpace
|
||||||
OpenXRHelper.xrCreateReferenceSpaceDelegate xrCreateReferenceSpace;
|
OpenXRHelper.xrCreateReferenceSpaceDelegate xrCreateReferenceSpace;
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reference space
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/man/html/xrCreateReferenceSpace.html">xrCreateReferenceSpace</see>
|
||||||
|
/// </summary>
|
||||||
public XrResult CreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
|
public XrResult CreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated)
|
if (!m_XrSessionCreated)
|
||||||
@@ -328,6 +361,10 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
/// xrDestroySpace
|
/// xrDestroySpace
|
||||||
OpenXRHelper.xrDestroySpaceDelegate xrDestroySpace;
|
OpenXRHelper.xrDestroySpaceDelegate xrDestroySpace;
|
||||||
|
/// <summary>
|
||||||
|
/// Destroys an XrSpace
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/man/html/xrDestroySpace.html">xrDestroySpace</see>
|
||||||
|
/// </summary>
|
||||||
public XrResult DestroySpace(XrSpace space)
|
public XrResult DestroySpace(XrSpace space)
|
||||||
{
|
{
|
||||||
if (space != 0)
|
if (space != 0)
|
||||||
@@ -435,9 +472,26 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
#region Wrapper Functions
|
#region Wrapper Functions
|
||||||
private const string ExtLib = "viveopenxr";
|
private const string ExtLib = "viveopenxr";
|
||||||
|
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_GetExternalSurfaceObj")]
|
||||||
|
public static extern IntPtr VIVEOpenXR_Compositionlayer_GetExternalSurfaceObj();
|
||||||
|
public IntPtr Compositionlayer_GetExternalSurfaceObj()
|
||||||
|
{
|
||||||
|
return VIVEOpenXR_Compositionlayer_GetExternalSurfaceObj();
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_GetExternalSurfaceObj2")]
|
||||||
|
public static extern IntPtr VIVEOpenXR_Compositionlayer_GetExternalSurfaceObj2(int layerID);
|
||||||
|
public IntPtr Compositionlayer_GetExternalSurfaceObj2(int layerID)
|
||||||
|
{
|
||||||
|
return VIVEOpenXR_Compositionlayer_GetExternalSurfaceObj2(layerID);
|
||||||
|
}
|
||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_Init")]
|
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_Init")]
|
||||||
public static extern int VIVEOpenXR_CompositionLayer_Init(XrSession session, uint textureWidth, uint textureHeight, GraphicsAPI graphicsAPI, bool isDynamic, bool isProtected, out uint imageCount);
|
public static extern int VIVEOpenXR_CompositionLayer_Init(XrSession session, uint textureWidth, uint textureHeight, GraphicsAPI graphicsAPI, bool isDynamic, bool isProtected, out uint imageCount, bool isExternal);
|
||||||
public int CompositionLayer_Init(uint textureWidth, uint textureHeight, GraphicsAPI graphicsAPI, bool isDynamic, bool isProtected, out uint imageCount)
|
/// <summary>
|
||||||
|
/// Init composion layer.
|
||||||
|
/// </summary>
|
||||||
|
public int CompositionLayer_Init(uint textureWidth, uint textureHeight, GraphicsAPI graphicsAPI, bool isDynamic, bool isProtected, out uint imageCount, bool isExternal = false)
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated)
|
if (!m_XrSessionCreated)
|
||||||
{
|
{
|
||||||
@@ -446,11 +500,14 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return VIVEOpenXR_CompositionLayer_Init(m_XrSession, textureWidth, textureHeight, graphicsAPI, isDynamic, isProtected, out imageCount);
|
return VIVEOpenXR_CompositionLayer_Init(m_XrSession, textureWidth, textureHeight, graphicsAPI, isDynamic, isProtected, out imageCount, isExternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_GetTexture")]
|
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_GetTexture")]
|
||||||
public static extern IntPtr VIVEOpenXR_CompositionLayer_GetTexture(int layerID, out uint imageIndex);
|
public static extern IntPtr VIVEOpenXR_CompositionLayer_GetTexture(int layerID, out uint imageIndex);
|
||||||
|
/// <summary>
|
||||||
|
/// Get composition layer texture.
|
||||||
|
/// </summary>
|
||||||
public IntPtr CompositionLayer_GetTexture(int layerID, out uint imageIndex)
|
public IntPtr CompositionLayer_GetTexture(int layerID, out uint imageIndex)
|
||||||
{
|
{
|
||||||
return VIVEOpenXR_CompositionLayer_GetTexture(layerID, out imageIndex);
|
return VIVEOpenXR_CompositionLayer_GetTexture(layerID, out imageIndex);
|
||||||
@@ -458,6 +515,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_ReleaseTexture")]
|
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_ReleaseTexture")]
|
||||||
public static extern bool VIVEOpenXR_CompositionLayer_ReleaseTexture(int layerID);
|
public static extern bool VIVEOpenXR_CompositionLayer_ReleaseTexture(int layerID);
|
||||||
|
/// <summary>
|
||||||
|
/// release composition layer texture.
|
||||||
|
/// </summary>
|
||||||
public bool CompositionLayer_ReleaseTexture(int layerID)
|
public bool CompositionLayer_ReleaseTexture(int layerID)
|
||||||
{
|
{
|
||||||
return VIVEOpenXR_CompositionLayer_ReleaseTexture(layerID);
|
return VIVEOpenXR_CompositionLayer_ReleaseTexture(layerID);
|
||||||
@@ -465,6 +525,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_Destroy")]
|
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_Destroy")]
|
||||||
public static extern bool VIVEOpenXR_CompositionLayer_Destroy(int layerID);
|
public static extern bool VIVEOpenXR_CompositionLayer_Destroy(int layerID);
|
||||||
|
/// <summary>
|
||||||
|
/// destroy composition layer.
|
||||||
|
/// </summary>
|
||||||
public bool CompositionLayer_Destroy(int layerID)
|
public bool CompositionLayer_Destroy(int layerID)
|
||||||
{
|
{
|
||||||
return VIVEOpenXR_CompositionLayer_Destroy(layerID);
|
return VIVEOpenXR_CompositionLayer_Destroy(layerID);
|
||||||
@@ -472,6 +535,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "submit_CompositionLayerQuad")]
|
[DllImportAttribute(ExtLib, EntryPoint = "submit_CompositionLayerQuad")]
|
||||||
public static extern void VIVEOpenXR_Submit_CompositionLayerQuad(XrCompositionLayerQuad quad, LayerType layerType, uint compositionDepth, int layerID);
|
public static extern void VIVEOpenXR_Submit_CompositionLayerQuad(XrCompositionLayerQuad quad, LayerType layerType, uint compositionDepth, int layerID);
|
||||||
|
/// <summary>
|
||||||
|
/// submit compostion layer of type quad.
|
||||||
|
/// </summary>
|
||||||
public void Submit_CompositionLayerQuad(XrCompositionLayerQuad quad, LayerType layerType, uint compositionDepth, int layerID)
|
public void Submit_CompositionLayerQuad(XrCompositionLayerQuad quad, LayerType layerType, uint compositionDepth, int layerID)
|
||||||
{
|
{
|
||||||
VIVEOpenXR_Submit_CompositionLayerQuad(quad, layerType, compositionDepth, layerID);
|
VIVEOpenXR_Submit_CompositionLayerQuad(quad, layerType, compositionDepth, layerID);
|
||||||
@@ -491,6 +557,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_GetFuncAddrs")]
|
[DllImportAttribute(ExtLib, EntryPoint = "compositionlayer_GetFuncAddrs")]
|
||||||
public static extern XrResult VIVEOpenXR_CompositionLayer_GetFuncAddrs(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrFuncPtr);
|
public static extern XrResult VIVEOpenXR_CompositionLayer_GetFuncAddrs(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrFuncPtr);
|
||||||
|
/// <summary>
|
||||||
|
/// get function address of composition layer.
|
||||||
|
/// </summary>
|
||||||
public XrResult CompositionLayer_GetFuncAddrs(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrFuncPtr)
|
public XrResult CompositionLayer_GetFuncAddrs(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrFuncPtr)
|
||||||
{
|
{
|
||||||
return VIVEOpenXR_CompositionLayer_GetFuncAddrs(xrInstance, xrGetInstanceProcAddrFuncPtr);
|
return VIVEOpenXR_CompositionLayer_GetFuncAddrs(xrInstance, xrGetInstanceProcAddrFuncPtr);
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
private const string kOpenXRColorScaleBiasExtensionString = "XR_KHR_composition_layer_color_scale_bias";
|
private const string kOpenXRColorScaleBiasExtensionString = "XR_KHR_composition_layer_color_scale_bias";
|
||||||
|
|
||||||
private bool m_ColorScaleBiasExtensionEnabled = true;
|
private bool m_ColorScaleBiasExtensionEnabled = true;
|
||||||
|
/// <summary>
|
||||||
|
/// The extension of "XR_KHR_composition_layer_color_scale_bias" is enabled or not.
|
||||||
|
/// </summary>
|
||||||
public bool ColorScaleBiasExtensionEnabled
|
public bool ColorScaleBiasExtensionEnabled
|
||||||
{
|
{
|
||||||
get { return m_ColorScaleBiasExtensionEnabled; }
|
get { return m_ColorScaleBiasExtensionEnabled; }
|
||||||
@@ -68,6 +71,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "submit_CompositionLayerColorBias")]
|
[DllImportAttribute(ExtLib, EntryPoint = "submit_CompositionLayerColorBias")]
|
||||||
public static extern void VIVEOpenXR_Submit_CompositionLayerColorBias(XrCompositionLayerColorScaleBiasKHR colorBias, int layerID);
|
public static extern void VIVEOpenXR_Submit_CompositionLayerColorBias(XrCompositionLayerColorScaleBiasKHR colorBias, int layerID);
|
||||||
|
/// <summary>
|
||||||
|
/// Submit Compostion Layer Color Bias Settings to specific layer ID.
|
||||||
|
/// </summary>
|
||||||
public void Submit_CompositionLayerColorBias(XrCompositionLayerColorScaleBiasKHR colorBias, int layerID)
|
public void Submit_CompositionLayerColorBias(XrCompositionLayerColorScaleBiasKHR colorBias, int layerID)
|
||||||
{
|
{
|
||||||
if (!ColorScaleBiasExtensionEnabled)
|
if (!ColorScaleBiasExtensionEnabled)
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
private const string kOpenXRCylinderExtensionString = "XR_KHR_composition_layer_cylinder";
|
private const string kOpenXRCylinderExtensionString = "XR_KHR_composition_layer_cylinder";
|
||||||
|
|
||||||
private bool m_CylinderExtensionEnabled = true;
|
private bool m_CylinderExtensionEnabled = true;
|
||||||
|
/// <summary>
|
||||||
|
/// The extension "XR_KHR_composition_layer_cylinder" is enabled or not.
|
||||||
|
/// </summary>
|
||||||
public bool CylinderExtensionEnabled
|
public bool CylinderExtensionEnabled
|
||||||
{
|
{
|
||||||
get { return m_CylinderExtensionEnabled; }
|
get { return m_CylinderExtensionEnabled; }
|
||||||
@@ -68,6 +71,9 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "submit_CompositionLayerCylinder")]
|
[DllImportAttribute(ExtLib, EntryPoint = "submit_CompositionLayerCylinder")]
|
||||||
public static extern void VIVEOpenXR_Submit_CompositionLayerCylinder(XrCompositionLayerCylinderKHR cylinder, LayerType layerType, uint compositionDepth, int layerID);
|
public static extern void VIVEOpenXR_Submit_CompositionLayerCylinder(XrCompositionLayerCylinderKHR cylinder, LayerType layerType, uint compositionDepth, int layerID);
|
||||||
|
/// <summary>
|
||||||
|
/// submit compostion layer of type cylinder.
|
||||||
|
/// </summary>
|
||||||
public void Submit_CompositionLayerCylinder(XrCompositionLayerCylinderKHR cylinder, LayerType layerType, uint compositionDepth, int layerID)
|
public void Submit_CompositionLayerCylinder(XrCompositionLayerCylinderKHR cylinder, LayerType layerType, uint compositionDepth, int layerID)
|
||||||
{
|
{
|
||||||
if (!CylinderExtensionEnabled)
|
if (!CylinderExtensionEnabled)
|
||||||
|
|||||||
@@ -301,8 +301,288 @@ namespace VIVE.OpenXR.CompositionLayer
|
|||||||
Underlay = 2
|
Underlay = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An application can create an <see cref="XrPassthroughHTC">XrPassthroughHTC</see> handle by calling <see cref="ViveCompositionLayerHelper.xrCreatePassthroughHTC">xrCreatePassthroughHTC</see>. The returned passthrough handle can be subsequently used in API calls.
|
||||||
|
/// </summary>
|
||||||
|
public struct XrPassthroughHTC : IEquatable<UInt64>
|
||||||
|
{
|
||||||
|
private readonly UInt64 value;
|
||||||
|
|
||||||
|
public XrPassthroughHTC(UInt64 u)
|
||||||
|
{
|
||||||
|
value = u;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator UInt64(XrPassthroughHTC equatable)
|
||||||
|
{
|
||||||
|
return equatable.value;
|
||||||
|
}
|
||||||
|
public static implicit operator XrPassthroughHTC(UInt64 u)
|
||||||
|
{
|
||||||
|
return new XrPassthroughHTC(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(XrPassthroughHTC other)
|
||||||
|
{
|
||||||
|
return value == other.value;
|
||||||
|
}
|
||||||
|
public bool Equals(UInt64 other)
|
||||||
|
{
|
||||||
|
return value == other;
|
||||||
|
}
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is XrPassthroughHTC && Equals((XrPassthroughHTC)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return value.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(XrPassthroughHTC a, XrPassthroughHTC b) { return a.Equals(b); }
|
||||||
|
public static bool operator !=(XrPassthroughHTC a, XrPassthroughHTC b) { return !a.Equals(b); }
|
||||||
|
public static bool operator >=(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value >= b.value; }
|
||||||
|
public static bool operator <=(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value <= b.value; }
|
||||||
|
public static bool operator >(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value > b.value; }
|
||||||
|
public static bool operator <(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value < b.value; }
|
||||||
|
public static XrPassthroughHTC operator +(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value + b.value; }
|
||||||
|
public static XrPassthroughHTC operator -(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value - b.value; }
|
||||||
|
public static XrPassthroughHTC operator *(XrPassthroughHTC a, XrPassthroughHTC b) { return a.value * b.value; }
|
||||||
|
public static XrPassthroughHTC operator /(XrPassthroughHTC a, XrPassthroughHTC b)
|
||||||
|
{
|
||||||
|
if (b.value == 0)
|
||||||
|
{
|
||||||
|
throw new DivideByZeroException();
|
||||||
|
}
|
||||||
|
return a.value / b.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The XrPassthroughFormHTC enumeration identifies the form of the passthrough, presenting the passthrough fill the full screen or project onto a specified mesh.
|
||||||
|
/// </summary>
|
||||||
|
public enum XrPassthroughFormHTC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Presents the passthrough with full of the entire screen..
|
||||||
|
/// </summary>
|
||||||
|
XR_PASSTHROUGH_FORM_PLANAR_HTC = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Presents the passthrough projecting onto a custom mesh.
|
||||||
|
/// </summary>
|
||||||
|
XR_PASSTHROUGH_FORM_PROJECTED_HTC = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The XrPassthroughCreateInfoHTC structure describes the information to create an <see cref="XrPassthroughCreateInfoHTC">XrPassthroughCreateInfoHTC</see> handle.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct XrPassthroughCreateInfoHTC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="XrStructureType">XrStructureType</see> of this structure.
|
||||||
|
/// </summary>
|
||||||
|
public XrStructureType type;
|
||||||
|
/// <summary>
|
||||||
|
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr next;
|
||||||
|
/// <summary>
|
||||||
|
/// The form specifies the form of passthrough.
|
||||||
|
/// </summary>
|
||||||
|
public XrPassthroughFormHTC form;
|
||||||
|
|
||||||
|
/// <param name="in_type">The <see cref="XrStructureType">XrStructureType</see> of this structure.</param>
|
||||||
|
/// <param name="in_next">NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.</param>
|
||||||
|
/// <param name="in_facialTrackingType">An XrFacialTrackingTypeHTC which describes which type of facial tracking should be used for this handle.</param>
|
||||||
|
public XrPassthroughCreateInfoHTC(XrStructureType in_type, IntPtr in_next, XrPassthroughFormHTC in_form)
|
||||||
|
{
|
||||||
|
type = in_type;
|
||||||
|
next = in_next;
|
||||||
|
form = in_form;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// <summary>
|
||||||
|
/// The XrCompositionLayerBaseHeader structure is not intended to be directly used, but forms a basis for defining current and future structures containing composition layer information. The XrFrameEndInfo structure contains an array of pointers to these polymorphic header structures.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct XrCompositionLayerBaseHeader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The XrStructureType of this structure.
|
||||||
|
/// </summary>
|
||||||
|
public XrStructureType type;
|
||||||
|
/// <summary>
|
||||||
|
/// Next is NULL or a pointer to the next structure in a structure chain, such as XrPassthroughMeshTransformInfoHTC.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr next;
|
||||||
|
/// <summary>
|
||||||
|
/// A bitmask of XrCompositionLayerFlagBits describing flags to apply to the layer.
|
||||||
|
/// </summary>
|
||||||
|
public XrCompositionLayerFlags layerFlags;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrSpace in which the layer will be kept stable over time.
|
||||||
|
/// </summary>
|
||||||
|
public XrSpace space;
|
||||||
|
};
|
||||||
|
/// <summary>
|
||||||
|
/// The application can specify the XrPassthroughColorHTC to adjust the alpha value of the passthrough. The range is between 0.0f and 1.0f, 1.0f means opaque.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct XrPassthroughColorHTC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The XrStructureType of this structure.
|
||||||
|
/// </summary>
|
||||||
|
public XrStructureType type;
|
||||||
|
/// <summary>
|
||||||
|
/// Next is NULL or a pointer to the next structure in a structure chain, such as XrPassthroughMeshTransformInfoHTC.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr next;
|
||||||
|
/// <summary>
|
||||||
|
/// The alpha value of the passthrough in the range [0, 1].
|
||||||
|
/// </summary>
|
||||||
|
public float alpha;
|
||||||
|
public XrPassthroughColorHTC(XrStructureType in_type, IntPtr in_next, float in_alpha)
|
||||||
|
{
|
||||||
|
type = in_type;
|
||||||
|
next = in_next;
|
||||||
|
alpha = in_alpha;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// <summary>
|
||||||
|
/// A pointer to XrCompositionLayerPassthroughHTC may be submitted in xrEndFrame as a pointer to the base structure XrCompositionLayerBaseHeader, in the desired layer order, to request the runtime to composite a passthrough layer into the final frame output.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct XrCompositionLayerPassthroughHTC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The XrStructureType of this structure.
|
||||||
|
/// </summary>
|
||||||
|
public XrStructureType type;
|
||||||
|
/// <summary>
|
||||||
|
/// Next is NULL or a pointer to the next structure in a structure chain, such as XrPassthroughMeshTransformInfoHTC.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr next;
|
||||||
|
/// <summary>
|
||||||
|
/// A bitmask of XrCompositionLayerFlagBits describing flags to apply to the layer.
|
||||||
|
/// </summary>
|
||||||
|
public XrCompositionLayerFlags layerFlags;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrSpace that specifies the layer’s space - must be XR_NULL_HANDLE.
|
||||||
|
/// </summary>
|
||||||
|
public XrSpace space;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrPassthroughHTC previously created by xrCreatePassthroughHTC.
|
||||||
|
/// </summary>
|
||||||
|
public XrPassthroughHTC passthrough;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrPassthroughColorHTC describing the color information with the alpha value of the passthrough layer.
|
||||||
|
/// </summary>
|
||||||
|
public XrPassthroughColorHTC color;
|
||||||
|
public XrCompositionLayerPassthroughHTC(XrStructureType in_type, IntPtr in_next, XrCompositionLayerFlags in_layerFlags,
|
||||||
|
XrSpace in_space, XrPassthroughHTC in_passthrough, XrPassthroughColorHTC in_color)
|
||||||
|
{
|
||||||
|
type = in_type;
|
||||||
|
next = in_next;
|
||||||
|
layerFlags = in_layerFlags;
|
||||||
|
space = in_space;
|
||||||
|
passthrough = in_passthrough;
|
||||||
|
color = in_color;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The XrPassthroughMeshTransformInfoHTC structure describes the mesh and transformation.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct XrPassthroughMeshTransformInfoHTC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The XrStructureType of this structure.
|
||||||
|
/// </summary>
|
||||||
|
public XrStructureType type;
|
||||||
|
/// <summary>
|
||||||
|
/// Next is NULL or a pointer to the next structure in a structure chain.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr next;
|
||||||
|
/// <summary>
|
||||||
|
/// The count of vertices array in the mesh.
|
||||||
|
/// </summary>
|
||||||
|
public UInt32 vertexCount;
|
||||||
|
/// <summary>
|
||||||
|
/// An array of XrVector3f. The size of the array must be equal to vertexCount.
|
||||||
|
/// </summary>
|
||||||
|
public XrVector3f[] vertices;
|
||||||
|
/// <summary>
|
||||||
|
/// The count of indices array in the mesh.
|
||||||
|
/// </summary>
|
||||||
|
public UInt32 indexCount;
|
||||||
|
/// <summary>
|
||||||
|
/// An array of triangle indices. The size of the array must be equal to indexCount.
|
||||||
|
/// </summary>
|
||||||
|
public UInt32[] indices;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrSpace that defines the projected passthrough's base space for transformations.
|
||||||
|
/// </summary>
|
||||||
|
public XrSpace baseSpace;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrTime that defines the time at which the transform is applied.
|
||||||
|
/// </summary>
|
||||||
|
public XrTime time;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrPosef that defines the pose of the mesh
|
||||||
|
/// </summary>
|
||||||
|
public XrPosef pose;
|
||||||
|
/// <summary>
|
||||||
|
/// The XrVector3f that defines the scale of the mesh
|
||||||
|
/// </summary>
|
||||||
|
public XrVector3f scale;
|
||||||
|
public XrPassthroughMeshTransformInfoHTC(XrStructureType in_type, IntPtr in_next, UInt32 in_vertexCount,
|
||||||
|
XrVector3f[] in_vertices, UInt32 in_indexCount, UInt32[] in_indices, XrSpace in_baseSpace, XrTime in_time,
|
||||||
|
XrPosef in_pose, XrVector3f in_scale)
|
||||||
|
{
|
||||||
|
type = in_type;
|
||||||
|
next = in_next;
|
||||||
|
vertexCount = in_vertexCount;
|
||||||
|
vertices = in_vertices;
|
||||||
|
indexCount = in_indexCount;
|
||||||
|
indices = in_indices;
|
||||||
|
baseSpace = in_baseSpace;
|
||||||
|
time = in_time;
|
||||||
|
pose = in_pose;
|
||||||
|
scale = in_scale;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static class ViveCompositionLayerHelper
|
public static class ViveCompositionLayerHelper
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate function of <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreatePassthroughHTC">xrCreatePassthroughHTC</see>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">An <see cref="XrSession">XrSession</see> in which the passthrough will be active.</param>
|
||||||
|
/// <param name="createInfo">createInfo is a pointer to an <see cref="XrPassthroughCreateInfoHTC">XrPassthroughCreateInfoHTC</see> structure containing information about how to create the passthrough.</param>
|
||||||
|
/// <param name="passthrough">passthrough is a pointer to a handle in which the created <see cref="XrPassthroughHTC">XrPassthroughHTC</see> is returned.</param>
|
||||||
|
/// <returns>XR_SUCCESS for success.</returns>
|
||||||
|
public delegate XrResult xrCreatePassthroughHTCDelegate(
|
||||||
|
XrSession session,
|
||||||
|
XrPassthroughCreateInfoHTC createInfo,
|
||||||
|
out XrPassthroughHTC passthrough);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate function of <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyPassthroughHTC">xrDestroyFacialTrackerHTC</see>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="passthrough">passthrough is the <see cref="XrPassthroughHTC">XrPassthroughHTC</see> to be destroyed..</param>
|
||||||
|
/// <returns>XR_SUCCESS for success.</returns>
|
||||||
|
public delegate XrResult xrDestroyPassthroughHTCDelegate(
|
||||||
|
XrPassthroughHTC passthrough);
|
||||||
// Flag bits for XrCompositionLayerFlags
|
// Flag bits for XrCompositionLayerFlags
|
||||||
public static XrCompositionLayerFlags XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT = 0x00000001;
|
public static XrCompositionLayerFlags XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT = 0x00000001;
|
||||||
public static XrCompositionLayerFlags XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT = 0x00000002;
|
public static XrCompositionLayerFlags XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT = 0x00000002;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
DocumentationLink = "..\\Documentation",
|
DocumentationLink = "..\\Documentation",
|
||||||
OpenxrExtensionStrings = kOpenxrExtensionStrings,
|
OpenxrExtensionStrings = kOpenxrExtensionStrings,
|
||||||
Version = "1.0.0",
|
Version = "1.0.0",
|
||||||
BuildTargetGroups = new[] { BuildTargetGroup.Android },
|
BuildTargetGroups = new[] { BuildTargetGroup.Android ,BuildTargetGroup.Standalone},
|
||||||
FeatureId = featureId
|
FeatureId = featureId
|
||||||
)]
|
)]
|
||||||
#endif
|
#endif
|
||||||
@@ -36,6 +36,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
static void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
|
static void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
|
||||||
|
|
||||||
private List<int> passthroughIDList = new List<int>();
|
private List<int> passthroughIDList = new List<int>();
|
||||||
|
/// <summary>
|
||||||
|
/// The List of passthrough ID.
|
||||||
|
/// </summary>
|
||||||
public List<int> PassthroughIDList { get{ return new List<int>(passthroughIDList); } }
|
public List<int> PassthroughIDList { get{ return new List<int>(passthroughIDList); } }
|
||||||
|
|
||||||
private List<XRInputSubsystem> inputSubsystems = new List<XRInputSubsystem>();
|
private List<XRInputSubsystem> inputSubsystems = new List<XRInputSubsystem>();
|
||||||
@@ -45,9 +48,15 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string featureId = "vive.openxr.feature.compositionlayer.passthrough";
|
public const string featureId = "vive.openxr.feature.compositionlayer.passthrough";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The extension string.
|
||||||
|
/// </summary>
|
||||||
public const string kOpenxrExtensionStrings = "XR_HTC_passthrough";
|
public const string kOpenxrExtensionStrings = "XR_HTC_passthrough";
|
||||||
|
|
||||||
private bool m_HTCPassthroughExtensionEnabled = true;
|
private bool m_HTCPassthroughExtensionEnabled = true;
|
||||||
|
/// <summary>
|
||||||
|
/// The HTC Passthrough extension is enabled or not.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthroughExtensionEnabled
|
public bool HTCPassthroughExtensionEnabled
|
||||||
{
|
{
|
||||||
get { return m_HTCPassthroughExtensionEnabled; }
|
get { return m_HTCPassthroughExtensionEnabled; }
|
||||||
@@ -55,10 +64,118 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
#region OpenXR Life Cycle
|
#region OpenXR Life Cycle
|
||||||
private bool m_XrInstanceCreated = false;
|
private bool m_XrInstanceCreated = false;
|
||||||
|
/// <summary>
|
||||||
|
/// The XR instance is created or not.
|
||||||
|
/// </summary>
|
||||||
public bool XrInstanceCreated
|
public bool XrInstanceCreated
|
||||||
{
|
{
|
||||||
get { return m_XrInstanceCreated; }
|
get { return m_XrInstanceCreated; }
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
private static IntPtr xrGetInstanceProcAddr_prev;
|
||||||
|
private static IntPtr XrEndFrame_prev;
|
||||||
|
private static IntPtr XrWaitFrame_prev;
|
||||||
|
private static List<IntPtr> layerListOrigin = new List<IntPtr>();
|
||||||
|
private static List<IntPtr> layerListModified = new List<IntPtr>();
|
||||||
|
private static IntPtr layersModified = Marshal.AllocHGlobal((int)(Marshal.SizeOf(typeof(IntPtr)) * 30)); //Preallocate a layer buffer with sufficient size and reuse it for each frame.
|
||||||
|
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.Log("EXT: registering our own xrGetInstanceProcAddr");
|
||||||
|
xrGetInstanceProcAddr_prev = func;
|
||||||
|
return Marshal.GetFunctionPointerForDelegate(Intercept_xrGetInstanceProcAddr);
|
||||||
|
}
|
||||||
|
[MonoPInvokeCallback(typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate))]
|
||||||
|
private static XrResult InterceptXrEndFrame_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 == "xrEndFrame")
|
||||||
|
{
|
||||||
|
XrEndFrame_prev = function;
|
||||||
|
m_intercept_xrEndFrame = intercepted_xrEndFrame;
|
||||||
|
function = Marshal.GetFunctionPointerForDelegate(m_intercept_xrEndFrame); ;
|
||||||
|
UnityEngine.Debug.Log("Getting xrEndFrame func");
|
||||||
|
}
|
||||||
|
if (name == "xrWaitFrame")
|
||||||
|
{
|
||||||
|
XrWaitFrame_prev = function;
|
||||||
|
m_intercept_xrWaitFrame = intercepted_xrWaitFrame;
|
||||||
|
function = Marshal.GetFunctionPointerForDelegate(m_intercept_xrWaitFrame); ;
|
||||||
|
UnityEngine.Debug.Log("Getting xrWaitFrame func");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MonoPInvokeCallback(typeof(OpenXRHelper.xrEndFrameDelegate))]
|
||||||
|
private static XrResult intercepted_xrEndFrame(XrSession session, ref XrFrameEndInfo frameEndInfo)
|
||||||
|
{
|
||||||
|
XrResult res;
|
||||||
|
// Get delegate of prev xrEndFrame.
|
||||||
|
var xrEndFrame = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrEndFrameDelegate>(XrEndFrame_prev);
|
||||||
|
|
||||||
|
layerListOrigin.Clear();
|
||||||
|
uint layerCount = frameEndInfo.layerCount;
|
||||||
|
IntPtr layers = frameEndInfo.layers;
|
||||||
|
for (int i = 0; i < layerCount; i++)
|
||||||
|
{
|
||||||
|
IntPtr ptr = Marshal.ReadIntPtr(layers, i * Marshal.SizeOf(typeof(IntPtr)));
|
||||||
|
XrCompositionLayerBaseHeader header = (XrCompositionLayerBaseHeader)Marshal.PtrToStructure(ptr, typeof(XrCompositionLayerBaseHeader));
|
||||||
|
layerListOrigin.Add(ptr);
|
||||||
|
}
|
||||||
|
List<IntPtr> layerListNew;
|
||||||
|
if (layerListModified.Count != 0)
|
||||||
|
{
|
||||||
|
layerListNew = new List<IntPtr>(layerListModified);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
layerListNew = new List<IntPtr>(layerListOrigin);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < layerListNew.Count; i++)
|
||||||
|
{
|
||||||
|
Marshal.WriteIntPtr(layersModified, i * Marshal.SizeOf(typeof(IntPtr)), layerListNew[i]);
|
||||||
|
}
|
||||||
|
frameEndInfo.layers = layersModified;
|
||||||
|
frameEndInfo.layerCount = (uint)layerListNew.Count;
|
||||||
|
|
||||||
|
res = xrEndFrame(session, ref frameEndInfo);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
private static XrFrameWaitInfo m_frameWaitInfo;
|
||||||
|
private static XrFrameState m_frameState;
|
||||||
|
[MonoPInvokeCallback(typeof(OpenXRHelper.xrWaitFrameDelegate))]
|
||||||
|
private static int intercepted_xrWaitFrame(ulong session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState)
|
||||||
|
{
|
||||||
|
var xrWaitFrame = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrWaitFrameDelegate>(XrWaitFrame_prev);
|
||||||
|
int res = xrWaitFrame(session, ref frameWaitInfo, ref frameState);
|
||||||
|
m_frameWaitInfo = frameWaitInfo;
|
||||||
|
m_frameState = frameState;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public void GetOriginEndFrameLayerList(out List<IntPtr> layers)
|
||||||
|
{
|
||||||
|
layers = new List<IntPtr>(layerListOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SubmitLayers(List<IntPtr> layers)
|
||||||
|
{
|
||||||
|
layerListModified = new List<IntPtr>(layers);
|
||||||
|
//UnityEngine.Debug.Log("####Update submit end " + layerListModified.Count);
|
||||||
|
}
|
||||||
|
public XrFrameState GetFrameState()
|
||||||
|
{
|
||||||
|
return m_frameState;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private XrInstance m_XrInstance = 0;
|
private XrInstance m_XrInstance = 0;
|
||||||
protected override bool OnInstanceCreate(ulong xrInstance)
|
protected override bool OnInstanceCreate(ulong xrInstance)
|
||||||
{
|
{
|
||||||
@@ -94,6 +211,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool m_XrSessionCreated = false;
|
private bool m_XrSessionCreated = false;
|
||||||
|
/// <summary>
|
||||||
|
/// The XR session is created or not.
|
||||||
|
/// </summary>
|
||||||
public bool XrSessionCreated
|
public bool XrSessionCreated
|
||||||
{
|
{
|
||||||
get { return m_XrSessionCreated; }
|
get { return m_XrSessionCreated; }
|
||||||
@@ -107,6 +227,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool m_XrSessionEnding = false;
|
private bool m_XrSessionEnding = false;
|
||||||
|
/// <summary>
|
||||||
|
/// The XR session is ending or not.
|
||||||
|
/// </summary>
|
||||||
public bool XrSessionEnding
|
public bool XrSessionEnding
|
||||||
{
|
{
|
||||||
get { return m_XrSessionEnding; }
|
get { return m_XrSessionEnding; }
|
||||||
@@ -239,13 +362,16 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
DEBUG("OnSessionEnd() " + m_XrSession);
|
DEBUG("OnSessionEnd() " + m_XrSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate of Passthrough Session Destroy.
|
||||||
|
/// </summary>
|
||||||
public delegate void OnPassthroughSessionDestroyDelegate(int passthroughID);
|
public delegate void OnPassthroughSessionDestroyDelegate(int passthroughID);
|
||||||
private Dictionary<int, OnPassthroughSessionDestroyDelegate> OnPassthroughSessionDestroyHandlerDictionary = new Dictionary<int, OnPassthroughSessionDestroyDelegate>();
|
private Dictionary<int, OnPassthroughSessionDestroyDelegate> OnPassthroughSessionDestroyHandlerDictionary = new Dictionary<int, OnPassthroughSessionDestroyDelegate>();
|
||||||
protected override void OnSessionDestroy(ulong xrSession)
|
protected override void OnSessionDestroy(ulong xrSession)
|
||||||
{
|
{
|
||||||
m_XrSessionCreated = false;
|
m_XrSessionCreated = false;
|
||||||
DEBUG("OnSessionDestroy() " + xrSession);
|
DEBUG("OnSessionDestroy() " + xrSession);
|
||||||
|
#if UNITY_ANDROID
|
||||||
//Notify that all passthrough layers should be destroyed
|
//Notify that all passthrough layers should be destroyed
|
||||||
List<int> currentPassthroughIDs = PassthroughIDList;
|
List<int> currentPassthroughIDs = PassthroughIDList;
|
||||||
foreach (int passthroughID in currentPassthroughIDs)
|
foreach (int passthroughID in currentPassthroughIDs)
|
||||||
@@ -254,7 +380,15 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
OnPassthroughSessionDestroyHandler?.Invoke(passthroughID);
|
OnPassthroughSessionDestroyHandler?.Invoke(passthroughID);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
//Notify that all passthrough layers should be destroyed
|
||||||
|
List<XrPassthroughHTC> currentPassthroughs = PassthroughList;
|
||||||
|
foreach (XrPassthroughHTC passthrough in currentPassthroughs)
|
||||||
|
{
|
||||||
|
DestroyPassthroughHTC(passthrough);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (m_HeadLockSpace != 0)
|
if (m_HeadLockSpace != 0)
|
||||||
{
|
{
|
||||||
DestroySpace(m_HeadLockSpace);
|
DestroySpace(m_HeadLockSpace);
|
||||||
@@ -272,6 +406,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current XR session state.
|
||||||
|
/// </summary>
|
||||||
public XrSessionState XrSessionCurrentState
|
public XrSessionState XrSessionCurrentState
|
||||||
{
|
{
|
||||||
get { return m_XrSessionNewState; }
|
get { return m_XrSessionNewState; }
|
||||||
@@ -301,14 +438,23 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region OpenXR function delegates
|
#region OpenXR function delegates
|
||||||
/// xrGetInstanceProcAddr
|
/// xrGetInstanceProcAddr
|
||||||
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
OpenXRHelper.xrGetInstanceProcAddrDelegate Intercept_xrGetInstanceProcAddr =
|
||||||
|
new OpenXRHelper.xrGetInstanceProcAddrDelegate(InterceptXrEndFrame_xrGetInstanceProcAddr);
|
||||||
|
#endif
|
||||||
|
private static OpenXRHelper.xrEndFrameDelegate m_intercept_xrEndFrame;
|
||||||
|
private static OpenXRHelper.xrWaitFrameDelegate m_intercept_xrWaitFrame;
|
||||||
/// xrGetSystemProperties
|
/// xrGetSystemProperties
|
||||||
OpenXRHelper.xrGetSystemPropertiesDelegate xrGetSystemProperties;
|
OpenXRHelper.xrGetSystemPropertiesDelegate xrGetSystemProperties;
|
||||||
|
/// <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 GetSystemProperties(ref XrSystemProperties properties)
|
public XrResult GetSystemProperties(ref XrSystemProperties properties)
|
||||||
{
|
{
|
||||||
if (m_XrInstanceCreated)
|
if (m_XrInstanceCreated)
|
||||||
@@ -335,6 +481,10 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
/// xrCreateReferenceSpace
|
/// xrCreateReferenceSpace
|
||||||
OpenXRHelper.xrCreateReferenceSpaceDelegate xrCreateReferenceSpace;
|
OpenXRHelper.xrCreateReferenceSpaceDelegate xrCreateReferenceSpace;
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reference space
|
||||||
|
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/man/html/xrCreateReferenceSpace.html">xrCreateReferenceSpace</see>
|
||||||
|
/// </summary>
|
||||||
public XrResult CreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
|
public XrResult CreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated)
|
if (!m_XrSessionCreated)
|
||||||
@@ -356,7 +506,55 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
}
|
}
|
||||||
return XrResult.XR_ERROR_REFERENCE_SPACE_UNSUPPORTED;
|
return XrResult.XR_ERROR_REFERENCE_SPACE_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
private List<XrPassthroughHTC> passthroughList = new List<XrPassthroughHTC>();
|
||||||
|
public List<XrPassthroughHTC> PassthroughList { get { return new List<XrPassthroughHTC>(passthroughList); } }
|
||||||
|
ViveCompositionLayerHelper.xrCreatePassthroughHTCDelegate xrCreatePassthroughHTC;
|
||||||
|
public XrResult CreatePassthroughHTC(XrPassthroughCreateInfoHTC createInfo, out XrPassthroughHTC passthrough)
|
||||||
|
{
|
||||||
|
if (!m_XrSessionCreated)
|
||||||
|
{
|
||||||
|
ERROR("CreatePassthroughHTC() XR_ERROR_SESSION_LOST.");
|
||||||
|
passthrough = 0;
|
||||||
|
return XrResult.XR_ERROR_SESSION_LOST;
|
||||||
|
}
|
||||||
|
if (!m_XrInstanceCreated)
|
||||||
|
{
|
||||||
|
ERROR("CreatePassthroughHTC() XR_ERROR_INSTANCE_LOST.");
|
||||||
|
passthrough = 0;
|
||||||
|
return XrResult.XR_ERROR_INSTANCE_LOST;
|
||||||
|
}
|
||||||
|
|
||||||
|
XrResult res = xrCreatePassthroughHTC(m_XrSession, createInfo, out passthrough);
|
||||||
|
if (res == XrResult.XR_SUCCESS)
|
||||||
|
passthroughList.Add(passthrough);
|
||||||
|
else
|
||||||
|
ERROR("CreatePassthroughHTC() "+res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViveCompositionLayerHelper.xrDestroyPassthroughHTCDelegate xrDestroyPassthroughHTC;
|
||||||
|
public XrResult DestroyPassthroughHTC(XrPassthroughHTC passthrough)
|
||||||
|
{
|
||||||
|
DEBUG("Entry");
|
||||||
|
XrResult res = xrDestroyPassthroughHTC(passthrough);
|
||||||
|
if (res == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
passthroughList.Remove(passthrough);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// According to XRInputSubsystem's tracking origin mode, return the corresponding XrSpace.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public XrSpace GetTrackingSpace()
|
||||||
|
{
|
||||||
|
var s = GetCurrentAppSpace();
|
||||||
|
Debug.Log("VivePassthrough GetTrackingSpace() s=" + s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
private bool GetXrFunctionDelegates(XrInstance xrInstance)
|
private bool GetXrFunctionDelegates(XrInstance xrInstance)
|
||||||
{
|
{
|
||||||
/// xrGetInstanceProcAddr
|
/// xrGetInstanceProcAddr
|
||||||
@@ -438,7 +636,41 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("xrDestroySpace");
|
ERROR("xrDestroySpace");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
/// xrCreatePassthroughHTC
|
||||||
|
if (XrGetInstanceProcAddr(xrInstance, "xrCreatePassthroughHTC", out funcPtr) == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (funcPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
DEBUG("Get function pointer of xrCreatePassthroughHTC.");
|
||||||
|
xrCreatePassthroughHTC = Marshal.GetDelegateForFunctionPointer(
|
||||||
|
funcPtr,
|
||||||
|
typeof(ViveCompositionLayerHelper.xrCreatePassthroughHTCDelegate)) as ViveCompositionLayerHelper.xrCreatePassthroughHTCDelegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ERROR("xrCreatePassthroughHTC");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/// xrCreatePassthroughHTC
|
||||||
|
if (XrGetInstanceProcAddr(xrInstance, "xrDestroyPassthroughHTC", out funcPtr) == XrResult.XR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (funcPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
DEBUG("Get function pointer of xrDestroyPassthroughHTC.");
|
||||||
|
xrDestroyPassthroughHTC = Marshal.GetDelegateForFunctionPointer(
|
||||||
|
funcPtr,
|
||||||
|
typeof(ViveCompositionLayerHelper.xrDestroyPassthroughHTCDelegate)) as ViveCompositionLayerHelper.xrDestroyPassthroughHTCDelegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ERROR("xrDestroyPassthroughHTC");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if UNITY_ANDROID
|
||||||
if (HTCPassthrough_GetFuncAddrs(xrInstance, xrGetInstanceProcAddr) == XrResult.XR_SUCCESS)
|
if (HTCPassthrough_GetFuncAddrs(xrInstance, xrGetInstanceProcAddr) == XrResult.XR_SUCCESS)
|
||||||
{
|
{
|
||||||
DEBUG("Get function pointers in native.");
|
DEBUG("Get function pointers in native.");
|
||||||
@@ -448,15 +680,18 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
ERROR("HTCPassthrough_GetFuncAddrs");
|
ERROR("HTCPassthrough_GetFuncAddrs");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Wrapper Functions
|
#region Wrapper Functions
|
||||||
private const string ExtLib = "viveopenxr";
|
private const string ExtLib = "viveopenxr";
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_CreatePassthrough")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_CreatePassthrough")]
|
||||||
private static extern int VIVEOpenXR_HTCPassthrough_CreatePassthrough(XrSession session, LayerType layerType, PassthroughLayerForm layerForm, uint compositionDepth = 0);
|
private static extern int VIVEOpenXR_HTCPassthrough_CreatePassthrough(XrSession session, LayerType layerType, PassthroughLayerForm layerForm, uint compositionDepth = 0);
|
||||||
|
/// <summary>
|
||||||
|
/// Create Passthrough.
|
||||||
|
/// </summary>
|
||||||
public int HTCPassthrough_CreatePassthrough(LayerType layerType, PassthroughLayerForm layerForm, OnPassthroughSessionDestroyDelegate onDestroyPassthroughHandler, uint compositionDepth = 0)
|
public int HTCPassthrough_CreatePassthrough(LayerType layerType, PassthroughLayerForm layerForm, OnPassthroughSessionDestroyDelegate onDestroyPassthroughHandler, uint compositionDepth = 0)
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated || m_XrSession == 0)
|
if (!m_XrSessionCreated || m_XrSession == 0)
|
||||||
@@ -484,6 +719,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetAlpha")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetAlpha")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_SetAlpha(int passthroughID, float alpha);
|
private static extern bool VIVEOpenXR_HTCPassthrough_SetAlpha(int passthroughID, float alpha);
|
||||||
|
/// <summary>
|
||||||
|
/// Set Passthough Alpha.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_SetAlpha(int passthroughID, float alpha)
|
public bool HTCPassthrough_SetAlpha(int passthroughID, float alpha)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -497,6 +735,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetLayerType")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetLayerType")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_SetLayerType(int passthroughID, LayerType layerType, uint compositionDepth = 0);
|
private static extern bool VIVEOpenXR_HTCPassthrough_SetLayerType(int passthroughID, LayerType layerType, uint compositionDepth = 0);
|
||||||
|
/// <summary>
|
||||||
|
/// Set Passthough Layer Type.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_SetLayerType(int passthroughID, LayerType layerType, uint compositionDepth = 0)
|
public bool HTCPassthrough_SetLayerType(int passthroughID, LayerType layerType, uint compositionDepth = 0)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -510,6 +751,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMesh")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMesh")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_SetMesh(int passthroughID, uint vertexCount, [In, Out] XrVector3f[] vertexBuffer, uint indexCount, [In, Out] uint[] indexBuffer);
|
private static extern bool VIVEOpenXR_HTCPassthrough_SetMesh(int passthroughID, uint vertexCount, [In, Out] XrVector3f[] vertexBuffer, uint indexCount, [In, Out] uint[] indexBuffer);
|
||||||
|
/// <summary>
|
||||||
|
/// Set Passthough Mesh.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_SetMesh(int passthroughID, uint vertexCount, [In, Out] XrVector3f[] vertexBuffer, uint indexCount, [In, Out] uint[] indexBuffer)
|
public bool HTCPassthrough_SetMesh(int passthroughID, uint vertexCount, [In, Out] XrVector3f[] vertexBuffer, uint indexCount, [In, Out] uint[] indexBuffer)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -523,6 +767,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransform")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransform")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransform(int passthroughID, XrSpace meshSpace, XrPosef meshPose, XrVector3f meshScale);
|
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransform(int passthroughID, XrSpace meshSpace, XrPosef meshPose, XrVector3f meshScale);
|
||||||
|
/// <summary>
|
||||||
|
/// Set Passthough Mesh Transform.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_SetMeshTransform(int passthroughID, XrSpace meshSpace, XrPosef meshPose, XrVector3f meshScale)
|
public bool HTCPassthrough_SetMeshTransform(int passthroughID, XrSpace meshSpace, XrPosef meshPose, XrVector3f meshScale)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -536,6 +783,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransformSpace")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransformSpace")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransformSpace(int passthroughID, XrSpace meshSpace);
|
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransformSpace(int passthroughID, XrSpace meshSpace);
|
||||||
|
/// <summary>
|
||||||
|
/// Set Passthough Mesh Transform Space.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_SetMeshTransformSpace(int passthroughID, XrSpace meshSpace)
|
public bool HTCPassthrough_SetMeshTransformSpace(int passthroughID, XrSpace meshSpace)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -549,6 +799,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransformPosition")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransformPosition")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransformPosition(int passthroughID, XrVector3f meshPosition);
|
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransformPosition(int passthroughID, XrVector3f meshPosition);
|
||||||
|
/// <summary>
|
||||||
|
/// Set Passthough Mesh Transform Position.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_SetMeshTransformPosition(int passthroughID, XrVector3f meshPosition)
|
public bool HTCPassthrough_SetMeshTransformPosition(int passthroughID, XrVector3f meshPosition)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -562,6 +815,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransformOrientation")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransformOrientation")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransformOrientation(int passthroughID, XrQuaternionf meshOrientation);
|
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransformOrientation(int passthroughID, XrQuaternionf meshOrientation);
|
||||||
|
/// <summary>
|
||||||
|
/// Set Passthough Mesh Transform orientation.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_SetMeshTransformOrientation(int passthroughID, XrQuaternionf meshOrientation)
|
public bool HTCPassthrough_SetMeshTransformOrientation(int passthroughID, XrQuaternionf meshOrientation)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -575,6 +831,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransformScale")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_SetMeshTransformScale")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransformScale(int passthroughID, XrVector3f meshScale);
|
private static extern bool VIVEOpenXR_HTCPassthrough_SetMeshTransformScale(int passthroughID, XrVector3f meshScale);
|
||||||
|
/// <summary>
|
||||||
|
/// Set Passthough Mesh Transform scale.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_SetMeshTransformScale(int passthroughID, XrVector3f meshScale)
|
public bool HTCPassthrough_SetMeshTransformScale(int passthroughID, XrVector3f meshScale)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -588,6 +847,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_DestroyPassthrough")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_DestroyPassthrough")]
|
||||||
private static extern bool VIVEOpenXR_HTCPassthrough_DestroyPassthrough(int passthroughID);
|
private static extern bool VIVEOpenXR_HTCPassthrough_DestroyPassthrough(int passthroughID);
|
||||||
|
/// <summary>
|
||||||
|
/// Destroy Passthough.
|
||||||
|
/// </summary>
|
||||||
public bool HTCPassthrough_DestroyPassthrough(int passthroughID)
|
public bool HTCPassthrough_DestroyPassthrough(int passthroughID)
|
||||||
{
|
{
|
||||||
if (!HTCPassthroughExtensionEnabled)
|
if (!HTCPassthroughExtensionEnabled)
|
||||||
@@ -606,9 +868,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
|
|
||||||
return destroyed;
|
return destroyed;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Hook native functions
|
#region Hook native functions
|
||||||
|
|
||||||
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_GetFuncAddrs")]
|
[DllImportAttribute(ExtLib, EntryPoint = "htcpassthrough_GetFuncAddrs")]
|
||||||
private static extern XrResult VIVEOpenXR_HTCPassthrough_GetFuncAddrs(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrFuncPtr);
|
private static extern XrResult VIVEOpenXR_HTCPassthrough_GetFuncAddrs(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrFuncPtr);
|
||||||
@@ -623,10 +885,12 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
return VIVEOpenXR_HTCPassthrough_GetFuncAddrs(xrInstance, xrGetInstanceProcAddrFuncPtr);
|
return VIVEOpenXR_HTCPassthrough_GetFuncAddrs(xrInstance, xrGetInstanceProcAddrFuncPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Helper Funcs
|
|
||||||
|
|
||||||
|
#region Helper Funcs
|
||||||
|
/// <summary>
|
||||||
|
/// Helper function to get XrSpace from space type.
|
||||||
|
/// </summary>
|
||||||
public XrSpace GetXrSpaceFromSpaceType(ProjectedPassthroughSpaceType spaceType)
|
public XrSpace GetXrSpaceFromSpaceType(ProjectedPassthroughSpaceType spaceType)
|
||||||
{
|
{
|
||||||
XrSpace meshSpace = 0;
|
XrSpace meshSpace = 0;
|
||||||
@@ -669,6 +933,6 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
|
|||||||
return meshSpace;
|
return meshSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace VIVE.OpenXR
|
|||||||
Desc = "Support the HTC foveation extension.",
|
Desc = "Support the HTC foveation extension.",
|
||||||
Company = "HTC",
|
Company = "HTC",
|
||||||
DocumentationLink = "..\\Documentation",
|
DocumentationLink = "..\\Documentation",
|
||||||
OpenxrExtensionStrings = "XR_HTC_foveation",
|
OpenxrExtensionStrings = kOpenxrExtensionString,
|
||||||
Version = "1.0.0",
|
Version = "1.0.0",
|
||||||
BuildTargetGroups = new[] { BuildTargetGroup.Android },
|
BuildTargetGroups = new[] { BuildTargetGroup.Android },
|
||||||
FeatureId = featureId
|
FeatureId = featureId
|
||||||
@@ -27,6 +27,11 @@ namespace VIVE.OpenXR
|
|||||||
#endif
|
#endif
|
||||||
public class ViveFoveation : OpenXRFeature
|
public class ViveFoveation : OpenXRFeature
|
||||||
{
|
{
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.ViveFoveation";
|
||||||
|
void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
|
||||||
|
void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); }
|
||||||
|
void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flag bits for XrFoveationDynamicFlagsHTC
|
/// Flag bits for XrFoveationDynamicFlagsHTC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -39,6 +44,31 @@ namespace VIVE.OpenXR
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string featureId = "vive.openxr.feature.foveation";
|
public const string featureId = "vive.openxr.feature.foveation";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_foveation">12.90. XR_HTC_foveation</see>.
|
||||||
|
/// </summary>
|
||||||
|
public const string kOpenxrExtensionString = "XR_HTC_foveation";
|
||||||
|
|
||||||
|
#region OpenXR Life Cycle
|
||||||
|
/// <summary>
|
||||||
|
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="xrInstance">The created instance.</param>
|
||||||
|
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
|
||||||
|
protected override bool OnInstanceCreate(ulong xrInstance)
|
||||||
|
{
|
||||||
|
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
|
||||||
|
{
|
||||||
|
WARNING("OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG("OnInstanceCreate() " + xrInstance);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||||
{
|
{
|
||||||
Debug.Log("EXT: registering our own xrGetInstanceProcAddr");
|
Debug.Log("EXT: registering our own xrGetInstanceProcAddr");
|
||||||
|
|||||||
@@ -334,8 +334,7 @@ namespace VIVE.OpenXR.Hand
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="properties">Points to an instance of the XrSystemProperties structure, that will be filled with returned information.</param>
|
/// <param name="properties">Points to an instance of the XrSystemProperties structure, that will be filled with returned information.</param>
|
||||||
/// <returns>XR_SUCCESS for success.</returns>
|
/// <returns>XR_SUCCESS for success.</returns>
|
||||||
[Obsolete("This function will become private in next release")]
|
private XrResult GetSystemProperties(ref XrSystemProperties properties)
|
||||||
public XrResult GetSystemProperties(ref XrSystemProperties properties)
|
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated)
|
if (!m_XrSessionCreated)
|
||||||
{
|
{
|
||||||
@@ -360,8 +359,7 @@ namespace VIVE.OpenXR.Hand
|
|||||||
/// <param name="spaceCountOutput">A pointer to the count of spaces written, or a pointer to the required capacity in the case that spaceCapacityInput is insufficient.</param>
|
/// <param name="spaceCountOutput">A pointer to the count of spaces written, or a pointer to the required capacity in the case that spaceCapacityInput is insufficient.</param>
|
||||||
/// <param name="spaces">A pointer to an application-allocated array that will be filled with the enumerant of each supported reference space. It can be NULL if spaceCapacityInput is 0.</param>
|
/// <param name="spaces">A pointer to an application-allocated array that will be filled with the enumerant of each supported reference space. It can be NULL if spaceCapacityInput is 0.</param>
|
||||||
/// <returns>XR_SUCCESS for success.</returns>
|
/// <returns>XR_SUCCESS for success.</returns>
|
||||||
[Obsolete("This function will become private in next release")]
|
private XrResult EnumerateReferenceSpaces(UInt32 spaceCapacityInput, out UInt32 spaceCountOutput, out XrReferenceSpaceType spaces)
|
||||||
public XrResult EnumerateReferenceSpaces(UInt32 spaceCapacityInput, out UInt32 spaceCountOutput, out XrReferenceSpaceType spaces)
|
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated)
|
if (!m_XrSessionCreated)
|
||||||
{
|
{
|
||||||
@@ -389,8 +387,7 @@ namespace VIVE.OpenXR.Hand
|
|||||||
/// <param name="createInfo">The XrReferenceSpaceCreateInfo used to specify the space.</param>
|
/// <param name="createInfo">The XrReferenceSpaceCreateInfo used to specify the space.</param>
|
||||||
/// <param name="space">The returned XrSpace handle.</param>
|
/// <param name="space">The returned XrSpace handle.</param>
|
||||||
/// <returns>XR_SUCCESS for success.</returns>
|
/// <returns>XR_SUCCESS for success.</returns>
|
||||||
[Obsolete("This function will become private in next release")]
|
private XrResult CreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
|
||||||
public XrResult CreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
|
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated)
|
if (!m_XrSessionCreated)
|
||||||
{
|
{
|
||||||
@@ -415,8 +412,7 @@ namespace VIVE.OpenXR.Hand
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="space">Must be a valid XrSpace handle.</param>
|
/// <param name="space">Must be a valid XrSpace handle.</param>
|
||||||
/// <returns>XR_SUCCESS for success.</returns>
|
/// <returns>XR_SUCCESS for success.</returns>
|
||||||
[Obsolete("This function will become private in next release")]
|
private XrResult DestroySpace(XrSpace space)
|
||||||
public XrResult DestroySpace(XrSpace space)
|
|
||||||
{
|
{
|
||||||
if (!m_XrSessionCreated)
|
if (!m_XrSessionCreated)
|
||||||
{
|
{
|
||||||
@@ -761,6 +757,11 @@ namespace VIVE.OpenXR.Hand
|
|||||||
private XrHandJointLocationEXT[] jointLocationsR = new XrHandJointLocationEXT[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT];
|
private XrHandJointLocationEXT[] jointLocationsR = new XrHandJointLocationEXT[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT];
|
||||||
private XrHandJointLocationsEXT locations = new XrHandJointLocationsEXT(XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT, IntPtr.Zero, false, 0, IntPtr.Zero);
|
private XrHandJointLocationsEXT locations = new XrHandJointLocationsEXT(XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT, IntPtr.Zero, false, 0, IntPtr.Zero);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the <see cref="XrSpace"> XrSpace </see> used in Hand Tracking.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="space">Tracking space in <see cref="XrSpace"> XrSpace </see>.</param>
|
||||||
|
/// <returns>True for valid data.</returns>
|
||||||
public bool GetHandTrackingSpace(out XrSpace space)
|
public bool GetHandTrackingSpace(out XrSpace space)
|
||||||
{
|
{
|
||||||
space = 0;
|
space = 0;
|
||||||
|
|||||||
@@ -194,61 +194,49 @@ namespace VIVE.OpenXR
|
|||||||
|
|
||||||
public bool GetUserPaths(string interactionProfileString, out XrPath[] userPaths)
|
public bool GetUserPaths(string interactionProfileString, out XrPath[] userPaths)
|
||||||
{
|
{
|
||||||
|
userPaths = null;
|
||||||
XrPathsForInteractionProfileEnumerateInfoHTC enumerateInfo;
|
XrPathsForInteractionProfileEnumerateInfoHTC enumerateInfo;
|
||||||
if (!m_XrInstanceCreated) { userPaths = null; return false; }
|
if (!m_XrInstanceCreated) { return false; }
|
||||||
|
|
||||||
string func = "GetUserPaths() ";
|
string func = "GetUserPaths() ";
|
||||||
|
|
||||||
if (xrEnumeratePathsForInteractionProfileHTC == null)
|
if (xrEnumeratePathsForInteractionProfileHTC == null)
|
||||||
{
|
{
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
sb.Clear().Append(LOG_TAG).Append(func).Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC"); WARNING(sb);
|
||||||
.Append("No function pointer of xrEnumeratePathsForInteractionProfileHTC"); WARNING(sb);
|
|
||||||
userPaths = null;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Get user path count of sepecified profile.
|
// 1. Get user path count of sepecified profile.
|
||||||
UInt32 trackerCount = 0;
|
UInt32 trackerCount = 0;
|
||||||
enumerateInfo.type = (XrStructureType)1000319000;//Todo : update openxr spec to prevent hot code.
|
enumerateInfo.type = XrStructureType.XR_TYPE_UNKNOWN;
|
||||||
enumerateInfo.next = IntPtr.Zero;
|
enumerateInfo.next = IntPtr.Zero;
|
||||||
enumerateInfo.interactionProfile = StringToPath(interactionProfileString);
|
enumerateInfo.interactionProfile = StringToPath(interactionProfileString);
|
||||||
enumerateInfo.userPath = OpenXRHelper.XR_NULL_PATH;
|
enumerateInfo.userPath = OpenXRHelper.XR_NULL_PATH;
|
||||||
|
|
||||||
XrResult result = xrEnumeratePathsForInteractionProfileHTC(m_XrInstance, ref enumerateInfo, 0, ref trackerCount, null);
|
XrResult result = xrEnumeratePathsForInteractionProfileHTC(m_XrInstance, ref enumerateInfo, 0, ref trackerCount, null);
|
||||||
|
sb.Clear().Append(LOG_TAG).Append(func).Append("xrEnumeratePathsForInteractionProfileHTC result: ").Append(result)
|
||||||
|
.Append(", profile: ").Append(interactionProfileString)
|
||||||
|
.Append(", trackerCount: ").Append(trackerCount);
|
||||||
|
DEBUG(sb);
|
||||||
|
if (result != XrResult.XR_SUCCESS || trackerCount <= 0) { return false; }
|
||||||
|
|
||||||
|
// 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)
|
if (result != XrResult.XR_SUCCESS)
|
||||||
{
|
{
|
||||||
sb.Clear().Append(LOG_TAG).Append(func)
|
sb.Clear().Append(LOG_TAG).Append(func).Append("Retrieves trackers failed."); ERROR(sb);
|
||||||
.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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userPaths = trackers;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetInputPathsWithUserPath(string interactionProfileString, XrPath userPath, out XrPath[] inputPaths)
|
public bool GetInputPathsWithUserPath(string interactionProfileString, XrPath userPath, out XrPath[] inputPaths)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -319,6 +319,10 @@ namespace VIVE.OpenXR
|
|||||||
XR_TYPE_PLANE_DETECTOR_LOCATION_EXT = 1000429005,
|
XR_TYPE_PLANE_DETECTOR_LOCATION_EXT = 1000429005,
|
||||||
XR_TYPE_PLANE_DETECTOR_POLYGON_BUFFER_EXT = 1000429006,
|
XR_TYPE_PLANE_DETECTOR_POLYGON_BUFFER_EXT = 1000429006,
|
||||||
XR_TYPE_SYSTEM_PLANE_DETECTION_PROPERTIES_EXT = 1000429007,
|
XR_TYPE_SYSTEM_PLANE_DETECTION_PROPERTIES_EXT = 1000429007,
|
||||||
|
XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC = 1000317001,
|
||||||
|
XR_TYPE_PASSTHROUGH_COLOR_HTC = 1000317002,
|
||||||
|
XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC = 1000317003,
|
||||||
|
XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC = 1000317004,
|
||||||
XR_TYPE_SYSTEM_ANCHOR_PROPERTIES_HTC = 1000319000,
|
XR_TYPE_SYSTEM_ANCHOR_PROPERTIES_HTC = 1000319000,
|
||||||
XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC = 1000319001,
|
XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC = 1000319001,
|
||||||
XR_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
XR_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||||
@@ -2007,6 +2011,48 @@ namespace VIVE.OpenXR
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool shouldRender;
|
public bool shouldRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A structure indicates the XrFrameEndInfo .
|
||||||
|
/// </summary>
|
||||||
|
public struct XrFrameEndInfo
|
||||||
|
{
|
||||||
|
/// <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>
|
||||||
|
/// displayTime is the XrTime at which this frame should be displayed.
|
||||||
|
/// </summary>
|
||||||
|
public XrTime displayTime;
|
||||||
|
/// <summary>
|
||||||
|
/// environmentBlendMode is the XrEnvironmentBlendMode value representing the desired environment blend mode for this frame.
|
||||||
|
/// </summary>
|
||||||
|
public XrEnvironmentBlendMode environmentBlendMode;
|
||||||
|
/// <summary>
|
||||||
|
/// layerCount is the number of composition layers in this frame. The maximum supported layer count is identified by XrSystemGraphicsProperties::maxLayerCount.
|
||||||
|
/// </summary>
|
||||||
|
public uint layerCount;
|
||||||
|
/// <summary>
|
||||||
|
/// layers is a pointer to an array of XrCompositionLayerBaseHeader pointers.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr layers;
|
||||||
|
|
||||||
|
public XrFrameEndInfo(XrStructureType type_,IntPtr next_, XrTime displayTime_, XrEnvironmentBlendMode environmentBlendMode_
|
||||||
|
,uint layerCount_, IntPtr layers_)
|
||||||
|
{
|
||||||
|
next = next_;
|
||||||
|
type = type_;
|
||||||
|
displayTime = displayTime_;
|
||||||
|
environmentBlendMode = environmentBlendMode_;
|
||||||
|
layerCount = layerCount_;
|
||||||
|
layers = layers_;
|
||||||
|
}
|
||||||
|
}
|
||||||
public static class OpenXRHelper
|
public static class OpenXRHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -2449,7 +2495,7 @@ namespace VIVE.OpenXR
|
|||||||
/// <param name="frameState">frameState is a pointer to a valid XrFrameState, an output parameter.</param>
|
/// <param name="frameState">frameState is a pointer to a valid XrFrameState, an output parameter.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public delegate int xrWaitFrameDelegate(ulong session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState);
|
public delegate int xrWaitFrameDelegate(ulong session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState);
|
||||||
|
public delegate XrResult xrEndFrameDelegate(XrSession session, ref XrFrameEndInfo frameEndInfo);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Help call xrGetInstanceProcAddr and convert the result to delegate.\
|
/// Help call xrGetInstanceProcAddr and convert the result to delegate.\
|
||||||
/// For example, "OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrGetSystemProperties", out XrGetSystemProperties);"
|
/// For example, "OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrGetSystemProperties", out XrGetSystemProperties);"
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using VIVE.OpenXR.CompositionLayer;
|
||||||
|
using VIVE.OpenXR.CompositionLayer.Passthrough;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR
|
||||||
|
{
|
||||||
|
public class XR_HTC_passthrough_defs
|
||||||
|
{
|
||||||
|
public virtual XrResult xrCreatePassthroughHTC(XrPassthroughCreateInfoHTC createInfo, out XrPassthroughHTC passthrough)
|
||||||
|
{
|
||||||
|
passthrough = 0;
|
||||||
|
return XrResult.XR_ERROR_RUNTIME_FAILURE;
|
||||||
|
}
|
||||||
|
public virtual XrResult xrDestroyPassthroughHTC(XrPassthroughHTC passthrough)
|
||||||
|
{
|
||||||
|
return XrResult.XR_ERROR_RUNTIME_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void GetOriginEndFrameLayerList(out List<IntPtr> layers)
|
||||||
|
{
|
||||||
|
layers = new List<IntPtr>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SubmitLayers(List<IntPtr> layers)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public virtual XrSpace GetTrackingSpace()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual XrFrameState GetFrameState()
|
||||||
|
{
|
||||||
|
return new XrFrameState();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public static class XR_HTC_passthrough
|
||||||
|
{
|
||||||
|
static XR_HTC_passthrough_defs m_Instance = null;
|
||||||
|
public static XR_HTC_passthrough_defs Interop
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_Instance == null)
|
||||||
|
{
|
||||||
|
m_Instance = new XR_HTC_passthrough_impls();
|
||||||
|
}
|
||||||
|
return m_Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XrResult xrCreatePassthroughHTC(XrPassthroughCreateInfoHTC createInfo, out XrPassthroughHTC passthrough)
|
||||||
|
{
|
||||||
|
return Interop.xrCreatePassthroughHTC(createInfo,out passthrough);
|
||||||
|
}
|
||||||
|
public static XrResult xrDestroyPassthroughHTC(XrPassthroughHTC passthrough)
|
||||||
|
{
|
||||||
|
return Interop.xrDestroyPassthroughHTC(passthrough);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: ba0bbb9482b5a5d479cf11f2253cdc3e
|
guid: 099596ee936f4724f866de5335ef9cf6
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.XR.OpenXR;
|
||||||
|
using VIVE.OpenXR.CompositionLayer;
|
||||||
|
using VIVE.OpenXR.CompositionLayer.Passthrough;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR
|
||||||
|
{
|
||||||
|
public class XR_HTC_passthrough_impls : XR_HTC_passthrough_defs
|
||||||
|
{
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.XR_HTC_passthrough_impls";
|
||||||
|
void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
|
||||||
|
public XR_HTC_passthrough_impls() { DEBUG("XR_HTC_passthrough_impls()"); }
|
||||||
|
private ViveCompositionLayerPassthrough feature = null;
|
||||||
|
|
||||||
|
private void ASSERT_FEATURE()
|
||||||
|
{
|
||||||
|
if (feature == null) { feature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayerPassthrough>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override XrResult xrCreatePassthroughHTC(XrPassthroughCreateInfoHTC createInfo, out XrPassthroughHTC passthrough)
|
||||||
|
{
|
||||||
|
XrResult result = XrResult.XR_ERROR_VALIDATION_FAILURE;
|
||||||
|
passthrough = 0;
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if(feature)
|
||||||
|
result = feature.CreatePassthroughHTC(createInfo,out passthrough);
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public override XrResult xrDestroyPassthroughHTC(XrPassthroughHTC passthrough)
|
||||||
|
{
|
||||||
|
XrResult result = XrResult.XR_ERROR_VALIDATION_FAILURE;
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if(feature)
|
||||||
|
result = feature.DestroyPassthroughHTC(passthrough);
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GetOriginEndFrameLayerList(out List<IntPtr> layers)
|
||||||
|
{
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
layers = new List<IntPtr>();
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (feature)
|
||||||
|
feature.GetOriginEndFrameLayerList(out layers);
|
||||||
|
|
||||||
|
else
|
||||||
|
layers = new List<IntPtr>();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
public override void SubmitLayers(List<IntPtr> layers)
|
||||||
|
{
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (feature)
|
||||||
|
feature.SubmitLayers(layers);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public override XrSpace GetTrackingSpace()
|
||||||
|
{
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (feature)
|
||||||
|
return feature.GetTrackingSpace();
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override XrFrameState GetFrameState()
|
||||||
|
{
|
||||||
|
ASSERT_FEATURE();
|
||||||
|
#if UNITY_STANDALONE
|
||||||
|
if (feature)
|
||||||
|
return feature.GetFrameState();
|
||||||
|
#endif
|
||||||
|
return new XrFrameState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: b9b8d210c92da6a49ac85755f7b15cbb
|
guid: 97968beb8cc921d4cbebb8e3a2bc3d6c
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -31,7 +31,7 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
public class Plane
|
public class Plane
|
||||||
{
|
{
|
||||||
public Vector3 scale; // Only width(X) and height(Y) are valid, Z is always 1.
|
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 center; // Should always be Vector3.Zero.
|
||||||
public Vector3[] verticesRaw; // The original vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/>
|
public Vector3[] verticesRaw; // The original vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/>
|
||||||
public Vector3[] verticesGenerated; // generated vertices for creating Mesh.
|
public Vector3[] verticesGenerated; // generated vertices for creating Mesh.
|
||||||
public Vector2[] uvsGenerated;
|
public Vector2[] uvsGenerated;
|
||||||
@@ -94,7 +94,12 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
IntPtr planeDetector = IntPtr.Zero;
|
IntPtr planeDetector = IntPtr.Zero;
|
||||||
VivePlaneDetection feature = null;
|
VivePlaneDetection feature = null;
|
||||||
|
|
||||||
public PlaneDetector(IntPtr pd, VivePlaneDetection f)
|
/// <summary>
|
||||||
|
/// Should not create PlaneDetector directly. Use <see cref="PlaneDetectionManager.CreatePlaneDetector" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pd">The native handle of plane detector</param>
|
||||||
|
/// <param name="f">the feature</param>
|
||||||
|
internal PlaneDetector(IntPtr pd, VivePlaneDetection f)
|
||||||
{
|
{
|
||||||
feature = f;
|
feature = f;
|
||||||
planeDetector = pd;
|
planeDetector = pd;
|
||||||
@@ -184,10 +189,10 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
|
|
||||||
locations = new List<PlaneDetectorLocation>();
|
locations = new List<PlaneDetectorLocation>();
|
||||||
|
|
||||||
// The plane's neutral pose is horizontal, and not like the plane pose in unity which is vertical.
|
// 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.
|
// 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.
|
// In Unity, the rotation applied order is in ZXY order.
|
||||||
Quaternion forward = Quaternion.Euler(-90, 180, 0);
|
Quaternion forward = Quaternion.Euler(-90, 180, 0);
|
||||||
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
|
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
|
||||||
{
|
{
|
||||||
XrPlaneDetectorLocationEXT location = locationsArray[i];
|
XrPlaneDetectorLocationEXT location = locationsArray[i];
|
||||||
@@ -273,9 +278,9 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
public static class PlaneDetectionManager
|
public static class PlaneDetectionManager
|
||||||
{
|
{
|
||||||
static VivePlaneDetection feature = null;
|
static VivePlaneDetection feature = null;
|
||||||
static bool isSupported = false;
|
static bool isSupported = false;
|
||||||
|
|
||||||
static void CheckFeature()
|
static void CheckFeature()
|
||||||
{
|
{
|
||||||
if (feature != null) return;
|
if (feature != null) return;
|
||||||
feature = OpenXRSettings.Instance.GetFeature<VivePlaneDetection>();
|
feature = OpenXRSettings.Instance.GetFeature<VivePlaneDetection>();
|
||||||
@@ -289,16 +294,16 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static VivePlaneDetection GetFeature()
|
public static VivePlaneDetection GetFeature()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CheckFeature();
|
CheckFeature();
|
||||||
}
|
}
|
||||||
catch (NotSupportedException)
|
catch (NotSupportedException)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("PlaneDetection feature is not enabled");
|
Debug.LogWarning("PlaneDetection feature is not enabled");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -308,7 +313,7 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
public static bool IsSupported()
|
public static bool IsSupported()
|
||||||
{
|
{
|
||||||
if (GetFeature() == null) return false;
|
if (GetFeature() == null) return false;
|
||||||
if (isSupported) return true;
|
if (isSupported) return true;
|
||||||
if (feature == null) return false;
|
if (feature == null) return false;
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
@@ -317,9 +322,9 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
{
|
{
|
||||||
Debug.Log("PlaneDetection: IsSupported() properties.supportedFeatures: " + properties.supportedFeatures);
|
Debug.Log("PlaneDetection: IsSupported() properties.supportedFeatures: " + properties.supportedFeatures);
|
||||||
ret = (properties.supportedFeatures & CAPABILITY_PLANE_DETECTION_BIT_EXT) > 0;
|
ret = (properties.supportedFeatures & CAPABILITY_PLANE_DETECTION_BIT_EXT) > 0;
|
||||||
isSupported = ret;
|
isSupported = ret;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Log("PlaneDetection: IsSupported() GetSystemProperties failed.");
|
Debug.Log("PlaneDetection: IsSupported() GetSystemProperties failed.");
|
||||||
}
|
}
|
||||||
@@ -342,6 +347,7 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// Plane detector is a session of detect plane. You don't need to create multiple plane detector in VIVE's implemention. You need destroy it.
|
||||||
|
/// Should call <see cref="IsSupported"/> first to check if the feature is supported.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>PlaneDetector's handle</returns>
|
/// <returns>PlaneDetector's handle</returns>
|
||||||
public static PlaneDetector CreatePlaneDetector()
|
public static PlaneDetector CreatePlaneDetector()
|
||||||
@@ -349,6 +355,8 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
CheckFeature();
|
CheckFeature();
|
||||||
if (feature == null)
|
if (feature == null)
|
||||||
return null;
|
return null;
|
||||||
|
if (IsSupported() == false)
|
||||||
|
return null;
|
||||||
|
|
||||||
var createInfo = MakeXrPlaneDetectorCreateInfoEXT();
|
var createInfo = MakeXrPlaneDetectorCreateInfoEXT();
|
||||||
var ret = feature.CreatePlaneDetector(createInfo, out var planeDetector);
|
var ret = feature.CreatePlaneDetector(createInfo, out var planeDetector);
|
||||||
@@ -370,4 +378,4 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
|||||||
feature.DestroyPlaneDetector(pd.GetDetectorRaw());
|
feature.DestroyPlaneDetector(pd.GetDetectorRaw());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,382 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditorInternal;
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(HandGrabInteractable))]
|
||||||
|
public class HandGrabInteractableEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private static HandGrabInteractable activeGrabbable = null;
|
||||||
|
private HandGrabInteractable handGrabbable = null;
|
||||||
|
private SerializedProperty m_IsGrabbable, m_FingerRequirement, m_Rigidbody, m_GrabPoses, m_ShowAllIndicator, m_OnBeginGrabbed, m_OnEndGrabbed,
|
||||||
|
m_OneHandContraintMovement, m_PreviewIndex, grabPoseName, gestureThumbPose, gestureIndexPose, gestureMiddlePose, gestureRingPose, gesturePinkyPose,
|
||||||
|
recordedGrabRotations, isLeft, enableIndicator, autoIndicator, indicatorObject, grabOffset;
|
||||||
|
private ReorderableList grabPoseList;
|
||||||
|
private bool showGrabPoses = false;
|
||||||
|
private bool showConstraint = false;
|
||||||
|
private bool showEvent = false;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
handGrabbable = target as HandGrabInteractable;
|
||||||
|
|
||||||
|
m_IsGrabbable = serializedObject.FindProperty("m_IsGrabbable");
|
||||||
|
m_FingerRequirement = serializedObject.FindProperty("m_FingerRequirement");
|
||||||
|
m_Rigidbody = serializedObject.FindProperty("m_Rigidbody");
|
||||||
|
m_GrabPoses = serializedObject.FindProperty("m_GrabPoses");
|
||||||
|
m_ShowAllIndicator = serializedObject.FindProperty("m_ShowAllIndicator");
|
||||||
|
m_OnBeginGrabbed = serializedObject.FindProperty("m_OnBeginGrabbed");
|
||||||
|
m_OnEndGrabbed = serializedObject.FindProperty("m_OnEndGrabbed");
|
||||||
|
m_OneHandContraintMovement = serializedObject.FindProperty("m_OneHandContraintMovement");
|
||||||
|
m_PreviewIndex = serializedObject.FindProperty("m_PreviewIndex");
|
||||||
|
|
||||||
|
#region ReorderableList
|
||||||
|
grabPoseList = new ReorderableList(serializedObject, m_GrabPoses, true, true, true, true);
|
||||||
|
|
||||||
|
grabPoseList.drawHeaderCallback = (Rect rect) =>
|
||||||
|
{
|
||||||
|
EditorGUI.LabelField(rect, "Grab Pose List");
|
||||||
|
};
|
||||||
|
|
||||||
|
grabPoseList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
|
||||||
|
{
|
||||||
|
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return; }
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(grabPoseName.stringValue))
|
||||||
|
{
|
||||||
|
grabPoseName.stringValue = $"Grab Pose {index + 1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect elementRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
|
||||||
|
grabPoseName.stringValue = EditorGUI.TextField(elementRect, grabPoseName.stringValue);
|
||||||
|
|
||||||
|
DrawGrabGesture(ref elementRect);
|
||||||
|
DrawHandedness(ref elementRect);
|
||||||
|
DrawIndicator(ref elementRect);
|
||||||
|
DrawMirrorButton(ref elementRect);
|
||||||
|
DrawPoseOffset(ref elementRect);
|
||||||
|
DrawFineTune(ref elementRect, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
grabPoseList.elementHeightCallback = (int index) =>
|
||||||
|
{
|
||||||
|
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return EditorGUIUtility.singleLineHeight; }
|
||||||
|
|
||||||
|
// Including Title, Handness, Show Indicator, Mirror Pose, Position, Rotation, Fine Tune
|
||||||
|
int minHeight = 7;
|
||||||
|
// To Show GrabGesture
|
||||||
|
if (recordedGrabRotations.arraySize == 0)
|
||||||
|
{
|
||||||
|
minHeight += 5;
|
||||||
|
}
|
||||||
|
if (enableIndicator.boolValue)
|
||||||
|
{
|
||||||
|
// To Show Auto Indicator
|
||||||
|
minHeight += 1;
|
||||||
|
// To Show Indicator Gameobject
|
||||||
|
if (!autoIndicator.boolValue)
|
||||||
|
{
|
||||||
|
minHeight += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EditorGUIUtility.singleLineHeight * minHeight + EditorGUIUtility.standardVerticalSpacing * minHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
grabPoseList.onAddCallback = (ReorderableList list) =>
|
||||||
|
{
|
||||||
|
m_GrabPoses.arraySize++;
|
||||||
|
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(list.count - 1)))
|
||||||
|
{
|
||||||
|
grabPoseName.stringValue = $"Grab Pose {list.count}";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
EditorGUILayout.PropertyField(m_Rigidbody);
|
||||||
|
EditorGUILayout.PropertyField(m_IsGrabbable);
|
||||||
|
EditorGUILayout.PropertyField(m_FingerRequirement);
|
||||||
|
showGrabPoses = EditorGUILayout.Foldout(showGrabPoses, "Grab Pose Settings");
|
||||||
|
if (showGrabPoses)
|
||||||
|
{
|
||||||
|
if (m_GrabPoses.arraySize == 0)
|
||||||
|
{
|
||||||
|
grabPoseList.elementHeight = EditorGUIUtility.singleLineHeight;
|
||||||
|
}
|
||||||
|
grabPoseList.DoLayoutList();
|
||||||
|
|
||||||
|
bool isToggle = EditorGUILayout.Toggle("Show All Indicator", m_ShowAllIndicator.boolValue);
|
||||||
|
if (isToggle != m_ShowAllIndicator.boolValue)
|
||||||
|
{
|
||||||
|
m_ShowAllIndicator.boolValue = isToggle;
|
||||||
|
for (int i = 0; i < m_GrabPoses.arraySize; i++)
|
||||||
|
{
|
||||||
|
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(i)))
|
||||||
|
{
|
||||||
|
enableIndicator.boolValue = m_ShowAllIndicator.boolValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showEvent = EditorGUILayout.Foldout(showEvent, "Grabbed Event");
|
||||||
|
if (showEvent)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_OnBeginGrabbed);
|
||||||
|
EditorGUILayout.PropertyField(m_OnEndGrabbed);
|
||||||
|
}
|
||||||
|
|
||||||
|
showConstraint = EditorGUILayout.Foldout(showConstraint, "Constraint Movement (Optional)");
|
||||||
|
if (showConstraint)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_OneHandContraintMovement);
|
||||||
|
}
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool UpdateGrabPose(SerializedProperty grabPose)
|
||||||
|
{
|
||||||
|
SerializedProperty indicator = grabPose.FindPropertyRelative("indicator");
|
||||||
|
if (grabPose == null || indicator == null) { return false; }
|
||||||
|
|
||||||
|
grabPoseName = grabPose.FindPropertyRelative("grabPoseName");
|
||||||
|
gestureThumbPose = grabPose.FindPropertyRelative("handGrabGesture.thumbPose");
|
||||||
|
gestureIndexPose = grabPose.FindPropertyRelative("handGrabGesture.indexPose");
|
||||||
|
gestureMiddlePose = grabPose.FindPropertyRelative("handGrabGesture.middlePose");
|
||||||
|
gestureRingPose = grabPose.FindPropertyRelative("handGrabGesture.ringPose");
|
||||||
|
gesturePinkyPose = grabPose.FindPropertyRelative("handGrabGesture.pinkyPose");
|
||||||
|
recordedGrabRotations = grabPose.FindPropertyRelative("recordedGrabRotations");
|
||||||
|
isLeft = grabPose.FindPropertyRelative("isLeft");
|
||||||
|
enableIndicator = indicator.FindPropertyRelative("enableIndicator");
|
||||||
|
autoIndicator = indicator.FindPropertyRelative("autoIndicator");
|
||||||
|
indicatorObject = indicator.FindPropertyRelative("target");
|
||||||
|
grabOffset = grabPose.FindPropertyRelative("grabOffset");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddElementHeight(ref Rect rect)
|
||||||
|
{
|
||||||
|
rect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGrabGesture(ref Rect rect)
|
||||||
|
{
|
||||||
|
if (recordedGrabRotations.arraySize == 0)
|
||||||
|
{
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
EditorGUI.PropertyField(rect, gestureThumbPose);
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
EditorGUI.PropertyField(rect, gestureIndexPose);
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
EditorGUI.PropertyField(rect, gestureMiddlePose);
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
EditorGUI.PropertyField(rect, gestureRingPose);
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
EditorGUI.PropertyField(rect, gesturePinkyPose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHandedness(ref Rect rect)
|
||||||
|
{
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
bool isToggle = EditorGUI.Toggle(rect, "Is Left", isLeft.boolValue);
|
||||||
|
if (isToggle != isLeft.boolValue)
|
||||||
|
{
|
||||||
|
isLeft.boolValue = isToggle;
|
||||||
|
SwitchRotations(ref recordedGrabRotations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawIndicator(ref Rect rect)
|
||||||
|
{
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
enableIndicator.boolValue = EditorGUI.Toggle(rect, "Show Indicator", enableIndicator.boolValue);
|
||||||
|
if (enableIndicator.boolValue)
|
||||||
|
{
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
autoIndicator.boolValue = EditorGUI.Toggle(rect, "Auto Generator Indicator", autoIndicator.boolValue);
|
||||||
|
if (!autoIndicator.boolValue)
|
||||||
|
{
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
indicatorObject.objectReferenceValue = (GameObject)EditorGUI.ObjectField(rect, "Indicator", (GameObject)indicatorObject.objectReferenceValue, typeof(GameObject), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_ShowAllIndicator.boolValue = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMirrorButton(ref Rect rect)
|
||||||
|
{
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
Rect labelRect = new Rect(rect.x, rect.y, EditorGUIUtility.labelWidth, rect.height);
|
||||||
|
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Mirror Pose"));
|
||||||
|
|
||||||
|
Rect mirrorXRect = new Rect(rect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, rect.y,
|
||||||
|
(rect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 3, rect.height);
|
||||||
|
Rect mirrorYRect = new Rect(mirrorXRect.x + mirrorXRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, mirrorXRect.width, rect.height);
|
||||||
|
Rect mirrorZRect = new Rect(mirrorYRect.x + mirrorYRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, mirrorYRect.width, rect.height);
|
||||||
|
if (GUI.Button(mirrorXRect, "Align X axis"))
|
||||||
|
{
|
||||||
|
MirrorPose(ref grabOffset, Vector3.right);
|
||||||
|
}
|
||||||
|
if (GUI.Button(mirrorYRect, "Align Y axis"))
|
||||||
|
{
|
||||||
|
MirrorPose(ref grabOffset, Vector3.up);
|
||||||
|
}
|
||||||
|
if (GUI.Button(mirrorZRect, "Align Z axis"))
|
||||||
|
{
|
||||||
|
MirrorPose(ref grabOffset, Vector3.forward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPoseOffset(ref Rect rect)
|
||||||
|
{
|
||||||
|
SerializedProperty srcPos = grabOffset.FindPropertyRelative("sourcePosition");
|
||||||
|
SerializedProperty srcRot = grabOffset.FindPropertyRelative("sourceRotation");
|
||||||
|
SerializedProperty tgtPos = grabOffset.FindPropertyRelative("targetPosition");
|
||||||
|
SerializedProperty tgtRot = grabOffset.FindPropertyRelative("targetRotation");
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
EditorGUI.Vector3Field(rect, "Position Offset", tgtPos.vector3Value - srcPos.vector3Value);
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
Vector3 rotEulerAngles = (Quaternion.Inverse(srcRot.quaternionValue) * tgtRot.quaternionValue).eulerAngles;
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
if (rotEulerAngles[i] > 180)
|
||||||
|
{
|
||||||
|
rotEulerAngles[i] = 360.0f - rotEulerAngles[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditorGUI.Vector3Field(rect, "Rotation Offset", rotEulerAngles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFineTune(ref Rect rect, int index)
|
||||||
|
{
|
||||||
|
AddElementHeight(ref rect);
|
||||||
|
Rect labelRect = new Rect(rect.x, rect.y, EditorGUIUtility.labelWidth, rect.height);
|
||||||
|
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Fine Tune"));
|
||||||
|
|
||||||
|
Rect previewRect = new Rect(rect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, rect.y,
|
||||||
|
(rect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 2, rect.height);
|
||||||
|
Rect updateRect = new Rect(previewRect.x + previewRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, previewRect.width, rect.height);
|
||||||
|
if (GUI.Button(previewRect, "Preview Grab Pose") && Application.isPlaying)
|
||||||
|
{
|
||||||
|
activeGrabbable = handGrabbable;
|
||||||
|
m_PreviewIndex.intValue = index;
|
||||||
|
ShowMeshHandPose();
|
||||||
|
}
|
||||||
|
GUI.enabled = activeGrabbable == handGrabbable && m_PreviewIndex.intValue == index;
|
||||||
|
if (GUI.Button(updateRect, "Update Grab Pose"))
|
||||||
|
{
|
||||||
|
UpdateGrabPose();
|
||||||
|
}
|
||||||
|
GUI.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert the rotation of joints of the current hand into those of another hand.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rotations">Rotation of joints of the current hand.</param>
|
||||||
|
private void SwitchRotations(ref SerializedProperty rotations)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < rotations.arraySize; i++)
|
||||||
|
{
|
||||||
|
Quaternion rotation = rotations.GetArrayElementAtIndex(i).quaternionValue;
|
||||||
|
Quaternion newRotation = Quaternion.Euler(rotation.eulerAngles.x, -rotation.eulerAngles.y, -rotation.eulerAngles.z);
|
||||||
|
rotations.GetArrayElementAtIndex(i).quaternionValue = newRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the pose properties (position and rotation) of a serialized object along a specified mirror axis.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pose">The serialized property representing the pose to be mirrored.</param>
|
||||||
|
/// <param name="mirrorAxis">The axis along which the mirroring should occur.</param>
|
||||||
|
private void MirrorPose(ref SerializedProperty pose, Vector3 mirrorAxis)
|
||||||
|
{
|
||||||
|
Vector3 sourcePosition = grabOffset.FindPropertyRelative("sourcePosition").vector3Value;
|
||||||
|
Quaternion sourceRotation = grabOffset.FindPropertyRelative("sourceRotation").quaternionValue;
|
||||||
|
Vector3 targetPosition = grabOffset.FindPropertyRelative("targetPosition").vector3Value;
|
||||||
|
Quaternion targetRotation = grabOffset.FindPropertyRelative("targetRotation").quaternionValue;
|
||||||
|
Vector3 reflectNormal = targetRotation * mirrorAxis;
|
||||||
|
|
||||||
|
Vector3 diffPos = sourcePosition - targetPosition;
|
||||||
|
Vector3 mirrorPosition = targetPosition + Vector3.Reflect(diffPos, reflectNormal);
|
||||||
|
pose.FindPropertyRelative("sourcePosition").vector3Value = mirrorPosition;
|
||||||
|
|
||||||
|
Vector3 sourceForward = sourceRotation * Vector3.forward;
|
||||||
|
Vector3 sourceUp = sourceRotation * Vector3.up;
|
||||||
|
Quaternion mirroredRotation = Quaternion.LookRotation(Vector3.Reflect(sourceForward, reflectNormal), Vector3.Reflect(sourceUp, reflectNormal));
|
||||||
|
pose.FindPropertyRelative("sourceRotation").quaternionValue = mirroredRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtain the MeshHand and set its position and rotation based on the grabOffset of grabpose.
|
||||||
|
/// </summary>
|
||||||
|
private void ShowMeshHandPose()
|
||||||
|
{
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(isLeft.boolValue ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
|
||||||
|
if (handPose != null && handPose is MeshHandPose meshHandPose)
|
||||||
|
{
|
||||||
|
GrabOffset grabOffsetObj = handGrabbable.grabPoses[m_PreviewIndex.intValue].grabOffset;
|
||||||
|
Quaternion handRot = handGrabbable.transform.rotation * Quaternion.Inverse(grabOffsetObj.rotOffset);
|
||||||
|
Quaternion handRotDiff = handRot * Quaternion.Inverse(grabOffsetObj.sourceRotation);
|
||||||
|
Vector3 handPos = handGrabbable.transform.position - handRotDiff * grabOffsetObj.posOffset;
|
||||||
|
meshHandPose.SetJointPose(JointType.Wrist, new Pose(handPos, handRot));
|
||||||
|
|
||||||
|
foreach (JointType joint in Enum.GetValues(typeof(JointType)))
|
||||||
|
{
|
||||||
|
if (joint == JointType.Wrist || joint == JointType.Count) { continue; }
|
||||||
|
|
||||||
|
meshHandPose.GetPosition(joint, out Vector3 pos, local: true);
|
||||||
|
Quaternion rot = recordedGrabRotations.GetArrayElementAtIndex((int)joint).quaternionValue;
|
||||||
|
meshHandPose.SetJointPose(joint, new Pose(pos, rot), local: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the grabpose based on position and rotation of the MeshHand and Object.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateGrabPose()
|
||||||
|
{
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(isLeft.boolValue ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
|
||||||
|
if (handPose != null && handPose is MeshHandPose meshHandPose)
|
||||||
|
{
|
||||||
|
meshHandPose.GetPosition(JointType.Wrist, out Vector3 wristPosition);
|
||||||
|
meshHandPose.GetRotation(JointType.Wrist, out Quaternion wristRotation);
|
||||||
|
|
||||||
|
Quaternion[] fingerJointRotation = new Quaternion[(int)JointType.Count];
|
||||||
|
for (int i = 0; i < fingerJointRotation.Length; i++)
|
||||||
|
{
|
||||||
|
meshHandPose.GetRotation((JointType)i, out Quaternion jointRotation, local: true);
|
||||||
|
fingerJointRotation[i] = jointRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrabPose grabPose = handGrabbable.grabPoses[m_PreviewIndex.intValue];
|
||||||
|
grabPose.Update(grabPoseName.stringValue, fingerJointRotation, isLeft.boolValue);
|
||||||
|
grabPose.grabOffset.Update(wristPosition, wristRotation, handGrabbable.transform.position, handGrabbable.transform.rotation);
|
||||||
|
handGrabbable.grabPoses[m_PreviewIndex.intValue] = grabPose;
|
||||||
|
GrabbablePoseRecorder.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(HandGrabInteractor))]
|
||||||
|
public class HandGrabInteractorEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private SerializedProperty m_Handedness, m_GrabDistance, m_OnBeginGrab, m_OnEndGrab;
|
||||||
|
private bool showEvent = false;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
m_Handedness = serializedObject.FindProperty("m_Handedness");
|
||||||
|
m_GrabDistance = serializedObject.FindProperty("m_GrabDistance");
|
||||||
|
m_OnBeginGrab = serializedObject.FindProperty("m_OnBeginGrab");
|
||||||
|
m_OnEndGrab = serializedObject.FindProperty("m_OnEndGrab");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
EditorGUILayout.PropertyField(m_Handedness);
|
||||||
|
EditorGUILayout.PropertyField(m_GrabDistance);
|
||||||
|
showEvent = EditorGUILayout.Foldout(showEvent, "Grab Event");
|
||||||
|
if (showEvent)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_OnBeginGrab);
|
||||||
|
EditorGUILayout.PropertyField(m_OnEndGrab);
|
||||||
|
|
||||||
|
}
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a8e7c213fed939e4b859638cde97a31c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(HandMeshManager))]
|
||||||
|
public class HandMeshManagerEditor : Editor
|
||||||
|
{
|
||||||
|
private HandMeshManager m_HandMesh;
|
||||||
|
private SerializedProperty m_Handedness, m_EnableCollider, m_HandJoints;
|
||||||
|
|
||||||
|
private bool showJoints = false;
|
||||||
|
public static readonly GUIContent findJoints = EditorGUIUtility.TrTextContent("Find Joints");
|
||||||
|
public static readonly GUIContent clearJoints = EditorGUIUtility.TrTextContent("All Clear");
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
m_HandMesh = target as HandMeshManager;
|
||||||
|
m_Handedness = serializedObject.FindProperty("m_Handedness");
|
||||||
|
m_EnableCollider = serializedObject.FindProperty("m_EnableCollider");
|
||||||
|
m_HandJoints = serializedObject.FindProperty("m_HandJoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox("Please check if your model is used to bind left hand poses", MessageType.None);
|
||||||
|
EditorGUILayout.PropertyField(m_Handedness, new GUIContent("Handedness"));
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox("Please check if you want the hand model with collision enabled.", MessageType.None);
|
||||||
|
EditorGUILayout.PropertyField(m_EnableCollider, new GUIContent("Enable Collider"));
|
||||||
|
|
||||||
|
showJoints = EditorGUILayout.Foldout(showJoints, "Hand Bones Reference");
|
||||||
|
if (showJoints)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Please change rotation to make sure your model should palm faces forward and fingers points up in global axis.", MessageType.Info);
|
||||||
|
|
||||||
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
|
{
|
||||||
|
using (new EditorGUI.DisabledScope())
|
||||||
|
{
|
||||||
|
if (GUILayout.Button(findJoints))
|
||||||
|
{
|
||||||
|
m_HandMesh.FindJoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (new EditorGUI.DisabledScope())
|
||||||
|
{
|
||||||
|
if (GUILayout.Button(clearJoints))
|
||||||
|
{
|
||||||
|
m_HandMesh.ClearJoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDetected = false;
|
||||||
|
for (int i = 0; i < m_HandJoints.arraySize; i++)
|
||||||
|
{
|
||||||
|
SerializedProperty bone = m_HandJoints.GetArrayElementAtIndex(i);
|
||||||
|
Transform boneTransform = (Transform)bone.objectReferenceValue;
|
||||||
|
if (boneTransform != null)
|
||||||
|
{
|
||||||
|
isDetected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isDetected)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_HandJoints.arraySize; i++)
|
||||||
|
{
|
||||||
|
SerializedProperty bone = m_HandJoints.GetArrayElementAtIndex(i);
|
||||||
|
EditorGUILayout.PropertyField(bone, new GUIContent(((JointType)i).ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: cc4f135068c02f84da2f529a300f9ba7
|
guid: 3744e08fd384e154c82d53686f0c82a6
|
||||||
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 44fadaef2720de846af62a7bbb2ec370
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!91 &9100000
|
||||||
|
AnimatorController:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: LeftHandGrab
|
||||||
|
serializedVersion: 5
|
||||||
|
m_AnimatorParameters: []
|
||||||
|
m_AnimatorLayers:
|
||||||
|
- serializedVersion: 5
|
||||||
|
m_Name: Base Layer
|
||||||
|
m_StateMachine: {fileID: 9113736214857964471}
|
||||||
|
m_Mask: {fileID: 0}
|
||||||
|
m_Motions: []
|
||||||
|
m_Behaviours: []
|
||||||
|
m_BlendingMode: 0
|
||||||
|
m_SyncedLayerIndex: -1
|
||||||
|
m_DefaultWeight: 0
|
||||||
|
m_IKPass: 0
|
||||||
|
m_SyncedLayerAffectsTiming: 0
|
||||||
|
m_Controller: {fileID: 9100000}
|
||||||
|
--- !u!1102 &83218441301297147
|
||||||
|
AnimatorState:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: LeftHandGrab
|
||||||
|
m_Speed: 1
|
||||||
|
m_CycleOffset: 0
|
||||||
|
m_Transitions: []
|
||||||
|
m_StateMachineBehaviours: []
|
||||||
|
m_Position: {x: 50, y: 50, z: 0}
|
||||||
|
m_IKOnFeet: 0
|
||||||
|
m_WriteDefaultValues: 1
|
||||||
|
m_Mirror: 0
|
||||||
|
m_SpeedParameterActive: 0
|
||||||
|
m_MirrorParameterActive: 0
|
||||||
|
m_CycleOffsetParameterActive: 0
|
||||||
|
m_TimeParameterActive: 0
|
||||||
|
m_Motion: {fileID: 7400000, guid: 44fadaef2720de846af62a7bbb2ec370, type: 2}
|
||||||
|
m_Tag:
|
||||||
|
m_SpeedParameter:
|
||||||
|
m_MirrorParameter:
|
||||||
|
m_CycleOffsetParameter:
|
||||||
|
m_TimeParameter:
|
||||||
|
--- !u!1107 &9113736214857964471
|
||||||
|
AnimatorStateMachine:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Base Layer
|
||||||
|
m_ChildStates:
|
||||||
|
- serializedVersion: 1
|
||||||
|
m_State: {fileID: 83218441301297147}
|
||||||
|
m_Position: {x: 200, y: 0, z: 0}
|
||||||
|
m_ChildStateMachines: []
|
||||||
|
m_AnyStateTransitions: []
|
||||||
|
m_EntryTransitions: []
|
||||||
|
m_StateMachineTransitions: {}
|
||||||
|
m_StateMachineBehaviours: []
|
||||||
|
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||||
|
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||||
|
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||||
|
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||||
|
m_DefaultState: {fileID: 83218441301297147}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1504c5149524a8a44a3854dfd4f94d1e
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 9100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a10ddbfcbce64f84787e026f4fc003a4
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1107 &-3394168293143139300
|
||||||
|
AnimatorStateMachine:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Base Layer
|
||||||
|
m_ChildStates:
|
||||||
|
- serializedVersion: 1
|
||||||
|
m_State: {fileID: 3187830036533893518}
|
||||||
|
m_Position: {x: 200, y: 0, z: 0}
|
||||||
|
m_ChildStateMachines: []
|
||||||
|
m_AnyStateTransitions: []
|
||||||
|
m_EntryTransitions: []
|
||||||
|
m_StateMachineTransitions: {}
|
||||||
|
m_StateMachineBehaviours: []
|
||||||
|
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||||
|
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||||
|
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||||
|
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||||
|
m_DefaultState: {fileID: 3187830036533893518}
|
||||||
|
--- !u!91 &9100000
|
||||||
|
AnimatorController:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: RightHandGrab
|
||||||
|
serializedVersion: 5
|
||||||
|
m_AnimatorParameters: []
|
||||||
|
m_AnimatorLayers:
|
||||||
|
- serializedVersion: 5
|
||||||
|
m_Name: Base Layer
|
||||||
|
m_StateMachine: {fileID: -3394168293143139300}
|
||||||
|
m_Mask: {fileID: 0}
|
||||||
|
m_Motions: []
|
||||||
|
m_Behaviours: []
|
||||||
|
m_BlendingMode: 0
|
||||||
|
m_SyncedLayerIndex: -1
|
||||||
|
m_DefaultWeight: 0
|
||||||
|
m_IKPass: 0
|
||||||
|
m_SyncedLayerAffectsTiming: 0
|
||||||
|
m_Controller: {fileID: 9100000}
|
||||||
|
--- !u!1102 &3187830036533893518
|
||||||
|
AnimatorState:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: RightHandGrab
|
||||||
|
m_Speed: 1
|
||||||
|
m_CycleOffset: 0
|
||||||
|
m_Transitions: []
|
||||||
|
m_StateMachineBehaviours: []
|
||||||
|
m_Position: {x: 50, y: 50, z: 0}
|
||||||
|
m_IKOnFeet: 0
|
||||||
|
m_WriteDefaultValues: 1
|
||||||
|
m_Mirror: 0
|
||||||
|
m_SpeedParameterActive: 0
|
||||||
|
m_MirrorParameterActive: 0
|
||||||
|
m_CycleOffsetParameterActive: 0
|
||||||
|
m_TimeParameterActive: 0
|
||||||
|
m_Motion: {fileID: 7400000, guid: a10ddbfcbce64f84787e026f4fc003a4, type: 2}
|
||||||
|
m_Tag:
|
||||||
|
m_SpeedParameter:
|
||||||
|
m_MirrorParameter:
|
||||||
|
m_CycleOffsetParameter:
|
||||||
|
m_TimeParameter:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 950573398b7eb5149bea536bfa6107ca
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 9100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -699,9 +699,8 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 4806409459047702211}
|
- component: {fileID: 4806409459047702211}
|
||||||
- component: {fileID: 1039589470112983840}
|
|
||||||
- component: {fileID: 1039589470112983849}
|
- component: {fileID: 1039589470112983849}
|
||||||
- component: {fileID: 1039589470112983855}
|
- component: {fileID: 1039589470112983840}
|
||||||
- component: {fileID: 1039589470112983852}
|
- component: {fileID: 1039589470112983852}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: VIVEXRHandGrabberLeft
|
m_Name: VIVEXRHandGrabberLeft
|
||||||
@@ -726,22 +725,6 @@ Transform:
|
|||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!114 &1039589470112983840
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 4806409459047702212}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: a3155365f073fdb45ba7a61887f8cf06, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Handedness: 1
|
|
||||||
m_GrabDistance: 0.03
|
|
||||||
m_EnableCollider: 1
|
|
||||||
colliderManager: {fileID: 1039589470112983855}
|
|
||||||
--- !u!114 &1039589470112983849
|
--- !u!114 &1039589470112983849
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -755,9 +738,7 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_Handedness: 1
|
m_Handedness: 1
|
||||||
m_HandGrabber: {fileID: 1039589470112983840}
|
m_EnableCollider: 1
|
||||||
m_RootJointType: 1
|
|
||||||
m_HandRootJoint: {fileID: 5303072143640256792}
|
|
||||||
m_HandJoints:
|
m_HandJoints:
|
||||||
- {fileID: 5188698556818484879}
|
- {fileID: 5188698556818484879}
|
||||||
- {fileID: 5303072143640256792}
|
- {fileID: 5303072143640256792}
|
||||||
@@ -785,7 +766,7 @@ MonoBehaviour:
|
|||||||
- {fileID: 8396010436565412298}
|
- {fileID: 8396010436565412298}
|
||||||
- {fileID: 235556365879058843}
|
- {fileID: 235556365879058843}
|
||||||
- {fileID: 950065069935064886}
|
- {fileID: 950065069935064886}
|
||||||
--- !u!114 &1039589470112983855
|
--- !u!114 &1039589470112983840
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
@@ -794,10 +775,43 @@ MonoBehaviour:
|
|||||||
m_GameObject: {fileID: 4806409459047702212}
|
m_GameObject: {fileID: 4806409459047702212}
|
||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
m_EditorHideFlags: 0
|
m_EditorHideFlags: 0
|
||||||
m_Script: {fileID: 11500000, guid: b9b8d210c92da6a49ac85755f7b15cbb, type: 3}
|
m_Script: {fileID: 11500000, guid: a3155365f073fdb45ba7a61887f8cf06, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
jointManager: {fileID: 1039589470112983849}
|
m_Handedness: 1
|
||||||
|
m_GrabDistance: 0.03
|
||||||
|
m_OnBeginGrab:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls:
|
||||||
|
- m_Target: {fileID: 1039589470112983849}
|
||||||
|
m_TargetAssemblyTypeName: VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager,
|
||||||
|
VIVE.OpenXR
|
||||||
|
m_MethodName: OnHandBeginGrab
|
||||||
|
m_Mode: 0
|
||||||
|
m_Arguments:
|
||||||
|
m_ObjectArgument: {fileID: 0}
|
||||||
|
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||||
|
m_IntArgument: 0
|
||||||
|
m_FloatArgument: 0
|
||||||
|
m_StringArgument:
|
||||||
|
m_BoolArgument: 0
|
||||||
|
m_CallState: 2
|
||||||
|
m_OnEndGrab:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls:
|
||||||
|
- m_Target: {fileID: 1039589470112983849}
|
||||||
|
m_TargetAssemblyTypeName: VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager,
|
||||||
|
VIVE.OpenXR
|
||||||
|
m_MethodName: OnHandEndGrab
|
||||||
|
m_Mode: 0
|
||||||
|
m_Arguments:
|
||||||
|
m_ObjectArgument: {fileID: 0}
|
||||||
|
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||||
|
m_IntArgument: 0
|
||||||
|
m_FloatArgument: 0
|
||||||
|
m_StringArgument:
|
||||||
|
m_BoolArgument: 0
|
||||||
|
m_CallState: 2
|
||||||
--- !u!114 &1039589470112983852
|
--- !u!114 &1039589470112983852
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -810,12 +824,12 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: fd5957dc7b39bd249885b5bb53749b7a, type: 3}
|
m_Script: {fileID: 11500000, guid: fd5957dc7b39bd249885b5bb53749b7a, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_GrabGesture:
|
animationClip: {fileID: 7400000, guid: 44fadaef2720de846af62a7bbb2ec370, type: 2}
|
||||||
thumbPose: 0
|
thumbBending: 1
|
||||||
indexPose: 0
|
indexBending: 1
|
||||||
middlePose: 0
|
middleBending: 1
|
||||||
ringPose: 0
|
ringBending: 1
|
||||||
pinkyPose: 0
|
pinkyBending: 1
|
||||||
--- !u!1 &5494989479935978129
|
--- !u!1 &5494989479935978129
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -142,7 +142,7 @@ Transform:
|
|||||||
- {fileID: 480636851454228699}
|
- {fileID: 480636851454228699}
|
||||||
m_Father: {fileID: 9030882590547856196}
|
m_Father: {fileID: 9030882590547856196}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
|
||||||
--- !u!1 &371782450477550846
|
--- !u!1 &371782450477550846
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -211,7 +211,7 @@ Transform:
|
|||||||
- {fileID: 1087333469102448396}
|
- {fileID: 1087333469102448396}
|
||||||
m_Father: {fileID: 2530992191103151071}
|
m_Father: {fileID: 2530992191103151071}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 105, y: 0, z: 0}
|
||||||
--- !u!1 &867837920217133379
|
--- !u!1 &867837920217133379
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -274,7 +274,7 @@ Transform:
|
|||||||
- {fileID: 6884329196261524271}
|
- {fileID: 6884329196261524271}
|
||||||
m_Father: {fileID: 5474254061592058453}
|
m_Father: {fileID: 5474254061592058453}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 82.94443, y: 0, z: 0}
|
||||||
--- !u!1 &1093720478279676966
|
--- !u!1 &1093720478279676966
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -306,7 +306,7 @@ Transform:
|
|||||||
- {fileID: 3049281197821631965}
|
- {fileID: 3049281197821631965}
|
||||||
m_Father: {fileID: 1486325851925083500}
|
m_Father: {fileID: 1486325851925083500}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 105, y: 0, z: 0}
|
||||||
--- !u!1 &1096893291684292564
|
--- !u!1 &1096893291684292564
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -338,7 +338,7 @@ Transform:
|
|||||||
- {fileID: 1486325851925083500}
|
- {fileID: 1486325851925083500}
|
||||||
m_Father: {fileID: 7523515941797842363}
|
m_Father: {fileID: 7523515941797842363}
|
||||||
m_RootOrder: 2
|
m_RootOrder: 2
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: -5, z: 0}
|
||||||
--- !u!1 &1525779470575164424
|
--- !u!1 &1525779470575164424
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -370,7 +370,7 @@ Transform:
|
|||||||
- {fileID: 2500751938805992760}
|
- {fileID: 2500751938805992760}
|
||||||
m_Father: {fileID: 7301969967702472780}
|
m_Father: {fileID: 7301969967702472780}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 105, y: 0, z: 0}
|
||||||
--- !u!1 &1707378898674977318
|
--- !u!1 &1707378898674977318
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -433,7 +433,7 @@ Transform:
|
|||||||
- {fileID: 4902734759167959098}
|
- {fileID: 4902734759167959098}
|
||||||
m_Father: {fileID: 6519278044083438900}
|
m_Father: {fileID: 6519278044083438900}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 80, y: -1, z: 0}
|
||||||
--- !u!1 &2548084297442148389
|
--- !u!1 &2548084297442148389
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -498,7 +498,7 @@ Transform:
|
|||||||
- {fileID: 2530992191103151071}
|
- {fileID: 2530992191103151071}
|
||||||
m_Father: {fileID: 7523515941797842363}
|
m_Father: {fileID: 7523515941797842363}
|
||||||
m_RootOrder: 4
|
m_RootOrder: 4
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 10, z: 0}
|
||||||
--- !u!1 &2826201843829277550
|
--- !u!1 &2826201843829277550
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -530,7 +530,7 @@ Transform:
|
|||||||
- {fileID: 6157606951436879209}
|
- {fileID: 6157606951436879209}
|
||||||
m_Father: {fileID: 3722484432303427184}
|
m_Father: {fileID: 3722484432303427184}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 80, y: 3, z: 0}
|
||||||
--- !u!1 &3500162954800955667
|
--- !u!1 &3500162954800955667
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -593,7 +593,7 @@ Transform:
|
|||||||
- {fileID: 5479178590596712992}
|
- {fileID: 5479178590596712992}
|
||||||
m_Father: {fileID: 1749534150074822731}
|
m_Father: {fileID: 1749534150074822731}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
|
||||||
--- !u!1 &3763232688899205820
|
--- !u!1 &3763232688899205820
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -625,7 +625,7 @@ Transform:
|
|||||||
- {fileID: 1749534150074822731}
|
- {fileID: 1749534150074822731}
|
||||||
m_Father: {fileID: 6712021276771197211}
|
m_Father: {fileID: 6712021276771197211}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 80, y: 3, z: 0}
|
||||||
--- !u!1 &5145593096174785248
|
--- !u!1 &5145593096174785248
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -657,7 +657,7 @@ Transform:
|
|||||||
- {fileID: 5474254061592058453}
|
- {fileID: 5474254061592058453}
|
||||||
m_Father: {fileID: 7523515941797842363}
|
m_Father: {fileID: 7523515941797842363}
|
||||||
m_RootOrder: 1
|
m_RootOrder: 1
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 41.8069, y: -11.3199, z: 103.867}
|
||||||
--- !u!1 &5587360065563961240
|
--- !u!1 &5587360065563961240
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -721,7 +721,7 @@ Transform:
|
|||||||
- {fileID: 2069893865991323087}
|
- {fileID: 2069893865991323087}
|
||||||
m_Father: {fileID: 8818665000486502236}
|
m_Father: {fileID: 8818665000486502236}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 105, y: 0, z: 0}
|
||||||
--- !u!1 &6561246079134113306
|
--- !u!1 &6561246079134113306
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -784,7 +784,7 @@ Transform:
|
|||||||
- {fileID: 9030882590547856196}
|
- {fileID: 9030882590547856196}
|
||||||
m_Father: {fileID: 565333190440153845}
|
m_Father: {fileID: 565333190440153845}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 80, y: 0, z: 0}
|
||||||
--- !u!1 &6983350266902418966
|
--- !u!1 &6983350266902418966
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -816,7 +816,7 @@ Transform:
|
|||||||
- {fileID: 3233556780283288469}
|
- {fileID: 3233556780283288469}
|
||||||
m_Father: {fileID: 6157606951436879209}
|
m_Father: {fileID: 6157606951436879209}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
|
||||||
--- !u!1 &7056793026468759881
|
--- !u!1 &7056793026468759881
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -848,7 +848,7 @@ Transform:
|
|||||||
- {fileID: 484802358358613714}
|
- {fileID: 484802358358613714}
|
||||||
m_Father: {fileID: 4902734759167959098}
|
m_Father: {fileID: 4902734759167959098}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
|
||||||
--- !u!1 &7152668487518777765
|
--- !u!1 &7152668487518777765
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -858,9 +858,8 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 7152668487518777764}
|
- component: {fileID: 7152668487518777764}
|
||||||
- component: {fileID: 3431167848623168881}
|
|
||||||
- component: {fileID: 3431167848623168894}
|
- component: {fileID: 3431167848623168894}
|
||||||
- component: {fileID: 3431167848623168893}
|
- component: {fileID: 3431167848623168881}
|
||||||
- component: {fileID: 3431167848623168882}
|
- component: {fileID: 3431167848623168882}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: VIVEXRHandGrabberRight
|
m_Name: VIVEXRHandGrabberRight
|
||||||
@@ -885,22 +884,6 @@ Transform:
|
|||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!114 &3431167848623168881
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 7152668487518777765}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: a3155365f073fdb45ba7a61887f8cf06, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Handedness: 0
|
|
||||||
m_GrabDistance: 0.03
|
|
||||||
m_EnableCollider: 1
|
|
||||||
colliderManager: {fileID: 3431167848623168893}
|
|
||||||
--- !u!114 &3431167848623168894
|
--- !u!114 &3431167848623168894
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -914,9 +897,7 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_Handedness: 0
|
m_Handedness: 0
|
||||||
m_HandGrabber: {fileID: 3431167848623168881}
|
m_EnableCollider: 1
|
||||||
m_RootJointType: 1
|
|
||||||
m_HandRootJoint: {fileID: 7523515941797842363}
|
|
||||||
m_HandJoints:
|
m_HandJoints:
|
||||||
- {fileID: 7633984271078933036}
|
- {fileID: 7633984271078933036}
|
||||||
- {fileID: 7523515941797842363}
|
- {fileID: 7523515941797842363}
|
||||||
@@ -944,7 +925,7 @@ MonoBehaviour:
|
|||||||
- {fileID: 6157606951436879209}
|
- {fileID: 6157606951436879209}
|
||||||
- {fileID: 2500751938805992760}
|
- {fileID: 2500751938805992760}
|
||||||
- {fileID: 3233556780283288469}
|
- {fileID: 3233556780283288469}
|
||||||
--- !u!114 &3431167848623168893
|
--- !u!114 &3431167848623168881
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
@@ -953,10 +934,43 @@ MonoBehaviour:
|
|||||||
m_GameObject: {fileID: 7152668487518777765}
|
m_GameObject: {fileID: 7152668487518777765}
|
||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
m_EditorHideFlags: 0
|
m_EditorHideFlags: 0
|
||||||
m_Script: {fileID: 11500000, guid: b9b8d210c92da6a49ac85755f7b15cbb, type: 3}
|
m_Script: {fileID: 11500000, guid: a3155365f073fdb45ba7a61887f8cf06, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
jointManager: {fileID: 3431167848623168894}
|
m_Handedness: 0
|
||||||
|
m_GrabDistance: 0.03
|
||||||
|
m_OnBeginGrab:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls:
|
||||||
|
- m_Target: {fileID: 3431167848623168894}
|
||||||
|
m_TargetAssemblyTypeName: VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager,
|
||||||
|
VIVE.OpenXR
|
||||||
|
m_MethodName: OnHandBeginGrab
|
||||||
|
m_Mode: 0
|
||||||
|
m_Arguments:
|
||||||
|
m_ObjectArgument: {fileID: 0}
|
||||||
|
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||||
|
m_IntArgument: 0
|
||||||
|
m_FloatArgument: 0
|
||||||
|
m_StringArgument:
|
||||||
|
m_BoolArgument: 0
|
||||||
|
m_CallState: 2
|
||||||
|
m_OnEndGrab:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls:
|
||||||
|
- m_Target: {fileID: 3431167848623168894}
|
||||||
|
m_TargetAssemblyTypeName: VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager,
|
||||||
|
VIVE.OpenXR
|
||||||
|
m_MethodName: OnHandEndGrab
|
||||||
|
m_Mode: 0
|
||||||
|
m_Arguments:
|
||||||
|
m_ObjectArgument: {fileID: 0}
|
||||||
|
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||||
|
m_IntArgument: 0
|
||||||
|
m_FloatArgument: 0
|
||||||
|
m_StringArgument:
|
||||||
|
m_BoolArgument: 0
|
||||||
|
m_CallState: 2
|
||||||
--- !u!114 &3431167848623168882
|
--- !u!114 &3431167848623168882
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -969,12 +983,12 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: fd5957dc7b39bd249885b5bb53749b7a, type: 3}
|
m_Script: {fileID: 11500000, guid: fd5957dc7b39bd249885b5bb53749b7a, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_GrabGesture:
|
animationClip: {fileID: 7400000, guid: a10ddbfcbce64f84787e026f4fc003a4, type: 2}
|
||||||
thumbPose: 0
|
thumbBending: 1
|
||||||
indexPose: 0
|
indexBending: 1
|
||||||
middlePose: 0
|
middleBending: 1
|
||||||
ringPose: 0
|
ringBending: 1
|
||||||
pinkyPose: 0
|
pinkyBending: 1
|
||||||
--- !u!1 &7904580642040219186
|
--- !u!1 &7904580642040219186
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -1006,7 +1020,7 @@ Transform:
|
|||||||
- {fileID: 7301969967702472780}
|
- {fileID: 7301969967702472780}
|
||||||
m_Father: {fileID: 7523515941797842363}
|
m_Father: {fileID: 7523515941797842363}
|
||||||
m_RootOrder: 5
|
m_RootOrder: 5
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 15, z: 0}
|
||||||
--- !u!1 &8086845646762513573
|
--- !u!1 &8086845646762513573
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -1038,7 +1052,7 @@ Transform:
|
|||||||
- {fileID: 1021821948623702842}
|
- {fileID: 1021821948623702842}
|
||||||
m_Father: {fileID: 2947514034224435963}
|
m_Father: {fileID: 2947514034224435963}
|
||||||
m_RootOrder: 0
|
m_RootOrder: 0
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 11.60709, y: 0, z: 0}
|
||||||
--- !u!1 &8136935140357987531
|
--- !u!1 &8136935140357987531
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ea1b2b1a3faba024aa3df5391529aec0
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,378 @@
|
|||||||
|
// "Wave SDK
|
||||||
|
// © 2020 HTC Corporation. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Unless otherwise required by copyright law and practice,
|
||||||
|
// upon the execution of HTC SDK license agreement,
|
||||||
|
// HTC grants you access to and use of the Wave SDK(s).
|
||||||
|
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||||
|
// conditions signed by you and all SDK and API requirements,
|
||||||
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is designed to edit grab gestures.
|
||||||
|
/// </summary>
|
||||||
|
[RequireComponent(typeof(HandMeshManager))]
|
||||||
|
public class CustomGrabPose : MonoBehaviour
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
|
||||||
|
#region Log
|
||||||
|
|
||||||
|
const string LOG_TAG = "Wave.Essence.Hand.Interaction.CustomGrabPose";
|
||||||
|
private StringBuilder m_sb = null;
|
||||||
|
internal StringBuilder sb
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_sb == null) { m_sb = new StringBuilder(); }
|
||||||
|
return m_sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void DEBUG(StringBuilder msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void WARNING(StringBuilder msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void ERROR(StringBuilder msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||||
|
int logFrame = 0;
|
||||||
|
bool printIntervalLog => logFrame == 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This structure is designed to record the rotation values of each joint at different bend angles.
|
||||||
|
/// </summary>
|
||||||
|
private struct JointBendingRotation
|
||||||
|
{
|
||||||
|
public FingerId fingerId;
|
||||||
|
public string jointPath;
|
||||||
|
public int bending;
|
||||||
|
public Quaternion rotation;
|
||||||
|
|
||||||
|
public JointBendingRotation(FingerId in_FingerId, string in_JointPath, int in_Bending, Quaternion in_Rotation)
|
||||||
|
{
|
||||||
|
fingerId = in_FingerId;
|
||||||
|
jointPath = in_JointPath;
|
||||||
|
bending = in_Bending;
|
||||||
|
rotation = in_Rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JointBendingRotation Identity => new JointBendingRotation(FingerId.Invalid, "", -1, Quaternion.identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private AnimationClip animationClip;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[Range(1, 30)]
|
||||||
|
private int thumbBending = 1;
|
||||||
|
[SerializeField]
|
||||||
|
[Range(1, 30)]
|
||||||
|
private int indexBending = 1;
|
||||||
|
[SerializeField]
|
||||||
|
[Range(1, 30)]
|
||||||
|
private int middleBending = 1;
|
||||||
|
[SerializeField]
|
||||||
|
[Range(1, 30)]
|
||||||
|
private int ringBending = 1;
|
||||||
|
[SerializeField]
|
||||||
|
[Range(1, 30)]
|
||||||
|
private int pinkyBending = 1;
|
||||||
|
|
||||||
|
private HandMeshManager m_HandMesh;
|
||||||
|
private Dictionary<FingerId, int> fingersBendingMapping = new Dictionary<FingerId, int>()
|
||||||
|
{
|
||||||
|
{FingerId.Thumb, 1 },
|
||||||
|
{FingerId.Index, 1 },
|
||||||
|
{FingerId.Middle, 1 },
|
||||||
|
{FingerId.Ring, 1 },
|
||||||
|
{FingerId.Pinky, 1 },
|
||||||
|
};
|
||||||
|
private static readonly Dictionary<string, FingerId> jointsPathMapping = new Dictionary<string, FingerId>()
|
||||||
|
{
|
||||||
|
{"WaveBone_0", FingerId.Invalid },
|
||||||
|
{"WaveBone_1", FingerId.Invalid },
|
||||||
|
{"WaveBone_2", FingerId.Thumb },
|
||||||
|
{"WaveBone_2/WaveBone_3", FingerId.Thumb },
|
||||||
|
{"WaveBone_2/WaveBone_3/WaveBone_4", FingerId.Thumb },
|
||||||
|
{"WaveBone_2/WaveBone_3/WaveBone_4/WaveBone_5", FingerId.Thumb },
|
||||||
|
{"WaveBone_6", FingerId.Index },
|
||||||
|
{"WaveBone_6/WaveBone_7", FingerId.Index },
|
||||||
|
{"WaveBone_6/WaveBone_7/WaveBone_8", FingerId.Index },
|
||||||
|
{"WaveBone_6/WaveBone_7/WaveBone_8/WaveBone_9", FingerId.Index },
|
||||||
|
{"WaveBone_6/WaveBone_7/WaveBone_8/WaveBone_9/WaveBone_10", FingerId.Index },
|
||||||
|
{"WaveBone_11", FingerId.Middle },
|
||||||
|
{"WaveBone_11/WaveBone_12", FingerId.Middle },
|
||||||
|
{"WaveBone_11/WaveBone_12/WaveBone_13", FingerId.Middle },
|
||||||
|
{"WaveBone_11/WaveBone_12/WaveBone_13/WaveBone_14", FingerId.Middle },
|
||||||
|
{"WaveBone_11/WaveBone_12/WaveBone_13/WaveBone_14/WaveBone_15", FingerId.Middle },
|
||||||
|
{"WaveBone_16", FingerId.Ring },
|
||||||
|
{"WaveBone_16/WaveBone_17", FingerId.Ring },
|
||||||
|
{"WaveBone_16/WaveBone_17/WaveBone_18", FingerId.Ring },
|
||||||
|
{"WaveBone_16/WaveBone_17/WaveBone_18/WaveBone_19", FingerId.Ring },
|
||||||
|
{"WaveBone_16/WaveBone_17/WaveBone_18/WaveBone_19/WaveBone_20", FingerId.Ring },
|
||||||
|
{"WaveBone_21", FingerId.Pinky },
|
||||||
|
{"WaveBone_21/WaveBone_22", FingerId.Pinky },
|
||||||
|
{"WaveBone_21/WaveBone_22/WaveBone_23", FingerId.Pinky },
|
||||||
|
{"WaveBone_21/WaveBone_22/WaveBone_23/WaveBone_24", FingerId.Pinky },
|
||||||
|
{"WaveBone_21/WaveBone_22/WaveBone_23/WaveBone_24/WaveBone_25", FingerId.Pinky },
|
||||||
|
};
|
||||||
|
private List<JointBendingRotation> jointsBending = new List<JointBendingRotation>();
|
||||||
|
|
||||||
|
private readonly float k_GrabDistance = 0.1f;
|
||||||
|
private HandGrabInteractable candidate = null;
|
||||||
|
private Pose wristPose = Pose.identity;
|
||||||
|
private Quaternion[] fingerJointRotation = new Quaternion[jointsPathMapping.Count];
|
||||||
|
|
||||||
|
#region MonoBehaviours
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
m_HandMesh = transform.GetComponent<HandMeshManager>();
|
||||||
|
if (m_HandMesh == null)
|
||||||
|
{
|
||||||
|
sb.Clear().Append("Failed to find HandMeshRenderer.");
|
||||||
|
ERROR(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animationClip != null)
|
||||||
|
{
|
||||||
|
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(animationClip);
|
||||||
|
|
||||||
|
foreach (string propertyName in jointsPathMapping.Keys)
|
||||||
|
{
|
||||||
|
SetEachFrameRotation(curveBindings, propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Clear().Append("Failed to find hand grab animation. The hand model will not change when you change bend angles of finger.");
|
||||||
|
sb.Append("However, you still can record grab pose if you use direct preview mode.");
|
||||||
|
WARNING(sb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
jointsBending.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (IsFingerBendingUpdated())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < fingerJointRotation.Length; i++)
|
||||||
|
{
|
||||||
|
var jointInfo = jointsPathMapping.ElementAt(i);
|
||||||
|
string jointPath = jointInfo.Key;
|
||||||
|
FingerId fingerId = jointInfo.Value;
|
||||||
|
int bending = -1;
|
||||||
|
if (fingersBendingMapping.ContainsKey(fingerId))
|
||||||
|
{
|
||||||
|
bending = fingersBendingMapping[fingerId] - 1;
|
||||||
|
}
|
||||||
|
if (jointsBending.Count(x => x.fingerId == fingerId && x.jointPath == jointPath && x.bending == bending) > 0)
|
||||||
|
{
|
||||||
|
JointBendingRotation jointRotation = jointsBending.FirstOrDefault(x => x.fingerId == fingerId && x.jointPath == jointPath && x.bending == bending);
|
||||||
|
fingerJointRotation[i] = jointRotation.rotation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fingerJointRotation[i] = Quaternion.identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_HandMesh != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < fingerJointRotation.Length; i++)
|
||||||
|
{
|
||||||
|
JointType joint = (JointType)i;
|
||||||
|
m_HandMesh.GetJointPositionAndRotation(joint, out Vector3 jointPosition, out _, local: true);
|
||||||
|
m_HandMesh.SetJointPositionAndRotation(joint, jointPosition, fingerJointRotation[i], local: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
|
||||||
|
{
|
||||||
|
FindNearInteractable();
|
||||||
|
SavePoseWithCandidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the rotation of each joint frame by frame and records it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="curveBindings">All the float curve bindings currently stored in the clip.</param>
|
||||||
|
/// <param name="jointPath">The path of the joint.</param>
|
||||||
|
private void SetEachFrameRotation(EditorCurveBinding[] curveBindings, string jointPath)
|
||||||
|
{
|
||||||
|
const int propertyCount = 4;
|
||||||
|
const int animeCount = 30;
|
||||||
|
const float animeFPS = 60.0f;
|
||||||
|
|
||||||
|
List<EditorCurveBinding> matchCurve = new List<EditorCurveBinding>();
|
||||||
|
foreach (EditorCurveBinding binding in curveBindings)
|
||||||
|
{
|
||||||
|
if (binding.path.Equals(jointPath))
|
||||||
|
{
|
||||||
|
matchCurve.Add(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchCurve.Count == propertyCount)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchCurve.Count == propertyCount)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < animeCount; i++)
|
||||||
|
{
|
||||||
|
Quaternion rotation = Quaternion.identity;
|
||||||
|
foreach (var curveBinding in matchCurve)
|
||||||
|
{
|
||||||
|
AnimationCurve curve = AnimationUtility.GetEditorCurve(animationClip, curveBinding);
|
||||||
|
switch (curveBinding.propertyName)
|
||||||
|
{
|
||||||
|
case "m_LocalRotation.x":
|
||||||
|
rotation.x = curve.Evaluate(i / animeFPS);
|
||||||
|
break;
|
||||||
|
case "m_LocalRotation.y":
|
||||||
|
rotation.y = curve.Evaluate(i / animeFPS);
|
||||||
|
break;
|
||||||
|
case "m_LocalRotation.z":
|
||||||
|
rotation.z = curve.Evaluate(i / animeFPS);
|
||||||
|
break;
|
||||||
|
case "m_LocalRotation.w":
|
||||||
|
rotation.w = curve.Evaluate(i / animeFPS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jointsBending.Add(new JointBendingRotation(jointsPathMapping[jointPath], jointPath, i, rotation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the current finger bend angle has changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the bend angle has changed; otherwise, false.</returns>
|
||||||
|
private bool IsFingerBendingUpdated()
|
||||||
|
{
|
||||||
|
bool updated = false;
|
||||||
|
|
||||||
|
if (fingersBendingMapping[FingerId.Thumb] != thumbBending)
|
||||||
|
{
|
||||||
|
fingersBendingMapping[FingerId.Thumb] = thumbBending;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (fingersBendingMapping[FingerId.Index] != indexBending)
|
||||||
|
{
|
||||||
|
fingersBendingMapping[FingerId.Index] = indexBending;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (fingersBendingMapping[FingerId.Middle] != middleBending)
|
||||||
|
{
|
||||||
|
fingersBendingMapping[FingerId.Middle] = middleBending;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (fingersBendingMapping[FingerId.Ring] != ringBending)
|
||||||
|
{
|
||||||
|
fingersBendingMapping[FingerId.Ring] = ringBending;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (fingersBendingMapping[FingerId.Pinky] != pinkyBending)
|
||||||
|
{
|
||||||
|
fingersBendingMapping[FingerId.Pinky] = pinkyBending;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update hand pose from HandMeshRenderer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Return true if updating hand pose from HandMeshRenderer; otherwise.</returns>
|
||||||
|
private bool UpdateHandPose()
|
||||||
|
{
|
||||||
|
bool updated = false;
|
||||||
|
if (m_HandMesh != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < fingerJointRotation.Length; i++)
|
||||||
|
{
|
||||||
|
if (i == (int)JointType.Wrist)
|
||||||
|
{
|
||||||
|
m_HandMesh.GetJointPositionAndRotation(JointType.Wrist, out wristPose.position, out wristPose.rotation);
|
||||||
|
}
|
||||||
|
m_HandMesh.GetJointPositionAndRotation((JointType)i, out _, out fingerJointRotation[i], local: true);
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (!updated)
|
||||||
|
{
|
||||||
|
sb.Clear().Append("Failed to update hand pose.");
|
||||||
|
DEBUG(sb);
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the nearest interactable object to the hand.
|
||||||
|
/// </summary>
|
||||||
|
public void FindNearInteractable()
|
||||||
|
{
|
||||||
|
if (!UpdateHandPose()) { return; }
|
||||||
|
|
||||||
|
candidate = null;
|
||||||
|
float maxScore = 0;
|
||||||
|
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
||||||
|
{
|
||||||
|
float distanceScore = interactable.CalculateDistanceScore(wristPose.position, k_GrabDistance);
|
||||||
|
if (distanceScore > maxScore)
|
||||||
|
{
|
||||||
|
maxScore = distanceScore;
|
||||||
|
candidate = interactable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (candidate == null)
|
||||||
|
{
|
||||||
|
sb.Clear().Append("Unable to find a suitable candidate.");
|
||||||
|
WARNING(sb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the position and rotation offset with the candidate.
|
||||||
|
/// </summary>
|
||||||
|
public void SavePoseWithCandidate()
|
||||||
|
{
|
||||||
|
if (!UpdateHandPose() || candidate == null) { return; }
|
||||||
|
|
||||||
|
Quaternion[] clone = new Quaternion[fingerJointRotation.Length];
|
||||||
|
Array.Copy(fingerJointRotation, clone, fingerJointRotation.Length);
|
||||||
|
GrabPose grabPose = GrabPose.Identity;
|
||||||
|
|
||||||
|
grabPose.Update($"Grab Pose {candidate.grabPoses.Count + 1}", clone, m_HandMesh.isLeft);
|
||||||
|
grabPose.grabOffset = new GrabOffset(wristPose.position, wristPose.rotation, candidate.transform.position, candidate.transform.rotation);
|
||||||
|
if (!candidate.grabPoses.Contains(grabPose))
|
||||||
|
{
|
||||||
|
candidate.grabPoses.Add(grabPose);
|
||||||
|
}
|
||||||
|
GrabbablePoseRecorder.SaveChanges();
|
||||||
|
|
||||||
|
sb.Clear().Append("Save grab pose successfully.");
|
||||||
|
DEBUG(sb);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
{
|
{
|
||||||
@@ -61,6 +62,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
grabbable.grabPoses.AddRange(updatedGrabPose);
|
grabbable.grabPoses.AddRange(updatedGrabPose);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
||||||
}
|
}
|
||||||
File.Delete(filepath);
|
File.Delete(filepath);
|
||||||
File.Delete(metaFilepath);
|
File.Delete(metaFilepath);
|
||||||
@@ -8,7 +8,9 @@
|
|||||||
// conditions signed by you and all SDK and API requirements,
|
// conditions signed by you and all SDK and API requirements,
|
||||||
// specifications, and documentation provided by HTC to You."
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
@@ -18,6 +20,26 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class HandGrabInteractable : MonoBehaviour, IHandGrabbable
|
public class HandGrabInteractable : MonoBehaviour, IHandGrabbable
|
||||||
{
|
{
|
||||||
|
#region Log
|
||||||
|
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandGrabInteractable";
|
||||||
|
private StringBuilder m_sb = null;
|
||||||
|
internal StringBuilder sb
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_sb == null) { m_sb = new StringBuilder(); }
|
||||||
|
return m_sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||||
|
int logFrame = 0;
|
||||||
|
bool printIntervalLog => logFrame == 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Interface Implement
|
#region Interface Implement
|
||||||
private HandGrabInteractor m_Grabber = null;
|
private HandGrabInteractor m_Grabber = null;
|
||||||
public IGrabber grabber => m_Grabber;
|
public IGrabber grabber => m_Grabber;
|
||||||
@@ -28,30 +50,47 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
private bool m_IsGrabbable = true;
|
private bool m_IsGrabbable = true;
|
||||||
public bool isGrabbable { get { return m_IsGrabbable; } set { m_IsGrabbable = value; } }
|
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]
|
[SerializeField]
|
||||||
private FingerRequirement m_FingerRequirement;
|
private FingerRequirement m_FingerRequirement;
|
||||||
public FingerRequirement fingerRequirement => m_FingerRequirement;
|
public FingerRequirement fingerRequirement => m_FingerRequirement;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
HandGrabbableEvent m_OnBeginGrabbed = new HandGrabbableEvent();
|
||||||
|
public HandGrabbableEvent onBeginGrabbed => m_OnBeginGrabbed;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
HandGrabbableEvent m_OnEndGrabbed = new HandGrabbableEvent();
|
||||||
|
public HandGrabbableEvent onEndGrabbed => m_OnEndGrabbed;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Public State
|
#region Public State
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private Rigidbody m_Rigidbody = null;
|
||||||
|
public new Rigidbody rigidbody => m_Rigidbody;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private List<GrabPose> m_GrabPoses = new List<GrabPose>();
|
private List<GrabPose> m_GrabPoses = new List<GrabPose>();
|
||||||
public List<GrabPose> grabPoses => m_GrabPoses;
|
public List<GrabPose> grabPoses => m_GrabPoses;
|
||||||
|
|
||||||
public GrabPose bestGrabPose => bestGrabPoseId != -1 ? m_GrabPoses[bestGrabPoseId] : GrabPose.Identity;
|
private GrabPose m_BestGrabPose = GrabPose.Identity;
|
||||||
|
public GrabPose bestGrabPose => m_BestGrabPose;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private bool m_ShowAllIndicator = false;
|
private bool m_ShowAllIndicator = false;
|
||||||
private List<Collider> allColliders = new List<Collider>();
|
private List<Collider> allColliders = new List<Collider>();
|
||||||
private HandGrabInteractor closestGrabber = null;
|
private HandGrabInteractor closestGrabber = null;
|
||||||
private int bestGrabPoseId = -1;
|
private OnBeginGrabbed beginGrabbed;
|
||||||
private OnBeginGrabbed onBeginGrabbed;
|
private OnEndGrabbed endGrabbed;
|
||||||
private OnEndGrabbed onEndGrabbed;
|
|
||||||
|
[SerializeField]
|
||||||
|
private IOneHandContraintMovement m_OneHandContraintMovement;
|
||||||
|
public bool isContraint => m_OneHandContraintMovement != null;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private int m_PreviewIndex = -1;
|
||||||
|
|
||||||
#region MonoBehaviour
|
#region MonoBehaviour
|
||||||
private void Awake()
|
private void Awake()
|
||||||
@@ -78,18 +117,24 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// <param name="grabber">The grabber to set.</param>
|
/// <param name="grabber">The grabber to set.</param>
|
||||||
public void SetGrabber(IGrabber grabber)
|
public void SetGrabber(IGrabber grabber)
|
||||||
{
|
{
|
||||||
if (grabber is HandGrabInteractor)
|
if (grabber is HandGrabInteractor handGrabber)
|
||||||
{
|
{
|
||||||
HandGrabInteractor handGrabber = grabber as HandGrabInteractor;
|
|
||||||
m_Grabber = handGrabber;
|
m_Grabber = handGrabber;
|
||||||
UpdateBestGrabPose(handGrabber.isLeft, handGrabber.handGrabState.GetJointPose(JointType.Palm));
|
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
|
||||||
onBeginGrabbed?.Invoke(this);
|
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
|
||||||
|
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
|
||||||
|
UpdateBestGrabPose(handGrabber.isLeft, new Pose(wristPos, wristRot));
|
||||||
|
beginGrabbed?.Invoke(this);
|
||||||
|
m_OnBeginGrabbed?.Invoke(this);
|
||||||
|
DEBUG($"{transform.name} is grabbed by {handGrabber.name}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_Grabber = null;
|
m_Grabber = null;
|
||||||
bestGrabPoseId = -1;
|
m_BestGrabPose = GrabPose.Identity;
|
||||||
onEndGrabbed?.Invoke(this);
|
endGrabbed?.Invoke(this);
|
||||||
|
m_OnEndGrabbed?.Invoke(this);
|
||||||
|
DEBUG($"{transform.name} is released.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +154,10 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int index = FindBestGrabPose(grabber.isLeft, grabber.handGrabState.GetJointPose((int)JointType.Palm));
|
HandPose handPose = HandPoseProvider.GetHandPose(grabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
|
||||||
|
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
|
||||||
|
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
|
||||||
|
int index = FindBestGrabPose(grabber.isLeft, new Pose(wristPos, wristRot));
|
||||||
ShowIndicatorByIndex(index);
|
ShowIndicatorByIndex(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,36 +189,60 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// Add a listener for the event triggered when the grabbable object is grabbed.
|
/// Add a listener for the event triggered when the grabbable object is grabbed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to be called when the grabbable object is grabbed.</param>
|
/// <param name="handler">The method to be called when the grabbable object is grabbed.</param>
|
||||||
|
[Obsolete("Please use onBeginGrabbed instead.")]
|
||||||
public void AddBeginGrabbedListener(OnBeginGrabbed handler)
|
public void AddBeginGrabbedListener(OnBeginGrabbed handler)
|
||||||
{
|
{
|
||||||
onBeginGrabbed += handler;
|
beginGrabbed += handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove a listener for the event triggered when the grabbable object is grabbed.
|
/// Remove a listener for the event triggered when the grabbable object is grabbed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||||
|
[Obsolete("Please use onBeginGrabbed instead.")]
|
||||||
public void RemoveBeginGrabbedListener(OnBeginGrabbed handler)
|
public void RemoveBeginGrabbedListener(OnBeginGrabbed handler)
|
||||||
{
|
{
|
||||||
onBeginGrabbed -= handler;
|
beginGrabbed -= handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a listener for the event triggered when the grabbable object is released.
|
/// Add a listener for the event triggered when the grabbable object is released.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to be called when the grabbable object is released.</param>
|
/// <param name="handler">The method to be called when the grabbable object is released.</param>
|
||||||
|
[Obsolete("Please use onEndGrabbed instead.")]
|
||||||
public void AddEndGrabbedListener(OnEndGrabbed handler)
|
public void AddEndGrabbedListener(OnEndGrabbed handler)
|
||||||
{
|
{
|
||||||
onEndGrabbed += handler;
|
endGrabbed += handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove a listener for the event triggered when the grabbable object is released.
|
/// Remove a listener for the event triggered when the grabbable object is released.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||||
|
[Obsolete("Please use onEndGrabbed instead.")]
|
||||||
public void RemoveEndGrabbedListener(OnEndGrabbed handler)
|
public void RemoveEndGrabbedListener(OnEndGrabbed handler)
|
||||||
{
|
{
|
||||||
onEndGrabbed -= handler;
|
endGrabbed -= handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the position and rotation of the self with the pose of the hand that is grabbing it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabberPose">The pose of the hand.</param>
|
||||||
|
public void UpdatePositionAndRotation(Pose grabberPose)
|
||||||
|
{
|
||||||
|
if (m_OneHandContraintMovement == null)
|
||||||
|
{
|
||||||
|
Quaternion handRotDiff = grabberPose.rotation * Quaternion.Inverse(m_BestGrabPose.grabOffset.sourceRotation);
|
||||||
|
transform.position = grabberPose.position + handRotDiff * m_BestGrabPose.grabOffset.posOffset;
|
||||||
|
transform.rotation = grabberPose.rotation * m_BestGrabPose.grabOffset.rotOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_OneHandContraintMovement != null)
|
||||||
|
{
|
||||||
|
m_OneHandContraintMovement.UpdatePose(grabberPose);
|
||||||
|
UpdateMeshPoseByGrabbable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -179,6 +251,13 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
{
|
{
|
||||||
|
if (m_OneHandContraintMovement != null)
|
||||||
|
{
|
||||||
|
m_OneHandContraintMovement.Initialize(this);
|
||||||
|
onBeginGrabbed.AddListener(m_OneHandContraintMovement.OnBeginGrabbed);
|
||||||
|
onEndGrabbed.AddListener(m_OneHandContraintMovement.OnEndGrabbed);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < m_GrabPoses.Count; i++)
|
for (int i = 0; i < m_GrabPoses.Count; i++)
|
||||||
{
|
{
|
||||||
if (m_GrabPoses[i].indicator.enableIndicator || m_ShowAllIndicator)
|
if (m_GrabPoses[i].indicator.enableIndicator || m_ShowAllIndicator)
|
||||||
@@ -190,7 +269,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
GrabPose grabPose = m_GrabPoses[i];
|
GrabPose grabPose = m_GrabPoses[i];
|
||||||
grabPose.indicator.CalculateGrabOffset(transform);
|
grabPose.indicator.CalculateGrabOffset(transform.position, transform.rotation);
|
||||||
m_GrabPoses[i] = grabPose;
|
m_GrabPoses[i] = grabPose;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,16 +284,12 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
private void AutoGenerateIndicator(int index)
|
private void AutoGenerateIndicator(int index)
|
||||||
{
|
{
|
||||||
AutoGenIndicator autoGenIndicator = new GameObject($"Indicator {index}", typeof(AutoGenIndicator)).GetComponent<AutoGenIndicator>();
|
AutoGenIndicator autoGenIndicator = new GameObject($"Indicator {index}", typeof(AutoGenIndicator)).GetComponent<AutoGenIndicator>();
|
||||||
|
|
||||||
GrabPose grabPose = m_GrabPoses[index];
|
GrabPose grabPose = m_GrabPoses[index];
|
||||||
// The grabPose.grabOffset was calculated as the position of the object minus the position of the hand,
|
Pose handPose = CalculateActualHandPose(grabPose.grabOffset);
|
||||||
// so inverse calculation is needed here to infer the position of the hand.
|
Vector3 closestPoint = GetClosestPoint(handPose.position);
|
||||||
Vector3 offset = transform.rotation * Quaternion.Inverse(grabPose.grabOffset.targetRotation) * -grabPose.grabOffset.position;
|
autoGenIndicator.SetPose(closestPoint, closestPoint - transform.position);
|
||||||
Vector3 defaultPosition = transform.position + offset;
|
|
||||||
Vector3 closestPoint = GetClosestPoint(defaultPosition);
|
|
||||||
autoGenIndicator.SetPose(closestPoint, offset.normalized);
|
|
||||||
grabPose.indicator.Update(true, true, autoGenIndicator.gameObject);
|
grabPose.indicator.Update(true, true, autoGenIndicator.gameObject);
|
||||||
grabPose.indicator.CalculateGrabOffset(transform);
|
grabPose.indicator.CalculateGrabOffset(transform.position, transform.rotation);
|
||||||
m_GrabPoses[index] = grabPose;
|
m_GrabPoses[index] = grabPose;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,10 +314,10 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
{
|
{
|
||||||
if (hit.collider == collider)
|
if (hit.collider == collider)
|
||||||
{
|
{
|
||||||
float hitDistnace = Vector3.Distance(sourcePos, hit.point);
|
float hitDistance = Vector3.Distance(sourcePos, hit.point);
|
||||||
if (distance > hitDistnace)
|
if (distance > hitDistance)
|
||||||
{
|
{
|
||||||
distance = hitDistnace;
|
distance = hitDistance;
|
||||||
closePoint = hit.point;
|
closePoint = hit.point;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,16 +339,27 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// <param name="isLeft">Whether the grabber is the left hand.</param>
|
/// <param name="isLeft">Whether the grabber is the left hand.</param>
|
||||||
/// <param name="grabberPose">The pose of the grabber.</param>
|
/// <param name="grabberPose">The pose of the grabber.</param>
|
||||||
/// <returns>True if a best grab pose is found; otherwise, false.</returns>
|
/// <returns>True if a best grab pose is found; otherwise, false.</returns>
|
||||||
private bool UpdateBestGrabPose(bool isLeft, Pose grabberPose)
|
private void UpdateBestGrabPose(bool isLeft, Pose grabberPose)
|
||||||
{
|
{
|
||||||
int index = FindBestGrabPose(isLeft, grabberPose);
|
int index = FindBestGrabPose(isLeft, grabberPose);
|
||||||
if (index != -1 && index < m_GrabPoses.Count)
|
if (index != -1 && index < m_GrabPoses.Count)
|
||||||
{
|
{
|
||||||
bestGrabPoseId = index;
|
m_BestGrabPose = m_GrabPoses[index];
|
||||||
return true;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_BestGrabPose.grabOffset = new GrabOffset(grabberPose.position, grabberPose.rotation, transform.position, transform.rotation);
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(isLeft ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
|
||||||
|
if (handPose is MeshHandPose meshHandPose)
|
||||||
|
{
|
||||||
|
Quaternion[] grabRotations = new Quaternion[(int)JointType.Count];
|
||||||
|
for (int i = 0; i < (int)JointType.Count; i++)
|
||||||
|
{
|
||||||
|
meshHandPose.GetRotation((JointType)i, out grabRotations[i], local: true);
|
||||||
|
}
|
||||||
|
m_BestGrabPose.recordedGrabRotations = grabRotations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
index = -1;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -291,7 +377,8 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
{
|
{
|
||||||
if (m_GrabPoses[i].isLeft == isLeft)
|
if (m_GrabPoses[i].isLeft == isLeft)
|
||||||
{
|
{
|
||||||
Vector3 grabDirection = transform.rotation * Quaternion.Inverse(m_GrabPoses[i].grabOffset.targetRotation) * -m_GrabPoses[i].grabOffset.position;
|
Pose handPose = CalculateActualHandPose(m_GrabPoses[i].grabOffset);
|
||||||
|
Vector3 grabDirection = handPose.position - transform.position;
|
||||||
float dot = Vector3.Dot(currentDirection.normalized, grabDirection.normalized);
|
float dot = Vector3.Dot(currentDirection.normalized, grabDirection.normalized);
|
||||||
if (dot > maxDot)
|
if (dot > maxDot)
|
||||||
{
|
{
|
||||||
@@ -316,7 +403,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
if (index >= 0 && index < m_GrabPoses.Count &&
|
if (index >= 0 && index < m_GrabPoses.Count &&
|
||||||
m_GrabPoses[index].indicator.enableIndicator)
|
m_GrabPoses[index].indicator.enableIndicator)
|
||||||
{
|
{
|
||||||
m_GrabPoses[index].indicator.UpdatePositionAndRotation(transform);
|
m_GrabPoses[index].indicator.UpdatePositionAndRotation(transform.position, transform.rotation);
|
||||||
m_GrabPoses[index].indicator.SetActive(true);
|
m_GrabPoses[index].indicator.SetActive(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,10 +422,31 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
{
|
{
|
||||||
if (grabPose.isLeft == isLeft)
|
if (grabPose.isLeft == isLeft)
|
||||||
{
|
{
|
||||||
grabPose.indicator.UpdatePositionAndRotation(transform);
|
grabPose.indicator.UpdatePositionAndRotation(transform.position, transform.rotation);
|
||||||
grabPose.indicator.SetActive(true);
|
grabPose.indicator.SetActive(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateMeshPoseByGrabbable()
|
||||||
|
{
|
||||||
|
if (grabber is HandGrabInteractor handGrabber)
|
||||||
|
{
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
|
||||||
|
if (handPose != null && handPose is MeshHandPose meshHandPose)
|
||||||
|
{
|
||||||
|
Pose realHandPose = CalculateActualHandPose(m_BestGrabPose.grabOffset);
|
||||||
|
meshHandPose.SetJointPose(JointType.Wrist, realHandPose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pose CalculateActualHandPose(GrabOffset grabOffset)
|
||||||
|
{
|
||||||
|
Quaternion handRot = transform.rotation * Quaternion.Inverse(grabOffset.rotOffset);
|
||||||
|
Quaternion handRotDiff = handRot * Quaternion.Inverse(grabOffset.sourceRotation);
|
||||||
|
Vector3 handPos = transform.position - handRotDiff * grabOffset.posOffset;
|
||||||
|
return new Pose(handPos, handRot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
// conditions signed by you and all SDK and API requirements,
|
// conditions signed by you and all SDK and API requirements,
|
||||||
// specifications, and documentation provided by HTC to You."
|
// specifications, and documentation provided by HTC to You."
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
@@ -17,6 +19,26 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class HandGrabInteractor : MonoBehaviour, IHandGrabber
|
public class HandGrabInteractor : MonoBehaviour, IHandGrabber
|
||||||
{
|
{
|
||||||
|
#region Log
|
||||||
|
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandGrabInteractor";
|
||||||
|
private StringBuilder m_sb = null;
|
||||||
|
internal StringBuilder sb
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_sb == null) { m_sb = new StringBuilder(); }
|
||||||
|
return m_sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||||
|
int logFrame = 0;
|
||||||
|
bool printIntervalLog => logFrame == 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
private enum GrabState
|
private enum GrabState
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
@@ -40,43 +62,23 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
private float m_GrabDistance = 0.03f;
|
private float m_GrabDistance = 0.03f;
|
||||||
public float grabDistance { get { return m_GrabDistance; } set { m_GrabDistance = value; } }
|
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;
|
public bool isLeft => handedness == Handedness.Left;
|
||||||
#endregion
|
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private GrabColliderManager colliderManager;
|
private HandGrabberEvent m_OnBeginGrab = new HandGrabberEvent();
|
||||||
|
public HandGrabberEvent onBeginGrab => m_OnBeginGrab;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private HandGrabberEvent m_OnEndGrab = new HandGrabberEvent();
|
||||||
|
public HandGrabberEvent onEndGrab => m_OnEndGrab;
|
||||||
|
#endregion
|
||||||
|
|
||||||
private readonly float MinGrabScore = 0.25f;
|
private readonly float MinGrabScore = 0.25f;
|
||||||
private readonly float MinDistanceScore = 0.25f;
|
private readonly float MinDistanceScore = 0.25f;
|
||||||
private HandGrabInteractable currentCaidate = null;
|
private HandGrabInteractable currentCandidate = null;
|
||||||
private GrabPose grabPose = GrabPose.Identity;
|
|
||||||
private GrabState m_State = GrabState.None;
|
private GrabState m_State = GrabState.None;
|
||||||
private Pose[] fingerTipPoses => new Pose[]
|
private Pose wristPose = Pose.identity;
|
||||||
{
|
private Vector3[] fingerTipPosition = new Vector3[(int)FingerId.Count];
|
||||||
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 OnBeginGrab beginGrabHandler;
|
||||||
private OnEndGrab endGrabHandler;
|
private OnEndGrab endGrabHandler;
|
||||||
|
|
||||||
@@ -86,45 +88,36 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
m_HandGrabState = new HandGrabState(isLeft);
|
m_HandGrabState = new HandGrabState(isLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
if (colliderManager != null)
|
|
||||||
{
|
|
||||||
colliderManager.EnableCollider(m_EnableCollider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
GrabManager.RegisterGrabber(this);
|
GrabManager.RegisterGrabber(this);
|
||||||
if (colliderManager != null)
|
|
||||||
{
|
|
||||||
colliderManager.AddImmovableCollisionListener(FreezeHandPose);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
GrabManager.UnregisterGrabber(this);
|
GrabManager.UnregisterGrabber(this);
|
||||||
if (colliderManager != null)
|
|
||||||
{
|
|
||||||
colliderManager.RemoveImmovableCollisionListener(FreezeHandPose);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
m_HandGrabState.UpdateState();
|
m_HandGrabState.UpdateState();
|
||||||
if (isFrozen)
|
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
|
||||||
|
if (handPose != null)
|
||||||
{
|
{
|
||||||
return;
|
handPose.GetPosition(JointType.Wrist, out wristPose.position);
|
||||||
|
handPose.GetRotation(JointType.Wrist, out wristPose.rotation);
|
||||||
|
handPose.GetPosition(JointType.Thumb_Tip, out fingerTipPosition[(int)FingerId.Thumb]);
|
||||||
|
handPose.GetPosition(JointType.Index_Tip, out fingerTipPosition[(int)FingerId.Index]);
|
||||||
|
handPose.GetPosition(JointType.Middle_Tip, out fingerTipPosition[(int)FingerId.Middle]);
|
||||||
|
handPose.GetPosition(JointType.Ring_Tip, out fingerTipPosition[(int)FingerId.Ring]);
|
||||||
|
handPose.GetPosition(JointType.Pinky_Tip, out fingerTipPosition[(int)FingerId.Pinky]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_State != GrabState.Grabbing)
|
if (m_State != GrabState.Grabbing)
|
||||||
{
|
{
|
||||||
FindCandidate();
|
FindCandidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_State)
|
switch (m_State)
|
||||||
{
|
{
|
||||||
case GrabState.None:
|
case GrabState.None:
|
||||||
@@ -142,51 +135,15 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
|
|
||||||
#region Public Interface
|
#region Public Interface
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the current joint pose of the grabber.
|
/// Checks if the specified joint type is required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jointId">The id of the joint for which to get the pose.</param>
|
/// <param name="joint">The joint that need to check.</param>
|
||||||
/// <returns>The current pose of the specified joint.</returns>
|
/// <returns>True if the joint is required; otherwise, false.</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)
|
public bool IsRequiredJoint(JointType joint)
|
||||||
{
|
{
|
||||||
if (m_Grabbable != null)
|
if (m_Grabbable != null)
|
||||||
{
|
{
|
||||||
GetJointIndex(joint, out int group, out _);
|
HandData.GetJointIndex(joint, out int group, out _);
|
||||||
switch (group)
|
switch (group)
|
||||||
{
|
{
|
||||||
case 2: return m_Grabbable.fingerRequirement.thumb == GrabRequirement.Required;
|
case 2: return m_Grabbable.fingerRequirement.thumb == GrabRequirement.Required;
|
||||||
@@ -203,6 +160,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// Add a listener for the event triggered when the grabber begins grabbing.
|
/// Add a listener for the event triggered when the grabber begins grabbing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to be called when the grabber begins grabbing.</param>
|
/// <param name="handler">The method to be called when the grabber begins grabbing.</param>
|
||||||
|
[Obsolete("Please use onBeginGrab instead.")]
|
||||||
public void AddBeginGrabListener(OnBeginGrab handler)
|
public void AddBeginGrabListener(OnBeginGrab handler)
|
||||||
{
|
{
|
||||||
beginGrabHandler += handler;
|
beginGrabHandler += handler;
|
||||||
@@ -212,6 +170,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// Remove a listener for the event triggered when the grabber begins grabbing.
|
/// Remove a listener for the event triggered when the grabber begins grabbing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||||
|
[Obsolete("Please use onBeginGrab instead.")]
|
||||||
public void RemoveBeginGrabListener(OnBeginGrab handler)
|
public void RemoveBeginGrabListener(OnBeginGrab handler)
|
||||||
{
|
{
|
||||||
beginGrabHandler -= handler;
|
beginGrabHandler -= handler;
|
||||||
@@ -221,6 +180,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// Add a listener for the event triggered when the grabber ends grabbing.
|
/// Add a listener for the event triggered when the grabber ends grabbing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to be called when the grabber ends grabbing.</param>
|
/// <param name="handler">The method to be called when the grabber ends grabbing.</param>
|
||||||
|
[Obsolete("Please use onEndGrab instead.")]
|
||||||
public void AddEndGrabListener(OnEndGrab handler)
|
public void AddEndGrabListener(OnEndGrab handler)
|
||||||
{
|
{
|
||||||
endGrabHandler += handler;
|
endGrabHandler += handler;
|
||||||
@@ -230,6 +190,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// Remove a listener for the event triggered when the grabber ends grabbing.
|
/// Remove a listener for the event triggered when the grabber ends grabbing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||||
|
[Obsolete("Please use onEndGrab instead.")]
|
||||||
public void RemoveEndGrabListener(OnEndGrab handler)
|
public void RemoveEndGrabListener(OnEndGrab handler)
|
||||||
{
|
{
|
||||||
endGrabHandler -= handler;
|
endGrabHandler -= handler;
|
||||||
@@ -241,19 +202,20 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void FindCandidate()
|
private void FindCandidate()
|
||||||
{
|
{
|
||||||
|
currentCandidate = null;
|
||||||
float distanceScore = float.MinValue;
|
float distanceScore = float.MinValue;
|
||||||
if (GetClosestGrabbable(m_GrabDistance, out HandGrabInteractable grabbable, out float score) && score > distanceScore)
|
if (GetClosestGrabbable(m_GrabDistance, out HandGrabInteractable grabbable, out float score) && score > distanceScore)
|
||||||
{
|
{
|
||||||
distanceScore = score;
|
distanceScore = score;
|
||||||
currentCaidate = grabbable;
|
currentCandidate = grabbable;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentCaidate != null)
|
if (currentCandidate != null)
|
||||||
{
|
{
|
||||||
float grabScore = Grab.CalculateHandGrabScore(this, currentCaidate);
|
float grabScore = Grab.CalculateHandGrabScore(this, currentCandidate);
|
||||||
if (distanceScore < MinDistanceScore || grabScore < MinGrabScore)
|
if (distanceScore < MinDistanceScore || grabScore < MinGrabScore)
|
||||||
{
|
{
|
||||||
currentCaidate = null;
|
currentCandidate = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,9 +234,10 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
||||||
{
|
{
|
||||||
interactable.ShowIndicator(false, this);
|
interactable.ShowIndicator(false, this);
|
||||||
foreach (Pose fingerTipPose in fingerTipPoses)
|
|
||||||
|
foreach (Vector3 tipPos in fingerTipPosition)
|
||||||
{
|
{
|
||||||
float distanceScore = interactable.CalculateDistanceScore(fingerTipPose.position, grabDistance);
|
float distanceScore = interactable.CalculateDistanceScore(tipPos, grabDistance);
|
||||||
if (distanceScore > maxScore)
|
if (distanceScore > maxScore)
|
||||||
{
|
{
|
||||||
maxScore = distanceScore;
|
maxScore = distanceScore;
|
||||||
@@ -294,7 +257,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void NoneUpdate()
|
private void NoneUpdate()
|
||||||
{
|
{
|
||||||
if (currentCaidate != null)
|
if (currentCandidate != null)
|
||||||
{
|
{
|
||||||
m_State = GrabState.Hover;
|
m_State = GrabState.Hover;
|
||||||
}
|
}
|
||||||
@@ -305,27 +268,22 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void HoverUpdate()
|
private void HoverUpdate()
|
||||||
{
|
{
|
||||||
if (currentCaidate == null)
|
if (currentCandidate == null)
|
||||||
{
|
{
|
||||||
m_State = GrabState.None;
|
m_State = GrabState.None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Grab.HandBeginGrab(this, currentCaidate))
|
if (Grab.HandBeginGrab(this, currentCandidate))
|
||||||
{
|
{
|
||||||
m_State = GrabState.Grabbing;
|
m_State = GrabState.Grabbing;
|
||||||
|
m_Grabbable = currentCandidate;
|
||||||
m_Grabbable = currentCaidate;
|
|
||||||
m_Grabbable.SetGrabber(this);
|
m_Grabbable.SetGrabber(this);
|
||||||
m_Grabbable.ShowIndicator(false, 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);
|
beginGrabHandler?.Invoke(this);
|
||||||
|
onBeginGrab?.Invoke(this);
|
||||||
|
|
||||||
|
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand begins to grab the {m_Grabbable.name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,55 +294,16 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
{
|
{
|
||||||
if (Grab.HandDoneGrab(this, m_Grabbable) || !Grab.HandIsGrabbing(this, m_Grabbable))
|
if (Grab.HandDoneGrab(this, m_Grabbable) || !Grab.HandIsGrabbing(this, m_Grabbable))
|
||||||
{
|
{
|
||||||
|
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand ends to grab the {m_Grabbable.name}");
|
||||||
|
|
||||||
|
endGrabHandler?.Invoke(this);
|
||||||
|
onEndGrab?.Invoke(this);
|
||||||
m_Grabbable.SetGrabber(null);
|
m_Grabbable.SetGrabber(null);
|
||||||
m_Grabbable = null;
|
m_Grabbable = null;
|
||||||
m_State = GrabState.Hover;
|
m_State = GrabState.Hover;
|
||||||
endGrabHandler?.Invoke(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_Grabbable.UpdatePositionAndRotation(wristPose);
|
||||||
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,148 @@
|
|||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public class OneGrabMoveConstraint : IOneHandContraintMovement
|
||||||
|
{
|
||||||
|
#region Log
|
||||||
|
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.OneGrabMoveConstraint";
|
||||||
|
private StringBuilder m_sb = null;
|
||||||
|
internal StringBuilder sb
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_sb == null) { m_sb = new StringBuilder(); }
|
||||||
|
return m_sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||||
|
int logFrame = 0;
|
||||||
|
bool printIntervalLog => logFrame == 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private Transform m_Constraint;
|
||||||
|
[SerializeField]
|
||||||
|
private ConstraintInfo m_NegativeXMove = ConstraintInfo.Identity;
|
||||||
|
private float defaultNegativeXPos = 0.0f;
|
||||||
|
[SerializeField]
|
||||||
|
private ConstraintInfo m_PositiveXMove = ConstraintInfo.Identity;
|
||||||
|
private float defaultPositiveXPos = 0.0f;
|
||||||
|
[SerializeField]
|
||||||
|
private ConstraintInfo m_NegativeYMove = ConstraintInfo.Identity;
|
||||||
|
private float defaultNegativeYPos = 0.0f;
|
||||||
|
[SerializeField]
|
||||||
|
private ConstraintInfo m_PositiveYMove = ConstraintInfo.Identity;
|
||||||
|
private float defaultPositiveYPos = 0.0f;
|
||||||
|
[SerializeField]
|
||||||
|
private ConstraintInfo m_NegativeZMove = ConstraintInfo.Identity;
|
||||||
|
private float defaultNegativeZPos = 0.0f;
|
||||||
|
[SerializeField]
|
||||||
|
private ConstraintInfo m_PositiveZMove = ConstraintInfo.Identity;
|
||||||
|
private float defaultPositiveZPos = 0.0f;
|
||||||
|
private Pose previousHandPose = Pose.identity;
|
||||||
|
private GrabPose currentGrabPose = GrabPose.Identity;
|
||||||
|
|
||||||
|
public override void Initialize(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
if (grabbable is HandGrabInteractable handGrabbable)
|
||||||
|
{
|
||||||
|
if (m_Constraint == null)
|
||||||
|
{
|
||||||
|
m_Constraint = handGrabbable.transform;
|
||||||
|
WARNING("Since no constraint object is set, self will be used as the constraint object.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_NegativeXMove.enableConstraint) { defaultNegativeXPos = m_Constraint.position.x - m_NegativeXMove.value; }
|
||||||
|
if (m_PositiveXMove.enableConstraint) { defaultPositiveXPos = m_Constraint.position.x + m_PositiveXMove.value; }
|
||||||
|
if (m_NegativeYMove.enableConstraint) { defaultNegativeYPos = m_Constraint.position.y - m_NegativeYMove.value; }
|
||||||
|
if (m_PositiveYMove.enableConstraint) { defaultPositiveYPos = m_Constraint.position.y + m_PositiveYMove.value; }
|
||||||
|
if (m_NegativeZMove.enableConstraint) { defaultNegativeZPos = m_Constraint.position.z - m_NegativeZMove.value; }
|
||||||
|
if (m_PositiveZMove.enableConstraint) { defaultPositiveZPos = m_Constraint.position.z + m_PositiveZMove.value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBeginGrabbed(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
if (grabbable is HandGrabInteractable handGrabbable)
|
||||||
|
{
|
||||||
|
currentGrabPose = handGrabbable.bestGrabPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grabbable.grabber is HandGrabInteractor handGrabber)
|
||||||
|
{
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
|
||||||
|
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
|
||||||
|
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
|
||||||
|
previousHandPose = new Pose(wristPos, wristRot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdatePose(Pose handPose)
|
||||||
|
{
|
||||||
|
if (previousHandPose == Pose.identity)
|
||||||
|
{
|
||||||
|
previousHandPose = handPose;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quaternion previousRotOffset = previousHandPose.rotation * Quaternion.Inverse(currentGrabPose.grabOffset.sourceRotation);
|
||||||
|
Vector3 previousPos = previousHandPose.position + previousRotOffset * currentGrabPose.grabOffset.posOffset;
|
||||||
|
|
||||||
|
Quaternion currentRotOffset = handPose.rotation * Quaternion.Inverse(currentGrabPose.grabOffset.sourceRotation);
|
||||||
|
Vector3 currentPos = handPose.position + currentRotOffset * currentGrabPose.grabOffset.posOffset;
|
||||||
|
|
||||||
|
Vector3 handOffset = currentPos - previousPos;
|
||||||
|
|
||||||
|
if (m_NegativeXMove.enableConstraint)
|
||||||
|
{
|
||||||
|
float x = (m_Constraint.position + handOffset).x;
|
||||||
|
x = Mathf.Max(defaultNegativeXPos, x);
|
||||||
|
m_Constraint.position = new Vector3(x, m_Constraint.position.y, m_Constraint.position.z);
|
||||||
|
}
|
||||||
|
if (m_PositiveXMove.enableConstraint)
|
||||||
|
{
|
||||||
|
float x = (m_Constraint.position + handOffset).x;
|
||||||
|
x = Mathf.Min(defaultPositiveXPos, x);
|
||||||
|
m_Constraint.position = new Vector3(x, m_Constraint.position.y, m_Constraint.position.z);
|
||||||
|
}
|
||||||
|
if (m_NegativeYMove.enableConstraint)
|
||||||
|
{
|
||||||
|
float y = (m_Constraint.position + handOffset).y;
|
||||||
|
y = Mathf.Max(defaultNegativeYPos, y);
|
||||||
|
m_Constraint.position = new Vector3(m_Constraint.position.x, y, m_Constraint.position.z);
|
||||||
|
}
|
||||||
|
if (m_PositiveYMove.enableConstraint)
|
||||||
|
{
|
||||||
|
float y = (m_Constraint.position + handOffset).y;
|
||||||
|
y = Mathf.Min(defaultPositiveYPos, y);
|
||||||
|
m_Constraint.position = new Vector3(m_Constraint.position.x, y, m_Constraint.position.z);
|
||||||
|
}
|
||||||
|
if (m_NegativeZMove.enableConstraint)
|
||||||
|
{
|
||||||
|
float z = (m_Constraint.position + handOffset).z;
|
||||||
|
z = Mathf.Max(defaultNegativeZPos, z);
|
||||||
|
m_Constraint.position = new Vector3(m_Constraint.position.x, m_Constraint.position.y, z);
|
||||||
|
}
|
||||||
|
if (m_PositiveZMove.enableConstraint)
|
||||||
|
{
|
||||||
|
float z = (m_Constraint.position + handOffset).z;
|
||||||
|
z = Mathf.Min(defaultPositiveZPos, z);
|
||||||
|
m_Constraint.position = new Vector3(m_Constraint.position.x, m_Constraint.position.y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
previousHandPose = handPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEndGrabbed(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
currentGrabPose = GrabPose.Identity;
|
||||||
|
previousHandPose = Pose.identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6d521c71dbb8287409daf6ef81005d79
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public class OneGrabRotateConstraint : IOneHandContraintMovement
|
||||||
|
{
|
||||||
|
#region Log
|
||||||
|
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.OneGrabRotateConstraint";
|
||||||
|
private StringBuilder m_sb = null;
|
||||||
|
internal StringBuilder sb
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_sb == null) { m_sb = new StringBuilder(); }
|
||||||
|
return m_sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||||
|
int logFrame = 0;
|
||||||
|
bool printIntervalLog => logFrame == 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private enum RotationAxis
|
||||||
|
{
|
||||||
|
XAxis = 0,
|
||||||
|
YAxis = 1,
|
||||||
|
ZAxis = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private Transform m_Constraint;
|
||||||
|
[SerializeField]
|
||||||
|
private Transform m_Pivot;
|
||||||
|
[SerializeField]
|
||||||
|
private RotationAxis m_RotationAxis = RotationAxis.XAxis;
|
||||||
|
[SerializeField]
|
||||||
|
private ConstraintInfo m_ClockwiseAngle = ConstraintInfo.Identity;
|
||||||
|
[SerializeField]
|
||||||
|
private ConstraintInfo m_CounterclockwiseAngle = ConstraintInfo.Identity;
|
||||||
|
private float totalRotationAngle = 0.0f;
|
||||||
|
private Pose previousHandPose = Pose.identity;
|
||||||
|
|
||||||
|
public override void Initialize(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
if (grabbable is HandGrabInteractable handGrabbable)
|
||||||
|
{
|
||||||
|
if (m_Constraint == null)
|
||||||
|
{
|
||||||
|
m_Constraint = handGrabbable.transform;
|
||||||
|
WARNING("Since no constraint object is set, self will be used as the constraint object.");
|
||||||
|
}
|
||||||
|
if (m_Pivot == null)
|
||||||
|
{
|
||||||
|
m_Pivot = handGrabbable.transform;
|
||||||
|
WARNING("Since no pivot is set, self will be used as the pivot.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBeginGrabbed(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
if (grabbable.grabber is HandGrabInteractor handGrabber)
|
||||||
|
{
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
|
||||||
|
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
|
||||||
|
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
|
||||||
|
previousHandPose = new Pose(wristPos, wristRot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdatePose(Pose handPose)
|
||||||
|
{
|
||||||
|
if (previousHandPose == Pose.identity)
|
||||||
|
{
|
||||||
|
previousHandPose = handPose;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 axis = Vector3.zero;
|
||||||
|
switch (m_RotationAxis)
|
||||||
|
{
|
||||||
|
case RotationAxis.XAxis: axis = Vector3.right; break;
|
||||||
|
case RotationAxis.YAxis: axis = Vector3.up; break;
|
||||||
|
case RotationAxis.ZAxis: axis = Vector3.forward; break;
|
||||||
|
}
|
||||||
|
Vector3 worldAxis = m_Pivot.TransformDirection(axis);
|
||||||
|
|
||||||
|
Vector3 previousOffset = previousHandPose.position - m_Pivot.position;
|
||||||
|
Vector3 previousVector = Vector3.ProjectOnPlane(previousOffset, worldAxis);
|
||||||
|
|
||||||
|
Vector3 targetOffset = handPose.position - m_Pivot.position;
|
||||||
|
Vector3 targetVector = Vector3.ProjectOnPlane(targetOffset, worldAxis);
|
||||||
|
|
||||||
|
float angleDelta = Vector3.Angle(previousVector, targetVector);
|
||||||
|
angleDelta *= Vector3.Dot(Vector3.Cross(previousVector, targetVector), worldAxis) > 0.0f ? 1.0f : -1.0f;
|
||||||
|
|
||||||
|
float previousAngle = totalRotationAngle;
|
||||||
|
totalRotationAngle += angleDelta;
|
||||||
|
if (m_CounterclockwiseAngle.enableConstraint)
|
||||||
|
{
|
||||||
|
totalRotationAngle = Mathf.Max(totalRotationAngle, -m_CounterclockwiseAngle.value);
|
||||||
|
}
|
||||||
|
if (m_ClockwiseAngle.enableConstraint)
|
||||||
|
{
|
||||||
|
totalRotationAngle = Mathf.Min(totalRotationAngle, m_ClockwiseAngle.value);
|
||||||
|
}
|
||||||
|
angleDelta = totalRotationAngle - previousAngle;
|
||||||
|
m_Constraint.RotateAround(m_Pivot.position, worldAxis, angleDelta);
|
||||||
|
|
||||||
|
previousHandPose = handPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEndGrabbed(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
previousHandPose = Pose.identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f25051021522804d90dc5448b7657a2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
// "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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public class HandColliderController : MonoBehaviour
|
||||||
|
{
|
||||||
|
#region Log
|
||||||
|
|
||||||
|
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandColliderController";
|
||||||
|
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||||
|
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||||
|
int logFrame = 0;
|
||||||
|
bool printIntervalLog => logFrame == 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private HandMeshManager m_HandMesh;
|
||||||
|
public HandMeshManager handMesh { get { return m_HandMesh; } set { m_HandMesh = value; } }
|
||||||
|
|
||||||
|
private bool m_EnableCollider = true;
|
||||||
|
public bool enableCollider
|
||||||
|
{
|
||||||
|
get { return m_EnableCollider; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_EnableCollider = value;
|
||||||
|
rootJoint.gameObject.SetActive(m_EnableCollider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Name Definition
|
||||||
|
// The order of joint name MUST align with runtime's definition
|
||||||
|
private readonly string[] JointsName = new string[]
|
||||||
|
{
|
||||||
|
"WaveBone_0", // WVR_HandJoint_Palm = 0
|
||||||
|
"WaveBone_1", // WVR_HandJoint_Wrist = 1
|
||||||
|
"WaveBone_2", // WVR_HandJoint_Thumb_Joint0 = 2
|
||||||
|
"WaveBone_3", // WVR_HandJoint_Thumb_Joint1 = 3
|
||||||
|
"WaveBone_4", // WVR_HandJoint_Thumb_Joint2 = 4
|
||||||
|
"WaveBone_5", // WVR_HandJoint_Thumb_Tip = 5
|
||||||
|
"WaveBone_6", // WVR_HandJoint_Index_Joint0 = 6
|
||||||
|
"WaveBone_7", // WVR_HandJoint_Index_Joint1 = 7
|
||||||
|
"WaveBone_8", // WVR_HandJoint_Index_Joint2 = 8
|
||||||
|
"WaveBone_9", // WVR_HandJoint_Index_Joint3 = 9
|
||||||
|
"WaveBone_10", // WVR_HandJoint_Index_Tip = 10
|
||||||
|
"WaveBone_11", // WVR_HandJoint_Middle_Joint0 = 11
|
||||||
|
"WaveBone_12", // WVR_HandJoint_Middle_Joint1 = 12
|
||||||
|
"WaveBone_13", // WVR_HandJoint_Middle_Joint2 = 13
|
||||||
|
"WaveBone_14", // WVR_HandJoint_Middle_Joint3 = 14
|
||||||
|
"WaveBone_15", // WVR_HandJoint_Middle_Tip = 15
|
||||||
|
"WaveBone_16", // WVR_HandJoint_Ring_Joint0 = 16
|
||||||
|
"WaveBone_17", // WVR_HandJoint_Ring_Joint1 = 17
|
||||||
|
"WaveBone_18", // WVR_HandJoint_Ring_Joint2 = 18
|
||||||
|
"WaveBone_19", // WVR_HandJoint_Ring_Joint3 = 19
|
||||||
|
"WaveBone_20", // WVR_HandJoint_Ring_Tip = 20
|
||||||
|
"WaveBone_21", // WVR_HandJoint_Pinky_Joint0 = 21
|
||||||
|
"WaveBone_22", // WVR_HandJoint_Pinky_Joint0 = 22
|
||||||
|
"WaveBone_23", // WVR_HandJoint_Pinky_Joint0 = 23
|
||||||
|
"WaveBone_24", // WVR_HandJoint_Pinky_Joint0 = 24
|
||||||
|
"WaveBone_25" // WVR_HandJoint_Pinky_Tip = 25
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private float handMass = 1.0f;
|
||||||
|
private JointCollider[] jointsCollider = new JointCollider[(int)JointType.Count];
|
||||||
|
private Transform rootJoint = null;
|
||||||
|
private Transform rootJointParent = null;
|
||||||
|
private Rigidbody rootJointRigidbody = null;
|
||||||
|
private Vector3 lastRootPos;
|
||||||
|
private Quaternion lastRotation;
|
||||||
|
private bool isInit = false;
|
||||||
|
private bool isTracked = true;
|
||||||
|
private List<Vector3> collisionDirections = new List<Vector3>();
|
||||||
|
private bool isGrabbing = false;
|
||||||
|
|
||||||
|
#region MonoBehaviour
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
StartCoroutine(WaitForInit());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (rootJoint != null)
|
||||||
|
{
|
||||||
|
JointCollider jointCollider = rootJoint.GetComponent<JointCollider>();
|
||||||
|
if (jointCollider != null)
|
||||||
|
{
|
||||||
|
jointCollider.RemoveJointCollisionListener(OnJointCollision);
|
||||||
|
}
|
||||||
|
Destroy(rootJoint.gameObject);
|
||||||
|
}
|
||||||
|
isInit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
JointCollider[] fullJointsCollider = FindObjectsOfType<JointCollider>(true);
|
||||||
|
for (int i = 0; i < fullJointsCollider.Length - 1; i++)
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j < fullJointsCollider.Length; j++)
|
||||||
|
{
|
||||||
|
Physics.IgnoreCollision(fullJointsCollider[i].Collider, fullJointsCollider[j].Collider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!isInit) { return; }
|
||||||
|
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(m_HandMesh.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
|
||||||
|
if (isTracked != handPose.IsTracked())
|
||||||
|
{
|
||||||
|
isTracked = handPose.IsTracked();
|
||||||
|
ChangeTrackingState(isTracked);
|
||||||
|
}
|
||||||
|
if (!isTracked) { return; }
|
||||||
|
|
||||||
|
for (int i = 0; i < jointsCollider.Length; i++)
|
||||||
|
{
|
||||||
|
if (jointsCollider[i] == null) { continue; }
|
||||||
|
|
||||||
|
handPose.GetPosition((JointType)i, out Vector3 jointPosition);
|
||||||
|
handPose.GetRotation((JointType)i, out Quaternion jointRotation);
|
||||||
|
if (i == (int)JointType.Wrist)
|
||||||
|
{
|
||||||
|
if (lastRootPos == Vector3.zero || lastRotation == Quaternion.identity)
|
||||||
|
{
|
||||||
|
rootJoint.localPosition = jointPosition;
|
||||||
|
rootJoint.localRotation = jointRotation;
|
||||||
|
}
|
||||||
|
lastRootPos = jointPosition;
|
||||||
|
lastRotation = jointRotation;
|
||||||
|
}
|
||||||
|
jointsCollider[i].transform.rotation = rootJointParent.rotation * jointRotation;
|
||||||
|
}
|
||||||
|
if (!isGrabbing)
|
||||||
|
{
|
||||||
|
m_HandMesh.SetJointPositionAndRotation(JointType.Wrist, rootJoint.position, rootJoint.rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FixedUpdate()
|
||||||
|
{
|
||||||
|
if (lastRootPos == Vector3.zero || lastRotation == Quaternion.identity) { return; }
|
||||||
|
|
||||||
|
if (isGrabbing)
|
||||||
|
{
|
||||||
|
rootJointRigidbody.velocity = Vector3.zero;
|
||||||
|
rootJointRigidbody.angularVelocity = Vector3.zero;
|
||||||
|
rootJoint.localPosition = lastRootPos;
|
||||||
|
rootJoint.localRotation = lastRotation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateVelocity();
|
||||||
|
UpdateAngularVelocity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private IEnumerator WaitForInit()
|
||||||
|
{
|
||||||
|
yield return new WaitUntil(() => m_HandMesh != null);
|
||||||
|
|
||||||
|
if (rootJoint != null)
|
||||||
|
{
|
||||||
|
JointCollider jointCollider = rootJoint.GetComponent<JointCollider>();
|
||||||
|
jointCollider.AddJointCollisionListener(OnJointCollision);
|
||||||
|
rootJointRigidbody = jointCollider.InitRigidbody(handMass);
|
||||||
|
isInit = true;
|
||||||
|
|
||||||
|
DEBUG("Hand Collider init successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes by copying the structure of the hand mesh and sequentially adding joint collider.
|
||||||
|
/// </summary>
|
||||||
|
public void InitJointColliders(Transform handRootJoint)
|
||||||
|
{
|
||||||
|
rootJointParent = handRootJoint.parent;
|
||||||
|
rootJoint = Instantiate(handRootJoint, rootJointParent);
|
||||||
|
rootJoint.name = handRootJoint.name;
|
||||||
|
List<Transform> totalTransforms = new List<Transform>() { rootJoint };
|
||||||
|
GetAllChildrenTransforms(rootJoint, ref totalTransforms);
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)JointType.Count; i++)
|
||||||
|
{
|
||||||
|
Transform target = totalTransforms.FirstOrDefault(x => x.name == JointsName[i]);
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
JointCollider jointCollider = target.gameObject.AddComponent<JointCollider>();
|
||||||
|
jointCollider.SetJointId(i);
|
||||||
|
jointsCollider[i] = jointCollider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetAllChildrenTransforms(Transform parent, ref List<Transform> childrenTransforms)
|
||||||
|
{
|
||||||
|
foreach (Transform child in parent)
|
||||||
|
{
|
||||||
|
childrenTransforms.Add(child);
|
||||||
|
GetAllChildrenTransforms(child, ref childrenTransforms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the velocity of a Rigidbody.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateVelocity()
|
||||||
|
{
|
||||||
|
Vector3 vel = (lastRootPos - rootJoint.position) / Time.deltaTime;
|
||||||
|
if (IsValidVelocity(vel))
|
||||||
|
{
|
||||||
|
if (collisionDirections.Count > 0)
|
||||||
|
{
|
||||||
|
float minAngle = float.MaxValue;
|
||||||
|
Vector3 closestDirection = Vector3.zero;
|
||||||
|
foreach (Vector3 direction in collisionDirections.ToList())
|
||||||
|
{
|
||||||
|
float angle = Mathf.Abs(Vector3.Angle(direction, vel));
|
||||||
|
if (angle < minAngle)
|
||||||
|
{
|
||||||
|
minAngle = angle;
|
||||||
|
closestDirection = direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collisionDirections.Clear();
|
||||||
|
|
||||||
|
Vector3 adjustedDirection = closestDirection;
|
||||||
|
if (Vector3.Dot(vel, closestDirection) > 0)
|
||||||
|
{
|
||||||
|
adjustedDirection *= -1f;
|
||||||
|
}
|
||||||
|
vel = Vector3.ProjectOnPlane(vel, adjustedDirection);
|
||||||
|
if (vel.magnitude > 1)
|
||||||
|
{
|
||||||
|
vel.Normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rootJointRigidbody.velocity = vel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the angularVelocity of a Rigidbody.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateAngularVelocity()
|
||||||
|
{
|
||||||
|
Quaternion diffRotation = Quaternion.Inverse(rootJoint.rotation) * lastRotation;
|
||||||
|
diffRotation.ToAngleAxis(out float angleInDegree, out Vector3 rotationAxis);
|
||||||
|
Vector3 angVel = (angleInDegree * rotationAxis * Mathf.Deg2Rad) / Time.deltaTime;
|
||||||
|
if (IsValidVelocity(angVel))
|
||||||
|
{
|
||||||
|
rootJointRigidbody.angularVelocity = angVel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the vector is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vector">The vector to be checked.</param>
|
||||||
|
/// <returns>rue if the vector is valid; otherwise, false.</returns>
|
||||||
|
private bool IsValidVelocity(Vector3 vector)
|
||||||
|
{
|
||||||
|
return !float.IsNaN(vector.x) && !float.IsNaN(vector.y) && !float.IsNaN(vector.z)
|
||||||
|
&& !float.IsInfinity(vector.x) && !float.IsInfinity(vector.y) && !float.IsInfinity(vector.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Event CallBack
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When tracking state changing, reset the pose and enable/disable collider.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isTracked">The bool that tracking state.</param>
|
||||||
|
private void ChangeTrackingState(bool isTracked)
|
||||||
|
{
|
||||||
|
if (!isTracked)
|
||||||
|
{
|
||||||
|
lastRootPos = Vector3.zero;
|
||||||
|
lastRotation = Quaternion.identity;
|
||||||
|
rootJointRigidbody.velocity = Vector3.zero;
|
||||||
|
rootJointRigidbody.angularVelocity = Vector3.zero;
|
||||||
|
}
|
||||||
|
foreach (JointCollider jointCollider in jointsCollider)
|
||||||
|
{
|
||||||
|
jointCollider.Collider.enabled = m_EnableCollider && isTracked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the hand begins grabbing an object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabber">The grabber that grabbing something.</param>
|
||||||
|
public void OnHandBeginGrab(IGrabber grabber)
|
||||||
|
{
|
||||||
|
EnableKinematic(true);
|
||||||
|
isGrabbing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the hand ends grabbing an object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grabber">The grabber that releasing something.</param>
|
||||||
|
public void OnHandEndGrab(IGrabber grabber)
|
||||||
|
{
|
||||||
|
EnableKinematic(false);
|
||||||
|
isGrabbing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable/Disable the rigidbody of hand collider.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enable">The bool that enable or disable the rigidbody.</param>
|
||||||
|
private void EnableKinematic(bool enable)
|
||||||
|
{
|
||||||
|
if (rootJointRigidbody != null)
|
||||||
|
{
|
||||||
|
rootJointRigidbody.isKinematic = enable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a joint collider collides with another object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collision">The collision data.</param>
|
||||||
|
/// <param name="state">The state of the collision.</param>
|
||||||
|
private void OnJointCollision(Collision collision, JointCollider.CollisionState state)
|
||||||
|
{
|
||||||
|
if (isGrabbing) { return; }
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case JointCollider.CollisionState.Enter:
|
||||||
|
case JointCollider.CollisionState.Stay:
|
||||||
|
if (collision.contactCount > 0 && (collision.rigidbody == null || collision.rigidbody.isKinematic))
|
||||||
|
{
|
||||||
|
ContactPoint[] contactPoints = new ContactPoint[collision.contactCount];
|
||||||
|
collision.GetContacts(contactPoints);
|
||||||
|
foreach (ContactPoint contactPoint in contactPoints)
|
||||||
|
{
|
||||||
|
collisionDirections.Add(contactPoint.normal * -1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 199fd8ec06e6e7e49a3fab9a9b7e7988
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -12,6 +12,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
|
||||||
#if UNITY_XR_HANDS
|
#if UNITY_XR_HANDS
|
||||||
using UnityEngine.XR.Hands;
|
using UnityEngine.XR.Hands;
|
||||||
@@ -779,7 +781,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// <param name="joint">The type of joint.</param>
|
/// <param name="joint">The type of joint.</param>
|
||||||
/// <param name="rotation">The reference to store the default joint rotation.</param>
|
/// <param name="rotation">The reference to store the default joint rotation.</param>
|
||||||
/// <returns>True if the default joint rotation is successfully retrieved; otherwise, false.</returns>
|
/// <returns>True if the default joint rotation is successfully retrieved; otherwise, false.</returns>
|
||||||
public bool GetDefaultJointRotationInGesture(bool isLeft, HandGrabGesture handGrabGesture, JointType joint, ref Quaternion rotation)
|
public static bool GetDefaultJointRotationInGesture(bool isLeft, HandGrabGesture handGrabGesture, JointType joint, ref Quaternion rotation)
|
||||||
{
|
{
|
||||||
FingerBendingLevel bendingLevel = FingerBendingLevel.Level0;
|
FingerBendingLevel bendingLevel = FingerBendingLevel.Level0;
|
||||||
GetJointIndex(joint, out int group, out int index);
|
GetJointIndex(joint, out int group, out int index);
|
||||||
@@ -802,7 +804,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// <param name="joint">The type of joint.</param>
|
/// <param name="joint">The type of joint.</param>
|
||||||
/// <param name="group">The group to which the joint belongs.</param>
|
/// <param name="group">The group to which the joint belongs.</param>
|
||||||
/// <param name="index">The index of the joint within its group.</param>
|
/// <param name="index">The index of the joint within its group.</param>
|
||||||
private static void GetJointIndex(JointType joint, out int group, out int index)
|
public static void GetJointIndex(JointType joint, out int group, out int index)
|
||||||
{
|
{
|
||||||
int jointId = (int)joint + 1;
|
int jointId = (int)joint + 1;
|
||||||
group = 0;
|
group = 0;
|
||||||
@@ -1448,19 +1450,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
hand.GetJointRotation((JointType)jointId, ref rot);
|
hand.GetJointRotation((JointType)jointId, ref rot);
|
||||||
return new Pose(pos, rot);
|
return new Pose(pos, rot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the default joint rotation for a specific HandGrabGesture and joint type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handGrabGesture">The HandGrabGesture is a gesture where a hand grabs.</param>
|
|
||||||
/// <param name="jointId">The index of joint.</param>
|
|
||||||
/// <returns>The default rotation of the specified joint in local coordinates.</returns>
|
|
||||||
public Quaternion GetDefaultJointRotationInGesture(HandGrabGesture handGrabGesture, int jointId)
|
|
||||||
{
|
|
||||||
Quaternion rotation = Quaternion.identity;
|
|
||||||
hand.GetDefaultJointRotationInGesture(m_IsLeft, handGrabGesture, (JointType)jointId, ref rotation);
|
|
||||||
return rotation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Grab Framework
|
#region Grab Framework
|
||||||
@@ -1665,35 +1654,46 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public struct GrabOffset
|
public struct GrabOffset
|
||||||
{
|
{
|
||||||
|
[SerializeField]
|
||||||
|
public Vector3 sourcePosition;
|
||||||
|
[SerializeField]
|
||||||
|
public Quaternion sourceRotation;
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
public Vector3 targetPosition;
|
public Vector3 targetPosition;
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
public Quaternion targetRotation;
|
public Quaternion targetRotation;
|
||||||
[SerializeField]
|
|
||||||
public Vector3 position;
|
|
||||||
[SerializeField]
|
|
||||||
public Quaternion rotation;
|
|
||||||
|
|
||||||
public GrabOffset(Vector3 in_TargetPosition, Quaternion in_targetRotation, Vector3 in_Position, Quaternion in_Rotation)
|
public Vector3 posOffset => targetPosition - sourcePosition;
|
||||||
|
public Quaternion rotOffset => Quaternion.Inverse(sourceRotation) * targetRotation;
|
||||||
|
|
||||||
|
public GrabOffset(Vector3 in_SourcePosition, Quaternion in_SourceRotation, Vector3 in_TargetPosition, Quaternion in_targetRotation)
|
||||||
{
|
{
|
||||||
|
sourcePosition = in_SourcePosition;
|
||||||
|
sourceRotation = in_SourceRotation;
|
||||||
targetPosition = in_TargetPosition;
|
targetPosition = in_TargetPosition;
|
||||||
targetRotation = in_targetRotation;
|
targetRotation = in_targetRotation;
|
||||||
position = in_Position;
|
|
||||||
rotation = in_Rotation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GrabOffset Identity => new GrabOffset(Vector3.zero, Quaternion.identity, Vector3.zero, Quaternion.identity);
|
public static GrabOffset Identity => new GrabOffset(Vector3.zero, Quaternion.identity, Vector3.zero, Quaternion.identity);
|
||||||
|
|
||||||
public void Update(Pose OffsetPose)
|
public void UpdateSource(Vector3 pos, Quaternion rot)
|
||||||
{
|
{
|
||||||
position = OffsetPose.position;
|
sourcePosition = pos;
|
||||||
rotation = OffsetPose.rotation;
|
sourceRotation = rot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(Vector3 pos, Quaternion rot)
|
public void UpdateTarget(Vector3 pos, Quaternion rot)
|
||||||
{
|
{
|
||||||
position = pos;
|
targetPosition = pos;
|
||||||
rotation = rot;
|
targetRotation = rot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Vector3 in_SourcePosition, Quaternion in_SourceRotation, Vector3 in_TargetPosition, Quaternion in_targetRotation)
|
||||||
|
{
|
||||||
|
sourcePosition = in_SourcePosition;
|
||||||
|
sourceRotation = in_SourceRotation;
|
||||||
|
targetPosition = in_TargetPosition;
|
||||||
|
targetRotation = in_targetRotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
@@ -1704,16 +1704,17 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
return obj is GrabOffset grabOffset &&
|
return obj is GrabOffset grabOffset &&
|
||||||
position == grabOffset.position &&
|
sourcePosition == grabOffset.sourcePosition &&
|
||||||
rotation == grabOffset.rotation;
|
sourceRotation == grabOffset.sourceRotation &&
|
||||||
|
targetPosition == grabOffset.targetPosition &&
|
||||||
|
targetRotation == grabOffset.targetRotation;
|
||||||
}
|
}
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
return position.GetHashCode() ^ rotation.GetHashCode();
|
return sourcePosition.GetHashCode() ^ sourceRotation.GetHashCode() ^ targetPosition.GetHashCode() ^ targetRotation.GetHashCode();
|
||||||
}
|
}
|
||||||
public static bool operator ==(GrabOffset source, GrabOffset target) => source.Equals(target);
|
public static bool operator ==(GrabOffset source, GrabOffset target) => source.Equals(target);
|
||||||
public static bool operator !=(GrabOffset source, GrabOffset target) => !(source == target);
|
public static bool operator !=(GrabOffset source, GrabOffset target) => !(source == target);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1729,14 +1730,14 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
public GameObject target;
|
public GameObject target;
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
public GrabOffset grabOffSet;
|
public GrabOffset grabOffset;
|
||||||
|
|
||||||
public Indicator(bool in_EnableIndicator, bool in_AutoIndicator, GameObject in_Target)
|
public Indicator(bool in_EnableIndicator, bool in_AutoIndicator, GameObject in_Target)
|
||||||
{
|
{
|
||||||
enableIndicator = in_EnableIndicator;
|
enableIndicator = in_EnableIndicator;
|
||||||
autoIndicator = in_AutoIndicator;
|
autoIndicator = in_AutoIndicator;
|
||||||
target = in_Target;
|
target = in_Target;
|
||||||
grabOffSet = GrabOffset.Identity;
|
grabOffset = GrabOffset.Identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Indicator Identity => new Indicator(true, true, null);
|
public static Indicator Identity => new Indicator(true, true, null);
|
||||||
@@ -1778,13 +1779,11 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// Calculates the grab offset based on the reference transform.
|
/// Calculates the grab offset based on the reference transform.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="refTransform">The reference transform used for calculation.</param>
|
/// <param name="refTransform">The reference transform used for calculation.</param>
|
||||||
public void CalculateGrabOffset(Transform refTransform)
|
public void CalculateGrabOffset(Vector3 grabbablePos, Quaternion grabbableRot)
|
||||||
{
|
{
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
Vector3 pos = target.transform.position - refTransform.position;
|
grabOffset.Update(grabbablePos, grabbableRot, target.transform.position, target.transform.rotation);
|
||||||
Quaternion rot = Quaternion.Inverse(refTransform.rotation) * target.transform.rotation;
|
|
||||||
grabOffSet.Update(pos, rot);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1792,12 +1791,13 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
/// Update the position and rotation of the target GameObject based on the reference transform.
|
/// Update the position and rotation of the target GameObject based on the reference transform.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="refTransform">The reference transform used for updating position and rotation.</param>
|
/// <param name="refTransform">The reference transform used for updating position and rotation.</param>
|
||||||
public void UpdatePositionAndRotation(Transform refTransform)
|
public void UpdatePositionAndRotation(Vector3 grabbablePos, Quaternion grabbableRot)
|
||||||
{
|
{
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
target.transform.position = refTransform.position + refTransform.rotation * grabOffSet.position;
|
Quaternion handRotDiff = grabbableRot * Quaternion.Inverse(grabOffset.sourceRotation);
|
||||||
target.transform.rotation = refTransform.rotation * grabOffSet.rotation;
|
target.transform.position = grabbablePos + handRotDiff * grabOffset.posOffset;
|
||||||
|
target.transform.rotation = grabbableRot * grabOffset.rotOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1807,11 +1807,11 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
enableIndicator == indicator.enableIndicator &&
|
enableIndicator == indicator.enableIndicator &&
|
||||||
autoIndicator == indicator.autoIndicator &&
|
autoIndicator == indicator.autoIndicator &&
|
||||||
target == indicator.target &&
|
target == indicator.target &&
|
||||||
grabOffSet == indicator.grabOffSet;
|
grabOffset == indicator.grabOffset;
|
||||||
}
|
}
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
return enableIndicator.GetHashCode() ^ autoIndicator.GetHashCode() ^ target.GetHashCode() ^ grabOffSet.GetHashCode();
|
return enableIndicator.GetHashCode() ^ autoIndicator.GetHashCode() ^ target.GetHashCode() ^ grabOffset.GetHashCode();
|
||||||
}
|
}
|
||||||
public static bool operator ==(Indicator source, Indicator target) => source.Equals(target);
|
public static bool operator ==(Indicator source, Indicator target) => source.Equals(target);
|
||||||
public static bool operator !=(Indicator source, Indicator target) => !(source == target);
|
public static bool operator !=(Indicator source, Indicator target) => !(source == target);
|
||||||
@@ -1896,9 +1896,18 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
public static bool operator !=(GrabPose source, GrabPose target) => !(source == target);
|
public static bool operator !=(GrabPose source, GrabPose target) => !(source == target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class HandGrabberEvent : UnityEvent<IGrabber> { };
|
||||||
|
[Serializable]
|
||||||
|
public class HandGrabbableEvent : UnityEvent<IGrabbable> { };
|
||||||
|
|
||||||
|
[Obsolete("Please use HandGrabberEvent instead.")]
|
||||||
public delegate void OnBeginGrab(IGrabber grabber);
|
public delegate void OnBeginGrab(IGrabber grabber);
|
||||||
|
[Obsolete("Please use HandGrabberEvent instead.")]
|
||||||
public delegate void OnEndGrab(IGrabber grabber);
|
public delegate void OnEndGrab(IGrabber grabber);
|
||||||
|
[Obsolete("Please use HandGrabbableEvent instead.")]
|
||||||
public delegate void OnBeginGrabbed(IGrabbable grabbable);
|
public delegate void OnBeginGrabbed(IGrabbable grabbable);
|
||||||
|
[Obsolete("Please use HandGrabbableEvent instead.")]
|
||||||
public delegate void OnEndGrabbed(IGrabbable grabbable);
|
public delegate void OnEndGrabbed(IGrabbable grabbable);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1908,9 +1917,16 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
{
|
{
|
||||||
IGrabbable grabbable { get; }
|
IGrabbable grabbable { get; }
|
||||||
bool isGrabbing { get; }
|
bool isGrabbing { get; }
|
||||||
|
HandGrabberEvent onBeginGrab { get; }
|
||||||
|
HandGrabberEvent onEndGrab { get; }
|
||||||
|
|
||||||
|
[Obsolete("Please use onBeginGrab instead.")]
|
||||||
void AddBeginGrabListener(OnBeginGrab handler);
|
void AddBeginGrabListener(OnBeginGrab handler);
|
||||||
|
[Obsolete("Please use onBeginGrab instead.")]
|
||||||
void RemoveBeginGrabListener(OnBeginGrab handler);
|
void RemoveBeginGrabListener(OnBeginGrab handler);
|
||||||
|
[Obsolete("Please use onEndGrab instead.")]
|
||||||
void AddEndGrabListener(OnEndGrab handler);
|
void AddEndGrabListener(OnEndGrab handler);
|
||||||
|
[Obsolete("Please use onEndGrab instead.")]
|
||||||
void RemoveEndGrabListener(OnEndGrab handler);
|
void RemoveEndGrabListener(OnEndGrab handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1931,11 +1947,17 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
IGrabber grabber { get; }
|
IGrabber grabber { get; }
|
||||||
bool isGrabbed { get; }
|
bool isGrabbed { get; }
|
||||||
bool isGrabbable { get; }
|
bool isGrabbable { get; }
|
||||||
bool forceMovable { get; }
|
HandGrabbableEvent onBeginGrabbed { get; }
|
||||||
|
HandGrabbableEvent onEndGrabbed { get; }
|
||||||
void SetGrabber(IGrabber grabber);
|
void SetGrabber(IGrabber grabber);
|
||||||
|
|
||||||
|
[Obsolete("Please use onBeginGrabbed instead.")]
|
||||||
void AddBeginGrabbedListener(OnBeginGrabbed handler);
|
void AddBeginGrabbedListener(OnBeginGrabbed handler);
|
||||||
|
[Obsolete("Please use onBeginGrabbed instead.")]
|
||||||
void RemoveBeginGrabbedListener(OnBeginGrabbed handler);
|
void RemoveBeginGrabbedListener(OnBeginGrabbed handler);
|
||||||
|
[Obsolete("Please use onEndGrabbed instead.")]
|
||||||
void AddEndGrabbedListener(OnEndGrabbed handler);
|
void AddEndGrabbedListener(OnEndGrabbed handler);
|
||||||
|
[Obsolete("Please use onEndGrabbed instead.")]
|
||||||
void RemoveEndGrabbedListener(OnEndGrabbed handler);
|
void RemoveEndGrabbedListener(OnEndGrabbed handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1947,6 +1969,33 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|||||||
FingerRequirement fingerRequirement { get; }
|
FingerRequirement fingerRequirement { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct ConstraintInfo
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
public bool enableConstraint;
|
||||||
|
[SerializeField]
|
||||||
|
public float value;
|
||||||
|
|
||||||
|
public ConstraintInfo(bool in_EnableConstraint, float in_Value)
|
||||||
|
{
|
||||||
|
enableConstraint = in_EnableConstraint;
|
||||||
|
value = in_Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConstraintInfo Identity => new ConstraintInfo(false, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IMovement : MonoBehaviour
|
||||||
|
{
|
||||||
|
public abstract void Initialize(IGrabbable grabbable);
|
||||||
|
public abstract void OnBeginGrabbed(IGrabbable grabbable);
|
||||||
|
public abstract void UpdatePose(Pose updatedPose);
|
||||||
|
public abstract void OnEndGrabbed(IGrabbable grabbable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IOneHandContraintMovement : IMovement { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The class is designed to serve as the Hand Grab API.
|
/// The class is designed to serve as the Hand Grab API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
#region Log
|
||||||
|
|
||||||
|
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}"); }
|
||||||
|
int logFrame = 0;
|
||||||
|
bool printIntervalLog => logFrame == 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private Handedness m_Handedness;
|
||||||
|
public bool isLeft { get { return m_Handedness == Handedness.Left; } }
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private bool m_EnableCollider = false;
|
||||||
|
public bool enableCollider
|
||||||
|
{
|
||||||
|
get { return m_EnableCollider; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_EnableCollider = value;
|
||||||
|
if (m_EnableCollider && m_HandCollider == null)
|
||||||
|
{
|
||||||
|
InitHandCollider();
|
||||||
|
}
|
||||||
|
else if (m_HandCollider != null)
|
||||||
|
{
|
||||||
|
m_HandCollider.enableCollider = m_EnableCollider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HandColliderController m_HandCollider = null;
|
||||||
|
public HandColliderController handCollider => m_HandCollider;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private Transform[] m_HandJoints = new Transform[k_JointCount];
|
||||||
|
|
||||||
|
private const int k_JointCount = (int)JointType.Count;
|
||||||
|
private const int k_RootId = (int)JointType.Wrist;
|
||||||
|
private bool updateRoot = false;
|
||||||
|
private int updatedFrameCount = 0;
|
||||||
|
private bool isGrabbing = false;
|
||||||
|
private bool isConstraint = false;
|
||||||
|
private HandGrabInteractor handGrabber;
|
||||||
|
private Quaternion[] grabJointsRotation = new Quaternion[k_JointCount];
|
||||||
|
|
||||||
|
#region MonoBehaviours
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
bool empty = m_HandJoints.Any(x => x == null);
|
||||||
|
if (empty)
|
||||||
|
{
|
||||||
|
ClearJoints();
|
||||||
|
FindJoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_EnableCollider && m_HandCollider == null)
|
||||||
|
{
|
||||||
|
InitHandCollider();
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshHandPose meshHandPose = transform.gameObject.AddComponent<MeshHandPose>();
|
||||||
|
meshHandPose.SetHandMeshRenderer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (m_HandCollider != null)
|
||||||
|
{
|
||||||
|
Destroy(m_HandCollider);
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshHandPose meshHandPose = transform.GetComponent<MeshHandPose>();
|
||||||
|
if (meshHandPose != null)
|
||||||
|
{
|
||||||
|
Destroy(meshHandPose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
HandData handData = CachedHand.Get(isLeft);
|
||||||
|
EnableHandModel(handData.isTracked);
|
||||||
|
if (!handData.isTracked) { return; }
|
||||||
|
|
||||||
|
//if (m_UseRuntimeModel || (!m_UseRuntimeModel && m_UseScale))
|
||||||
|
//{
|
||||||
|
// Vector3 scale = Vector3.one;
|
||||||
|
// if (GetHandScale(ref scale, isLeft))
|
||||||
|
// {
|
||||||
|
// m_HandJoints[rootId].localScale = scale;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// m_HandJoints[rootId].localScale = Vector3.one;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (Time.frameCount - updatedFrameCount > 5)
|
||||||
|
{
|
||||||
|
updateRoot = false;
|
||||||
|
}
|
||||||
|
if (!updateRoot)
|
||||||
|
{
|
||||||
|
Vector3 rootPosition = Vector3.zero;
|
||||||
|
Quaternion rootRotation = Quaternion.identity;
|
||||||
|
handData.GetJointPosition((JointType)k_RootId, ref rootPosition);
|
||||||
|
handData.GetJointRotation((JointType)k_RootId, ref rootRotation);
|
||||||
|
|
||||||
|
m_HandJoints[k_RootId].position = m_HandJoints[k_RootId].parent.position + rootPosition;
|
||||||
|
m_HandJoints[k_RootId].rotation = m_HandJoints[k_RootId].parent.rotation * rootRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_HandJoints.Length; i++)
|
||||||
|
{
|
||||||
|
if (m_HandJoints[i] == null || i == k_RootId) { continue; }
|
||||||
|
|
||||||
|
Quaternion jointRotation = Quaternion.identity;
|
||||||
|
handData.GetJointRotation((JointType)i, ref jointRotation);
|
||||||
|
m_HandJoints[i].rotation = m_HandJoints[k_RootId].parent.rotation * jointRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGrabbing)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_HandJoints.Length; i++)
|
||||||
|
{
|
||||||
|
if (i == k_RootId) { continue; }
|
||||||
|
|
||||||
|
Quaternion currentRotation = m_HandJoints[i].rotation;
|
||||||
|
Quaternion maxRotation = m_HandJoints[i].parent.rotation * grabJointsRotation[i];
|
||||||
|
if (isConstraint ||
|
||||||
|
handGrabber.IsRequiredJoint((JointType)i) ||
|
||||||
|
OverFlex(currentRotation, maxRotation) >= 0 ||
|
||||||
|
FlexAngle(currentRotation, maxRotation) >= 110)
|
||||||
|
{
|
||||||
|
m_HandJoints[i].rotation = maxRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Interface
|
||||||
|
|
||||||
|
public void OnHandBeginGrab(IGrabber grabber)
|
||||||
|
{
|
||||||
|
if (grabber is HandGrabInteractor handGrabber)
|
||||||
|
{
|
||||||
|
this.handGrabber = handGrabber;
|
||||||
|
|
||||||
|
if (grabber.grabbable is HandGrabInteractable handGrabbable)
|
||||||
|
{
|
||||||
|
if (handGrabbable.bestGrabPose != GrabPose.Identity)
|
||||||
|
{
|
||||||
|
if (handGrabbable.bestGrabPose.recordedGrabRotations.Length == (int)JointType.Count)
|
||||||
|
{
|
||||||
|
grabJointsRotation = handGrabbable.bestGrabPose.recordedGrabRotations;
|
||||||
|
}
|
||||||
|
else if (handGrabbable.bestGrabPose.handGrabGesture != HandGrabGesture.Identity)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < grabJointsRotation.Length; i++)
|
||||||
|
{
|
||||||
|
HandData.GetDefaultJointRotationInGesture(isLeft, handGrabbable.bestGrabPose.handGrabGesture, (JointType)i, ref grabJointsRotation[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isGrabbing = true;
|
||||||
|
isConstraint = handGrabbable.isContraint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_EnableCollider && m_HandCollider != null)
|
||||||
|
{
|
||||||
|
m_HandCollider.OnHandBeginGrab(grabber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHandEndGrab(IGrabber grabber)
|
||||||
|
{
|
||||||
|
isGrabbing = false;
|
||||||
|
this.handGrabber = null;
|
||||||
|
|
||||||
|
if (m_EnableCollider && handCollider != null)
|
||||||
|
{
|
||||||
|
handCollider.OnHandEndGrab(grabber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the position and rotation of the specified joint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="joint">The joint type to get position and rotation from.</param>
|
||||||
|
/// <param name="position">The position of the joint.</param>
|
||||||
|
/// <param name="rotation">The rotation of the joint.</param>
|
||||||
|
/// <param name="local">Whether to get the local position and rotation.</param>
|
||||||
|
/// <returns>True if the joint position and rotation are successfully obtained; otherwise, false.</returns>
|
||||||
|
public bool GetJointPositionAndRotation(JointType joint, out Vector3 position, out Quaternion rotation, bool local = false)
|
||||||
|
{
|
||||||
|
position = Vector3.zero;
|
||||||
|
rotation = Quaternion.identity;
|
||||||
|
int jointId = (int)joint;
|
||||||
|
if (jointId >= 0 && jointId < k_JointCount && m_HandJoints[jointId] != null)
|
||||||
|
{
|
||||||
|
if (!local)
|
||||||
|
{
|
||||||
|
position = m_HandJoints[jointId].position;
|
||||||
|
rotation = m_HandJoints[jointId].rotation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
position = m_HandJoints[jointId].localPosition;
|
||||||
|
rotation = m_HandJoints[jointId].localRotation;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the position and rotation of the specified joint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="joint">The joint type to set position and rotation for.</param>
|
||||||
|
/// <param name="position">The new position of the joint.</param>
|
||||||
|
/// <param name="rotation">The new rotation of the joint.</param>
|
||||||
|
/// <param name="local">Whether to set the local position and rotation.</param>
|
||||||
|
/// <returns>True if the joint position and rotation are successfully set; otherwise, false.</returns>
|
||||||
|
public bool SetJointPositionAndRotation(JointType joint, Vector3 position, Quaternion rotation, bool local = false)
|
||||||
|
{
|
||||||
|
int jointId = (int)joint;
|
||||||
|
if (jointId >= 0 && jointId < k_JointCount && m_HandJoints[jointId] != null)
|
||||||
|
{
|
||||||
|
if (!local)
|
||||||
|
{
|
||||||
|
m_HandJoints[jointId].position = position;
|
||||||
|
m_HandJoints[jointId].rotation = rotation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_HandJoints[jointId].localPosition = position;
|
||||||
|
m_HandJoints[jointId].localRotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (joint == JointType.Wrist)
|
||||||
|
{
|
||||||
|
updatedFrameCount = Time.frameCount;
|
||||||
|
updateRoot = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Name Definition
|
||||||
|
// The order of joint name MUST align with runtime's definition
|
||||||
|
private readonly string[] JointsName = new string[]
|
||||||
|
{
|
||||||
|
"WaveBone_0", // WVR_HandJoint_Palm = 0
|
||||||
|
"WaveBone_1", // WVR_HandJoint_Wrist = 1
|
||||||
|
"WaveBone_2", // WVR_HandJoint_Thumb_Joint0 = 2
|
||||||
|
"WaveBone_3", // WVR_HandJoint_Thumb_Joint1 = 3
|
||||||
|
"WaveBone_4", // WVR_HandJoint_Thumb_Joint2 = 4
|
||||||
|
"WaveBone_5", // WVR_HandJoint_Thumb_Tip = 5
|
||||||
|
"WaveBone_6", // WVR_HandJoint_Index_Joint0 = 6
|
||||||
|
"WaveBone_7", // WVR_HandJoint_Index_Joint1 = 7
|
||||||
|
"WaveBone_8", // WVR_HandJoint_Index_Joint2 = 8
|
||||||
|
"WaveBone_9", // WVR_HandJoint_Index_Joint3 = 9
|
||||||
|
"WaveBone_10", // WVR_HandJoint_Index_Tip = 10
|
||||||
|
"WaveBone_11", // WVR_HandJoint_Middle_Joint0 = 11
|
||||||
|
"WaveBone_12", // WVR_HandJoint_Middle_Joint1 = 12
|
||||||
|
"WaveBone_13", // WVR_HandJoint_Middle_Joint2 = 13
|
||||||
|
"WaveBone_14", // WVR_HandJoint_Middle_Joint3 = 14
|
||||||
|
"WaveBone_15", // WVR_HandJoint_Middle_Tip = 15
|
||||||
|
"WaveBone_16", // WVR_HandJoint_Ring_Joint0 = 16
|
||||||
|
"WaveBone_17", // WVR_HandJoint_Ring_Joint1 = 17
|
||||||
|
"WaveBone_18", // WVR_HandJoint_Ring_Joint2 = 18
|
||||||
|
"WaveBone_19", // WVR_HandJoint_Ring_Joint3 = 19
|
||||||
|
"WaveBone_20", // WVR_HandJoint_Ring_Tip = 20
|
||||||
|
"WaveBone_21", // WVR_HandJoint_Pinky_Joint0 = 21
|
||||||
|
"WaveBone_22", // WVR_HandJoint_Pinky_Joint0 = 22
|
||||||
|
"WaveBone_23", // WVR_HandJoint_Pinky_Joint0 = 23
|
||||||
|
"WaveBone_24", // WVR_HandJoint_Pinky_Joint0 = 24
|
||||||
|
"WaveBone_25" // WVR_HandJoint_Pinky_Tip = 25
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void GetAllChildrenTransforms(Transform parent, ref List<Transform> childrenTransforms)
|
||||||
|
{
|
||||||
|
foreach (Transform child in parent)
|
||||||
|
{
|
||||||
|
childrenTransforms.Add(child);
|
||||||
|
GetAllChildrenTransforms(child, ref childrenTransforms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FindJoints()
|
||||||
|
{
|
||||||
|
List<Transform> totalTransforms = new List<Transform>() { transform };
|
||||||
|
GetAllChildrenTransforms(transform, ref totalTransforms);
|
||||||
|
|
||||||
|
for (int i = 0; i < m_HandJoints.Length; i++)
|
||||||
|
{
|
||||||
|
Transform jointTransform = totalTransforms.FirstOrDefault(x => x.name == JointsName[i]);
|
||||||
|
if (jointTransform != null)
|
||||||
|
{
|
||||||
|
m_HandJoints[i] = jointTransform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearJoints()
|
||||||
|
{
|
||||||
|
Array.Clear(m_HandJoints, 0, m_HandJoints.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitHandCollider()
|
||||||
|
{
|
||||||
|
m_HandCollider = gameObject.AddComponent<HandColliderController>();
|
||||||
|
m_HandCollider.InitJointColliders(m_HandJoints[k_RootId]);
|
||||||
|
m_HandCollider.handMesh = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnableHandModel(bool enable)
|
||||||
|
{
|
||||||
|
if (m_HandJoints[k_RootId].gameObject.activeSelf != enable)
|
||||||
|
{
|
||||||
|
m_HandJoints[k_RootId].gameObject.SetActive(enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e199f8a96e0fdf7498bbb0bf45e5b11a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public abstract class HandPose : MonoBehaviour
|
||||||
|
{
|
||||||
|
#region Log
|
||||||
|
private const string LOG_TAG = "Wave.Essence.Hand.Interaction.HandPose";
|
||||||
|
private static StringBuilder m_sb = null;
|
||||||
|
protected static StringBuilder sb
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_sb == null) { m_sb = new StringBuilder(); }
|
||||||
|
return m_sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void DEBUG(string msg) { Debug.Log($"{LOG_TAG}.{m_PoseType}, {msg}"); }
|
||||||
|
protected void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}.{m_PoseType}, {msg}"); }
|
||||||
|
protected void ERROR(string msg) { Debug.LogError($"{LOG_TAG}.{m_PoseType}, {msg}"); }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected HandPoseType m_PoseType = HandPoseType.UNKNOWN;
|
||||||
|
protected bool m_Initialized = false;
|
||||||
|
protected bool m_IsTracked = false;
|
||||||
|
protected const int poseCount = (int)JointType.Count;
|
||||||
|
protected Vector3[] m_Position = Enumerable.Repeat(Vector3.zero, poseCount).ToArray();
|
||||||
|
protected Vector3[] m_LocalPosition = Enumerable.Repeat(Vector3.zero, poseCount).ToArray();
|
||||||
|
protected Quaternion[] m_Rotation = Enumerable.Repeat(Quaternion.identity, poseCount).ToArray();
|
||||||
|
protected Quaternion[] m_LocalRotation = Enumerable.Repeat(Quaternion.identity, poseCount).ToArray();
|
||||||
|
|
||||||
|
protected virtual void OnEnable()
|
||||||
|
{
|
||||||
|
HandPoseProvider.RegisterHandPose(m_PoseType, this);
|
||||||
|
}
|
||||||
|
protected virtual void OnDisable()
|
||||||
|
{
|
||||||
|
HandPoseProvider.UnregisterHandPose(m_PoseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetType(HandPoseType poseType)
|
||||||
|
{
|
||||||
|
m_PoseType = poseType;
|
||||||
|
m_Initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsTracked()
|
||||||
|
{
|
||||||
|
return m_IsTracked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool GetRotation(JointType joint, out Quaternion value, bool local = false)
|
||||||
|
{
|
||||||
|
value = Quaternion.identity;
|
||||||
|
if (joint != JointType.Count)
|
||||||
|
{
|
||||||
|
value = local ? m_LocalRotation[(int)joint] : m_Rotation[(int)joint];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool GetPosition(JointType joint, out Vector3 value, bool local = false)
|
||||||
|
{
|
||||||
|
value = Vector3.zero;
|
||||||
|
if (joint != JointType.Count)
|
||||||
|
{
|
||||||
|
value = local ? m_LocalPosition[(int)joint] : m_Position[(int)joint];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1ced5448f0990b040b82475106e1b1d3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public enum HandPoseType : UInt32
|
||||||
|
{
|
||||||
|
UNKNOWN = 0x7FFFFFFF,
|
||||||
|
|
||||||
|
HAND_LEFT = 100,
|
||||||
|
HAND_RIGHT = 101,
|
||||||
|
|
||||||
|
MESH_LEFT = 200,
|
||||||
|
MESH_RIGHT = 201,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HandPoseProvider
|
||||||
|
{
|
||||||
|
private static Dictionary<HandPoseType, HandPose> m_HandPoseMap = new Dictionary<HandPoseType, HandPose>();
|
||||||
|
public static Dictionary<HandPoseType, HandPose> HandPoseMap
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_HandPoseMap == null) { m_HandPoseMap = new Dictionary<HandPoseType, HandPose>(); }
|
||||||
|
return m_HandPoseMap;
|
||||||
|
}
|
||||||
|
private set { m_HandPoseMap = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool RegisterHandPose(in HandPoseType poseType, in HandPose handPose)
|
||||||
|
{
|
||||||
|
if (!HandPoseMap.ContainsKey(poseType))
|
||||||
|
{
|
||||||
|
HandPoseMap.Add(poseType, handPose);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool UnregisterHandPose(in HandPoseType poseType)
|
||||||
|
{
|
||||||
|
if (HandPoseMap.ContainsKey(poseType))
|
||||||
|
{
|
||||||
|
HandPoseMap.Remove(poseType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandPose GetHandPose(in HandPoseType poseType)
|
||||||
|
{
|
||||||
|
if (HandPoseMap.ContainsKey(poseType))
|
||||||
|
{
|
||||||
|
return HandPoseMap[poseType];
|
||||||
|
}
|
||||||
|
if (poseType == HandPoseType.HAND_LEFT || poseType == HandPoseType.MESH_LEFT)
|
||||||
|
{
|
||||||
|
return GetDefaultHandPose("LeftHandPose", HandPoseType.HAND_LEFT);
|
||||||
|
}
|
||||||
|
else if (poseType == HandPoseType.HAND_RIGHT || poseType == HandPoseType.MESH_RIGHT)
|
||||||
|
{
|
||||||
|
return GetDefaultHandPose("RightHandPose", HandPoseType.HAND_RIGHT);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Name(this HandPoseType poseType)
|
||||||
|
{
|
||||||
|
string name = "";
|
||||||
|
switch (poseType)
|
||||||
|
{
|
||||||
|
case HandPoseType.HAND_LEFT: name = "HAND_LEFT"; break;
|
||||||
|
case HandPoseType.HAND_RIGHT: name = "HAND_LEFT"; break;
|
||||||
|
case HandPoseType.MESH_LEFT: name = "MESH_LEFT"; break;
|
||||||
|
case HandPoseType.MESH_RIGHT: name = "MESH_RIGHT"; break;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HandPose GetDefaultHandPose(string poseName, HandPoseType poseType)
|
||||||
|
{
|
||||||
|
if (!HandPoseMap.ContainsKey(poseType))
|
||||||
|
{
|
||||||
|
GameObject handPoseObject = new GameObject(poseName);
|
||||||
|
RealHandPose realHandPose = handPoseObject.AddComponent<RealHandPose>();
|
||||||
|
realHandPose.SetType(poseType);
|
||||||
|
RegisterHandPose(poseType, realHandPose);
|
||||||
|
return realHandPose;
|
||||||
|
}
|
||||||
|
return HandPoseMap[poseType];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 059d07a57fa09e543bf2f2b6d5788bb7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public class MeshHandPose : HandPose
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private HandMeshManager m_HandMesh;
|
||||||
|
|
||||||
|
private bool keepUpdate = false;
|
||||||
|
|
||||||
|
protected override void OnEnable()
|
||||||
|
{
|
||||||
|
StartCoroutine(WaitInit());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDisable()
|
||||||
|
{
|
||||||
|
base.OnDisable();
|
||||||
|
if (keepUpdate)
|
||||||
|
{
|
||||||
|
keepUpdate = false;
|
||||||
|
StopCoroutine(UpdatePose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHandMeshRenderer(HandMeshManager handMeshRenderer)
|
||||||
|
{
|
||||||
|
m_HandMesh = handMeshRenderer;
|
||||||
|
SetType(handMeshRenderer.isLeft ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetJointPose(JointType joint, Pose jointPose, bool local = false)
|
||||||
|
{
|
||||||
|
if (m_HandMesh != null)
|
||||||
|
{
|
||||||
|
return m_HandMesh.SetJointPositionAndRotation(joint, jointPose.position, jointPose.rotation, local);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator WaitInit()
|
||||||
|
{
|
||||||
|
yield return new WaitUntil(() => m_Initialized);
|
||||||
|
base.OnEnable();
|
||||||
|
if (!keepUpdate)
|
||||||
|
{
|
||||||
|
keepUpdate = true;
|
||||||
|
StartCoroutine(UpdatePose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator UpdatePose()
|
||||||
|
{
|
||||||
|
while (keepUpdate)
|
||||||
|
{
|
||||||
|
yield return new WaitForFixedUpdate();
|
||||||
|
|
||||||
|
HandPose handPose = HandPoseProvider.GetHandPose(m_HandMesh.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
|
||||||
|
m_IsTracked = handPose.IsTracked();
|
||||||
|
|
||||||
|
for (int i = 0; i < poseCount; i++)
|
||||||
|
{
|
||||||
|
if (m_HandMesh.GetJointPositionAndRotation((JointType)i, out Vector3 position, out Quaternion rotation) &&
|
||||||
|
m_HandMesh.GetJointPositionAndRotation((JointType)i, out Vector3 localPosition, out Quaternion localRotation, local: true))
|
||||||
|
{
|
||||||
|
m_Position[i] = position;
|
||||||
|
m_Rotation[i] = rotation;
|
||||||
|
m_LocalPosition[i] = localPosition;
|
||||||
|
m_LocalRotation[i] = localRotation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Position[i] = Vector3.zero;
|
||||||
|
m_Rotation[i] = Quaternion.identity;
|
||||||
|
m_LocalPosition[i] = Vector3.zero;
|
||||||
|
m_LocalRotation[i] = Quaternion.identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5e78e2d8c82d5584faaf4b66d40d1057
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public class RealHandPose : HandPose
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private Handedness m_Handedness;
|
||||||
|
private bool isLeft => m_Handedness == Handedness.Left;
|
||||||
|
private bool keepUpdate = false;
|
||||||
|
|
||||||
|
protected override void OnEnable()
|
||||||
|
{
|
||||||
|
StartCoroutine(WaitInit());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDisable()
|
||||||
|
{
|
||||||
|
base.OnDisable();
|
||||||
|
if (keepUpdate)
|
||||||
|
{
|
||||||
|
keepUpdate = false;
|
||||||
|
StopCoroutine(UpdatePose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetType(HandPoseType poseType)
|
||||||
|
{
|
||||||
|
if (poseType == HandPoseType.HAND_LEFT)
|
||||||
|
{
|
||||||
|
m_Handedness = Handedness.Left;
|
||||||
|
}
|
||||||
|
else if (poseType == HandPoseType.HAND_RIGHT)
|
||||||
|
{
|
||||||
|
m_Handedness = Handedness.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.SetType(poseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator WaitInit()
|
||||||
|
{
|
||||||
|
yield return new WaitUntil(() => m_Initialized);
|
||||||
|
base.OnEnable();
|
||||||
|
if (!keepUpdate)
|
||||||
|
{
|
||||||
|
keepUpdate = true;
|
||||||
|
StartCoroutine(UpdatePose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator UpdatePose()
|
||||||
|
{
|
||||||
|
Vector3 position = Vector3.zero;
|
||||||
|
Quaternion rotation = Quaternion.identity;
|
||||||
|
while (keepUpdate)
|
||||||
|
{
|
||||||
|
yield return new WaitForEndOfFrame();
|
||||||
|
|
||||||
|
HandData handData = CachedHand.Get(isLeft);
|
||||||
|
m_IsTracked = handData.isTracked;
|
||||||
|
if (!m_IsTracked) { continue; }
|
||||||
|
|
||||||
|
for (int i = 0; i < poseCount; i++)
|
||||||
|
{
|
||||||
|
if (handData.GetJointPosition((JointType)i, ref position) && handData.GetJointRotation((JointType)i, ref rotation))
|
||||||
|
{
|
||||||
|
m_Position[i] = position;
|
||||||
|
m_Rotation[i] = rotation;
|
||||||
|
m_LocalPosition[i] = position;
|
||||||
|
m_LocalRotation[i] = rotation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Position[i] = Vector3.zero;
|
||||||
|
m_Rotation[i] = Quaternion.identity;
|
||||||
|
m_LocalPosition[i] = Vector3.zero;
|
||||||
|
m_LocalRotation[i] = Quaternion.identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 52790aba0e3d55f4fb27aded6c698d8b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
// "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 JointCollider : MonoBehaviour
|
||||||
|
{
|
||||||
|
public enum CollisionState
|
||||||
|
{
|
||||||
|
Enter = 0,
|
||||||
|
Stay = 1,
|
||||||
|
Exit = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
private CapsuleCollider m_Collider = null;
|
||||||
|
public Collider Collider => m_Collider;
|
||||||
|
|
||||||
|
public delegate void OnJointCollision(Collision collision, CollisionState state);
|
||||||
|
private OnJointCollision onJointCollision;
|
||||||
|
|
||||||
|
private const float k_ColliderRadius = 1E-6f;
|
||||||
|
private const float k_ColliderHeight = 1E-6f;
|
||||||
|
private JointType jointType = JointType.Count;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
InitCollider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the joint id and adjust collider size..
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">JointType of joint.</param>
|
||||||
|
public void SetJointId(int id)
|
||||||
|
{
|
||||||
|
InitCollider();
|
||||||
|
|
||||||
|
jointType = (JointType)id;
|
||||||
|
switch (jointType)
|
||||||
|
{
|
||||||
|
case JointType.Palm:
|
||||||
|
m_Collider.center = new Vector3(0f, -0.006f, -0.0025f);
|
||||||
|
m_Collider.radius = 0.016f;
|
||||||
|
m_Collider.height = 0.075f;
|
||||||
|
m_Collider.direction = 0;
|
||||||
|
break;
|
||||||
|
case JointType.Wrist:
|
||||||
|
m_Collider.center = new Vector3(0f, -0.003f, 0f);
|
||||||
|
m_Collider.radius = 0.02f;
|
||||||
|
m_Collider.height = 0.055f;
|
||||||
|
m_Collider.direction = 0;
|
||||||
|
break;
|
||||||
|
case JointType.Thumb_Joint0:
|
||||||
|
m_Collider.center = new Vector3(0f, -0.005f, 0f);
|
||||||
|
m_Collider.radius = 0.02f;
|
||||||
|
m_Collider.height = 0.05f;
|
||||||
|
break;
|
||||||
|
case JointType.Thumb_Joint1:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.012f;
|
||||||
|
m_Collider.height = 0.040f;
|
||||||
|
break;
|
||||||
|
case JointType.Thumb_Joint2:
|
||||||
|
m_Collider.center = new Vector3(0f, -0.003f, 0f);
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Thumb_Tip:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, -0.005f);
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.025f;
|
||||||
|
break;
|
||||||
|
case JointType.Index_Joint0:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.02f);
|
||||||
|
m_Collider.radius = 0.012f;
|
||||||
|
m_Collider.height = 0.1f;
|
||||||
|
break;
|
||||||
|
case JointType.Index_Joint1:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.01f);
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.04f;
|
||||||
|
break;
|
||||||
|
case JointType.Index_Joint2:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Index_Joint3:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Index_Tip:
|
||||||
|
m_Collider.center = new Vector3(0f, -0.001f, -0.0025f);
|
||||||
|
m_Collider.radius = 0.008f;
|
||||||
|
m_Collider.height = 0.015f;
|
||||||
|
break;
|
||||||
|
case JointType.Middle_Joint0:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.02f);
|
||||||
|
m_Collider.radius = 0.012f;
|
||||||
|
m_Collider.height = 0.1f;
|
||||||
|
break;
|
||||||
|
case JointType.Middle_Joint1:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.01f);
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.04f;
|
||||||
|
break;
|
||||||
|
case JointType.Middle_Joint2:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Middle_Joint3:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Middle_Tip:
|
||||||
|
m_Collider.center = new Vector3(0f, -0.002f, -0.0025f);
|
||||||
|
m_Collider.radius = 0.008f;
|
||||||
|
m_Collider.height = 0.015f;
|
||||||
|
break;
|
||||||
|
case JointType.Ring_Joint0:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.02f);
|
||||||
|
m_Collider.radius = 0.012f;
|
||||||
|
m_Collider.height = 0.1f;
|
||||||
|
break;
|
||||||
|
case JointType.Ring_Joint1:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.01f);
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.04f;
|
||||||
|
break;
|
||||||
|
case JointType.Ring_Joint2:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Ring_Joint3:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Ring_Tip:
|
||||||
|
m_Collider.center = new Vector3(0f, -0.002f, -0.0025f);
|
||||||
|
m_Collider.radius = 0.008f;
|
||||||
|
m_Collider.height = 0.015f;
|
||||||
|
break;
|
||||||
|
case JointType.Pinky_Joint0:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.02f);
|
||||||
|
m_Collider.radius = 0.012f;
|
||||||
|
m_Collider.height = 0.1f;
|
||||||
|
break;
|
||||||
|
case JointType.Pinky_Joint1:
|
||||||
|
m_Collider.center = new Vector3(0f, 0f, 0.01f);
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.04f;
|
||||||
|
break;
|
||||||
|
case JointType.Pinky_Joint2:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Pinky_Joint3:
|
||||||
|
m_Collider.center = Vector3.zero;
|
||||||
|
m_Collider.radius = 0.01f;
|
||||||
|
m_Collider.height = 0.01f;
|
||||||
|
break;
|
||||||
|
case JointType.Pinky_Tip:
|
||||||
|
m_Collider.center = new Vector3(0f, -0.002f, -0.0025f);
|
||||||
|
m_Collider.radius = 0.006f;
|
||||||
|
m_Collider.height = 0.015f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rigidbody InitRigidbody(float mass)
|
||||||
|
{
|
||||||
|
Rigidbody rigidbody = gameObject.AddComponent<Rigidbody>();
|
||||||
|
rigidbody.mass = mass;
|
||||||
|
rigidbody.useGravity = false;
|
||||||
|
rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
|
||||||
|
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||||
|
rigidbody.solverIterations = 100;
|
||||||
|
rigidbody.solverVelocityIterations = 15;
|
||||||
|
return rigidbody;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitCollider()
|
||||||
|
{
|
||||||
|
if (m_Collider == null)
|
||||||
|
{
|
||||||
|
m_Collider = gameObject.AddComponent<CapsuleCollider>();
|
||||||
|
m_Collider.radius = k_ColliderRadius;
|
||||||
|
m_Collider.height = k_ColliderHeight;
|
||||||
|
m_Collider.direction = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollisionEnter(Collision collision)
|
||||||
|
{
|
||||||
|
if (!IsJointCollider(collision.collider))
|
||||||
|
{
|
||||||
|
onJointCollision?.Invoke(collision, CollisionState.Enter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollisionStay(Collision collision)
|
||||||
|
{
|
||||||
|
if (!IsJointCollider(collision.collider))
|
||||||
|
{
|
||||||
|
onJointCollision?.Invoke(collision, CollisionState.Stay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollisionExit(Collision collision)
|
||||||
|
{
|
||||||
|
if (!IsJointCollider(collision.collider))
|
||||||
|
{
|
||||||
|
onJointCollision?.Invoke(collision, CollisionState.Exit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsJointCollider(Collider collider)
|
||||||
|
{
|
||||||
|
JointCollider jointCollider = collider.GetComponent<JointCollider>();
|
||||||
|
return jointCollider != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddJointCollisionListener(OnJointCollision handler)
|
||||||
|
{
|
||||||
|
onJointCollision += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveJointCollisionListener(OnJointCollision handler)
|
||||||
|
{
|
||||||
|
onJointCollision -= handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ca9a78f9ae24b3f4a92881fa9a680081
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||||
|
{
|
||||||
|
public class PhysicsInteractable : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private float forceMultiplier = 1.0f;
|
||||||
|
|
||||||
|
private readonly int MIN_POSE_SAMPLES = 2;
|
||||||
|
private readonly int MAX_POSE_SAMPLES = 10;
|
||||||
|
private readonly float MIN_VELOCITY = 0.5f;
|
||||||
|
|
||||||
|
private Rigidbody interactableRigidbody;
|
||||||
|
private List<Pose> movementPoses = new List<Pose>();
|
||||||
|
private List<float> timestamps = new List<float>();
|
||||||
|
private bool isBegin = false;
|
||||||
|
private bool isEnd = false;
|
||||||
|
private object lockVel = new object();
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (interactableRigidbody == null) { return; }
|
||||||
|
|
||||||
|
if (isBegin)
|
||||||
|
{
|
||||||
|
RecordMovement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FixedUpdate()
|
||||||
|
{
|
||||||
|
if (interactableRigidbody == null) { return; }
|
||||||
|
|
||||||
|
if (isEnd)
|
||||||
|
{
|
||||||
|
interactableRigidbody.velocity = Vector3.zero;
|
||||||
|
interactableRigidbody.angularVelocity = Vector3.zero;
|
||||||
|
|
||||||
|
Vector3 velocity = CalculateVelocity();
|
||||||
|
if (velocity.magnitude > MIN_VELOCITY)
|
||||||
|
{
|
||||||
|
interactableRigidbody.AddForce(velocity * forceMultiplier, ForceMode.Impulse);
|
||||||
|
}
|
||||||
|
interactableRigidbody = null;
|
||||||
|
|
||||||
|
movementPoses.Clear();
|
||||||
|
timestamps.Clear();
|
||||||
|
isEnd = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecordMovement()
|
||||||
|
{
|
||||||
|
float time = Time.time;
|
||||||
|
if (movementPoses.Count == 0 ||
|
||||||
|
timestamps[movementPoses.Count - 1] != time)
|
||||||
|
{
|
||||||
|
movementPoses.Add(new Pose(interactableRigidbody.position, interactableRigidbody.rotation));
|
||||||
|
timestamps.Add(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movementPoses.Count > MAX_POSE_SAMPLES)
|
||||||
|
{
|
||||||
|
movementPoses.RemoveAt(0);
|
||||||
|
timestamps.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 CalculateVelocity()
|
||||||
|
{
|
||||||
|
if (movementPoses.Count >= MIN_POSE_SAMPLES)
|
||||||
|
{
|
||||||
|
List<Vector3> velocities = new List<Vector3>();
|
||||||
|
for (int i = 0; i < movementPoses.Count - 1; i++)
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j < movementPoses.Count; j++)
|
||||||
|
{
|
||||||
|
velocities.Add(GetVelocity(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vector3 finalVelocity = FindBestVelocity(velocities);
|
||||||
|
return finalVelocity;
|
||||||
|
}
|
||||||
|
return Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 GetVelocity(int idx1, int idx2)
|
||||||
|
{
|
||||||
|
if (idx1 < 0 || idx1 >= movementPoses.Count
|
||||||
|
|| idx2 < 0 || idx2 >= movementPoses.Count
|
||||||
|
|| movementPoses.Count < MIN_POSE_SAMPLES)
|
||||||
|
{
|
||||||
|
return Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx2 < idx1)
|
||||||
|
{
|
||||||
|
(idx1, idx2) = (idx2, idx1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 currentPos = movementPoses[idx2].position;
|
||||||
|
Vector3 previousPos = movementPoses[idx1].position;
|
||||||
|
float currentTime = timestamps[idx2];
|
||||||
|
float previousTime = timestamps[idx1];
|
||||||
|
float timeDelta = currentTime - previousTime;
|
||||||
|
if (currentPos == null || previousPos == null || timeDelta == 0)
|
||||||
|
{
|
||||||
|
return Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 velocity = (currentPos - previousPos) / timeDelta;
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 FindBestVelocity(List<Vector3> velocities)
|
||||||
|
{
|
||||||
|
Vector3 bestVelocity = Vector3.zero;
|
||||||
|
float bestScore = float.PositiveInfinity;
|
||||||
|
|
||||||
|
Parallel.For(0, velocities.Count, i =>
|
||||||
|
{
|
||||||
|
float score = 0f;
|
||||||
|
for (int j = 0; j < velocities.Count; j++)
|
||||||
|
{
|
||||||
|
if (i != j)
|
||||||
|
{
|
||||||
|
score += (velocities[i] - velocities[j]).magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (lockVel)
|
||||||
|
{
|
||||||
|
if (score < bestScore)
|
||||||
|
{
|
||||||
|
bestVelocity = velocities[i];
|
||||||
|
bestScore = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return bestVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeginInteractabled(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
if (grabbable is HandGrabInteractable handGrabbable)
|
||||||
|
{
|
||||||
|
interactableRigidbody = handGrabbable.rigidbody;
|
||||||
|
}
|
||||||
|
isBegin = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnEndInteractabled(IGrabbable grabbable)
|
||||||
|
{
|
||||||
|
isBegin = false;
|
||||||
|
isEnd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 23de72f64d9c93540b53c64f183e4efa
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
// "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
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
// "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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
// "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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,486 +0,0 @@
|
|||||||
// "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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -127,7 +127,7 @@ namespace VIVE.OpenXR.Samples.Anchor
|
|||||||
|
|
||||||
if (AnchorManager.GetTrackingSpacePose(anchor, out Pose pose))
|
if (AnchorManager.GetTrackingSpacePose(anchor, out Pose pose))
|
||||||
{
|
{
|
||||||
// Convert tracking space pose to rig space pose
|
// Convert tracking space pose to world space pose
|
||||||
obj.position = rig.TransformPoint(pose.position);
|
obj.position = rig.TransformPoint(pose.position);
|
||||||
obj.rotation = rig.rotation * pose.rotation;
|
obj.rotation = rig.rotation * pose.rotation;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6a008b18e6da66442857fdd566150c9c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: HandMatLeft
|
||||||
|
m_Shader: {fileID: 4800000, guid: e7d763db3f0479b49a6e358298f1717f, type: 3}
|
||||||
|
m_ShaderKeywords:
|
||||||
|
m_LightmapFlags: 4
|
||||||
|
m_EnableInstancingVariants: 0
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: -1
|
||||||
|
stringTagMap: {}
|
||||||
|
disabledShaderPasses: []
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _AlphaText:
|
||||||
|
m_Texture: {fileID: 2800000, guid: 7a3b01026ed0d664eb88c1a71f03465a, type: 3}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _texcoord:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _texcoord3:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Floats:
|
||||||
|
- _Gradient_Blur: 0.2854134
|
||||||
|
- _Gradient_level: 0.5225084
|
||||||
|
- _Opacity: 0.45
|
||||||
|
- _OutlineThickness: 0.001
|
||||||
|
- __dirty: 1
|
||||||
|
- _line_opacity: 0.5
|
||||||
|
m_Colors:
|
||||||
|
- _ConColorA: {r: 0.1058824, g: 0.6901961, b: 0.9019608, a: 0}
|
||||||
|
- _ConColorB: {r: 1, g: 1, b: 1, a: 0}
|
||||||
|
- _GraColorA: {r: 0.1058824, g: 0.6901961, b: 0.9019608, a: 0}
|
||||||
|
- _GraColorB: {r: 1, g: 1, b: 1, a: 0}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2687505453755f7438ac99850b96b1a4
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: HandMatRight
|
||||||
|
m_Shader: {fileID: 4800000, guid: e7d763db3f0479b49a6e358298f1717f, type: 3}
|
||||||
|
m_ShaderKeywords:
|
||||||
|
m_LightmapFlags: 0
|
||||||
|
m_EnableInstancingVariants: 0
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: -1
|
||||||
|
stringTagMap: {}
|
||||||
|
disabledShaderPasses: []
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _AlphaText:
|
||||||
|
m_Texture: {fileID: 2800000, guid: 7a3b01026ed0d664eb88c1a71f03465a, type: 3}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _texcoord:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _texcoord3:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Floats:
|
||||||
|
- _Gradient_Blur: 0.2854134
|
||||||
|
- _Gradient_level: 0.5225084
|
||||||
|
- _Opacity: 0.45
|
||||||
|
- _OutlineThickness: 0.001
|
||||||
|
- __dirty: 1
|
||||||
|
- _line_opacity: 0.5
|
||||||
|
m_Colors:
|
||||||
|
- _ConColorA: {r: 0.1058824, g: 0.6901961, b: 0.9019608, a: 0}
|
||||||
|
- _ConColorB: {r: 1, g: 1, b: 1, a: 0}
|
||||||
|
- _GraColorA: {r: 0.1058824, g: 0.6901961, b: 0.9019608, a: 0}
|
||||||
|
- _GraColorB: {r: 1, g: 1, b: 1, a: 0}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 68f46279552dd914784cc5edae6479b8
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b67e19957d781e74ea93eb33ee345f49
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// Made with Amplify Shader Editor
|
||||||
|
Shader "Wave/Essence/Hand/Model"
|
||||||
|
{
|
||||||
|
Properties
|
||||||
|
{
|
||||||
|
[HDR]_GraColorA("GradianColorA", Color) = (0.1058824,0.6901961,0.9019608,0)
|
||||||
|
[HDR]_GraColorB("GradianColorB", Color) = (1,1,1,0)
|
||||||
|
[HDR]_ConGraColorA("ContourColorA", Color) = (0.1058824,0.6901961,0.9019608,0)
|
||||||
|
[HDR]_ConGraColorB("ContourColorB", Color) = (1,1,1,0)
|
||||||
|
_Gradient_Blur("Gradient_Blur", Range(0 , 1)) = 0.2854134
|
||||||
|
_Gradient_level("Gradient_level", Range(0 , 1)) = 0.5225084
|
||||||
|
_Opacity("Opacity", Range(0 , 1)) = 0.45
|
||||||
|
_line_opacity("line_opacity", Range(0 , 1)) = 0.5
|
||||||
|
_OutlineThickness("OutlineThickness", Range(0 , 0.002)) = 0.001
|
||||||
|
_AlphaText("AlphaText", 2D) = "white" {}
|
||||||
|
[HideInInspector] _texcoord3("", 2D) = "white" {}
|
||||||
|
[HideInInspector] _texcoord("", 2D) = "white" {}
|
||||||
|
[HideInInspector] __dirty("", Int) = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
SubShader
|
||||||
|
{
|
||||||
|
Pass
|
||||||
|
{
|
||||||
|
ColorMask 0
|
||||||
|
ZWrite On
|
||||||
|
}
|
||||||
|
|
||||||
|
Tags{ "RenderType" = "Transparent" "Queue" = "Transparent+0"}
|
||||||
|
Cull Front
|
||||||
|
CGPROGRAM
|
||||||
|
#pragma target 3.0
|
||||||
|
#pragma surface outlineSurf Outline nofog alpha:fade keepalpha noshadow noambient novertexlights nolightmap nodynlightmap nodirlightmap nometa noforwardadd vertex:outlineVertexDataFunc
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct Input
|
||||||
|
{
|
||||||
|
float2 uv3_texcoord3;
|
||||||
|
float2 uv_texcoord;
|
||||||
|
};
|
||||||
|
uniform float _OutlineThickness;
|
||||||
|
uniform float4 _ConGraColorA;
|
||||||
|
uniform float4 _ConGraColorB;
|
||||||
|
uniform float _Gradient_level;
|
||||||
|
uniform float _Gradient_Blur;
|
||||||
|
uniform float _line_opacity;
|
||||||
|
uniform sampler2D _AlphaText;
|
||||||
|
|
||||||
|
void outlineVertexDataFunc(inout appdata_full v, out Input o)
|
||||||
|
{
|
||||||
|
UNITY_INITIALIZE_OUTPUT(Input, o);
|
||||||
|
float outlineVar = _OutlineThickness;
|
||||||
|
v.vertex.xyz += (v.normal * outlineVar);
|
||||||
|
}
|
||||||
|
inline half4 LightingOutline(SurfaceOutput s, half3 lightDir, half atten) { return half4 (0,0,0, s.Alpha); }
|
||||||
|
void outlineSurf(Input i, inout SurfaceOutput o)
|
||||||
|
{
|
||||||
|
float clampResult114 = clamp((_Gradient_level + ((_Gradient_level - i.uv3_texcoord3.y) / _Gradient_Blur)) , 0.0 , 1.0);
|
||||||
|
float4 lerpResult100 = lerp(_ConGraColorA, _ConGraColorB, clampResult114);
|
||||||
|
float4 tex2DNode92 = tex2D(_AlphaText, i.uv_texcoord);
|
||||||
|
o.Emission = lerpResult100.rgb;
|
||||||
|
o.Alpha = (_line_opacity * tex2DNode92).r;
|
||||||
|
}
|
||||||
|
ENDCG
|
||||||
|
|
||||||
|
|
||||||
|
Tags{ "RenderType" = "Transparent" "Queue" = "Transparent+0" "IgnoreProjector" = "True" "IsEmissive" = "true" }
|
||||||
|
Cull Back
|
||||||
|
Blend SrcAlpha OneMinusSrcAlpha
|
||||||
|
|
||||||
|
CGPROGRAM
|
||||||
|
#pragma target 3.0
|
||||||
|
#pragma surface surf Unlit keepalpha noshadow vertex:vertexDataFunc
|
||||||
|
struct Input
|
||||||
|
{
|
||||||
|
float2 uv3_texcoord3;
|
||||||
|
float2 uv_texcoord;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform float4 _GraColorA;
|
||||||
|
uniform float4 _GraColorB;
|
||||||
|
uniform float _Gradient_level;
|
||||||
|
uniform float _Gradient_Blur;
|
||||||
|
uniform float _Opacity;
|
||||||
|
uniform sampler2D _AlphaText;
|
||||||
|
|
||||||
|
void vertexDataFunc(inout appdata_full v, out Input o)
|
||||||
|
{
|
||||||
|
UNITY_INITIALIZE_OUTPUT(Input, o);
|
||||||
|
v.vertex.xyz += 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline half4 LightingUnlit(SurfaceOutput s, half3 lightDir, half atten)
|
||||||
|
{
|
||||||
|
return half4 (0, 0, 0, s.Alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
void surf(Input i , inout SurfaceOutput o)
|
||||||
|
{
|
||||||
|
float clampResult114 = clamp((_Gradient_level + ((_Gradient_level - i.uv3_texcoord3.y) / _Gradient_Blur)) , 0.0 , 1.0);
|
||||||
|
float4 lerpResult100 = lerp(_GraColorA , _GraColorB , clampResult114);
|
||||||
|
o.Emission = lerpResult100.rgb;
|
||||||
|
float4 color104 = IsGammaSpace() ? float4(0,0,0,0) : float4(0,0,0,0);
|
||||||
|
float4 color102 = IsGammaSpace() ? float4(1,1,1,0) : float4(1,1,1,0);
|
||||||
|
float smoothstepResult103 = smoothstep(-0.05 , 1.0 , i.uv3_texcoord3.y);
|
||||||
|
float4 lerpResult105 = lerp(color104 , color102 , smoothstepResult103);
|
||||||
|
float4 tex2DNode92 = tex2D(_AlphaText, i.uv_texcoord);
|
||||||
|
o.Alpha = (lerpResult105 * _Opacity * tex2DNode92).r;
|
||||||
|
}
|
||||||
|
|
||||||
|
ENDCG
|
||||||
|
}
|
||||||
|
CustomEditor "ASEMaterialInspector"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e7d763db3f0479b49a6e358298f1717f
|
||||||
|
ShaderImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
defaultTextures: []
|
||||||
|
nonModifiableTextures: []
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0f5b18323086f074aaa5e48c525580fb
|
||||||
|
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