version 2.4.0

This commit is contained in:
Sean Lu(呂祥榮)
2024-07-03 14:58:53 +08:00
parent 7f2a459592
commit 3dd72f5f56
162 changed files with 23632 additions and 27154 deletions

View File

@@ -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();

View File

@@ -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)

View File

@@ -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>

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 layers 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;

View File

@@ -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; }
@@ -306,9 +443,18 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
#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,7 +680,7 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
ERROR("HTCPassthrough_GetFuncAddrs"); ERROR("HTCPassthrough_GetFuncAddrs");
return false; return false;
} }
#endif
return true; return true;
} }
#endregion #endregion
@@ -457,6 +689,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
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)
@@ -626,7 +888,9 @@ namespace VIVE.OpenXR.CompositionLayer.Passthrough
#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;

View File

@@ -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");

View File

@@ -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;

View File

@@ -194,37 +194,32 @@ 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);
if (result != XrResult.XR_SUCCESS) sb.Clear().Append(LOG_TAG).Append(func).Append("xrEnumeratePathsForInteractionProfileHTC result: ").Append(result)
{ .Append(", profile: ").Append(interactionProfileString)
sb.Clear().Append(LOG_TAG).Append(func) .Append(", trackerCount: ").Append(trackerCount);
.Append("Retrieves trackerCount failed."); ERROR(sb); DEBUG(sb);
userPaths = null; if (result != XrResult.XR_SUCCESS || trackerCount <= 0) { return false; }
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. // 2. Get user paths of sepecified profile.
List<XrPath> trackerList = CreateList<XrPath>(trackerCount, OpenXRHelper.XR_NULL_PATH); List<XrPath> trackerList = CreateList<XrPath>(trackerCount, OpenXRHelper.XR_NULL_PATH);
XrPath[] trackers = trackerList.ToArray(); XrPath[] trackers = trackerList.ToArray();
@@ -236,20 +231,13 @@ namespace VIVE.OpenXR
paths: trackers); 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 trackers failed."); ERROR(sb);
userPaths = null;
return false; return false;
} }
userPaths = trackers; userPaths = trackers;
return true; return true;
} }
else
{
userPaths = null;
return false;
}
}
public bool GetInputPathsWithUserPath(string interactionProfileString, XrPath userPath, out XrPath[] inputPaths) public bool GetInputPathsWithUserPath(string interactionProfileString, XrPath userPath, out XrPath[] inputPaths)
{ {

View File

@@ -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);"

View File

@@ -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);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: ba0bbb9482b5a5d479cf11f2253cdc3e guid: 099596ee936f4724f866de5335ef9cf6
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -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();
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b9b8d210c92da6a49ac85755f7b15cbb guid: 97968beb8cc921d4cbebb8e3a2bc3d6c
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -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;
@@ -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);

View File

@@ -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 HTCs 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

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -1,5 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: cc4f135068c02f84da2f529a300f9ba7 guid: 3744e08fd384e154c82d53686f0c82a6
folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 44fadaef2720de846af62a7bbb2ec370
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1504c5149524a8a44a3854dfd4f94d1e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a10ddbfcbce64f84787e026f4fc003a4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 950573398b7eb5149bea536bfa6107ca
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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 HTCs 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
}
}

View File

@@ -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);

View File

@@ -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);
}
} }
} }

View File

@@ -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;
} }
} }
} }

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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 HTCs 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
}
}

View File

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

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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];
}
}
}

View File

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

View File

@@ -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;
}
}
}
}
}
}

View File

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

View File

@@ -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;
}
}
}
}
}
}

View File

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

View File

@@ -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 HTCs 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;
}
}
}

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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 HTCs 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

View File

@@ -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

View File

@@ -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 HTCs 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
}
}

View File

@@ -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 HTCs 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;
}
}
}

View File

@@ -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 HTCs 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
}
}

View File

@@ -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
}
}

View File

@@ -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;

View File

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

View File

@@ -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}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2687505453755f7438ac99850b96b1a4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 68f46279552dd914784cc5edae6479b8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -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"
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e7d763db3f0479b49a6e358298f1717f
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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