version 2.3.0
This commit is contained in:
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Anchor.meta
Normal file
8
com.htc.upm.vive.openxr/Runtime/Toolkits/Anchor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ade49fa87f3cc404ea1b73cf2066c9a5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
180
com.htc.upm.vive.openxr/Runtime/Toolkits/Anchor/AnchorManager.cs
Normal file
180
com.htc.upm.vive.openxr/Runtime/Toolkits/Anchor/AnchorManager.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using VIVE.OpenXR.Feature;
|
||||
using VIVE.OpenXR.Anchor;
|
||||
using static VIVE.OpenXR.Anchor.ViveAnchor;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.Anchor
|
||||
{
|
||||
public static class AnchorManager
|
||||
{
|
||||
static ViveAnchor feature = null;
|
||||
static bool isSupported = false;
|
||||
|
||||
static void CheckFeature()
|
||||
{
|
||||
if (feature != null) return;
|
||||
|
||||
feature = OpenXRSettings.Instance.GetFeature<ViveAnchor>();
|
||||
if (feature == null)
|
||||
throw new NotSupportedException("ViveAnchor feature is not enabled");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to get the extention feature instance.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ViveAnchor GetFeature()
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckFeature();
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
Debug.LogWarning("ViveAnchor feature is not enabled");
|
||||
return null;
|
||||
}
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the extension is supported.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsSupported()
|
||||
{
|
||||
if (GetFeature() == null) return false;
|
||||
if (isSupported) return true;
|
||||
|
||||
var ret = false;
|
||||
if (feature.GetProperties(out XrSystemAnchorPropertiesHTC properties) == XrResult.XR_SUCCESS)
|
||||
{
|
||||
Debug.Log("Anchor: IsSupported() properties.supportedFeatures: " + properties.supportsAnchor);
|
||||
ret = properties.supportsAnchor;
|
||||
isSupported = ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Anchor: IsSupported() GetSystemProperties failed.");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a spatial anchor at tracking space (Camera Rig).
|
||||
/// </summary>
|
||||
/// <param name="pose">The related pose to the tracking space (Camera Rig)</param>
|
||||
/// <returns>Anchor container</returns>
|
||||
public static Anchor CreateAnchor(Pose pose, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckFeature();
|
||||
XrSpace baseSpace = feature.GetTrackingSpace();
|
||||
XrSpatialAnchorCreateInfoHTC createInfo = new XrSpatialAnchorCreateInfoHTC();
|
||||
createInfo.type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC;
|
||||
createInfo.poseInSpace = new XrPosef();
|
||||
createInfo.poseInSpace.position = pose.position.ToOpenXRVector();
|
||||
createInfo.poseInSpace.orientation = pose.rotation.ToOpenXRQuaternion();
|
||||
createInfo.name.name = name;
|
||||
createInfo.space = baseSpace;
|
||||
|
||||
if (feature.CreateSpatialAnchor(createInfo, out XrSpace anchor) == XrResult.XR_SUCCESS)
|
||||
{
|
||||
return new Anchor(anchor, name);
|
||||
}
|
||||
} catch (Exception) { }
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool GetSpatialAnchorName(Anchor anchor, out string name)
|
||||
{
|
||||
return GetSpatialAnchorName(anchor.GetXrSpace(), out name);
|
||||
}
|
||||
|
||||
public static bool GetSpatialAnchorName(XrSpace anchor, out string name)
|
||||
{
|
||||
name = "";
|
||||
CheckFeature();
|
||||
XrResult ret = feature.GetSpatialAnchorName(anchor, out XrSpatialAnchorNameHTC xrName);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
name = xrName.name;
|
||||
return ret == XrResult.XR_SUCCESS;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the XrSpace stand for current tracking space.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static XrSpace GetTrackingSpace()
|
||||
{
|
||||
CheckFeature();
|
||||
return feature.GetTrackingSpace();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the pose related to current tracking space. Only when position and orientation are both valid, the pose is valid.
|
||||
/// </summary>
|
||||
/// <param name="anchor"></param>
|
||||
/// <param name="pose"></param>
|
||||
/// <returns>true if both position and rotation are valid.</returns>
|
||||
public static bool GetTrackingSpacePose(Anchor anchor, out Pose pose)
|
||||
{
|
||||
var sw = SpaceWrapper.Instance;
|
||||
return anchor.GetRelatedPose(feature.GetTrackingSpace(), ViveInterceptors.Instance.GetPredictTime(), out pose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anchor is a named Space. It can be used to create a spatial anchor, or get the anchor's name.
|
||||
/// After use it, you should call Dispose() to release the anchor.
|
||||
/// </summary>
|
||||
public class Anchor : VIVE.OpenXR.Feature.Space
|
||||
{
|
||||
/// <summary>
|
||||
/// The anchor's name
|
||||
/// </summary>
|
||||
string name;
|
||||
|
||||
/// <summary>
|
||||
/// The anchor's name
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = GetSpatialAnchorName();
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
internal Anchor(XrSpace anchor, string name) : base(anchor)
|
||||
{
|
||||
// Get the current tracking space.
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
internal Anchor(Anchor other) : base(other.space)
|
||||
{
|
||||
// Get the current tracking space.
|
||||
name = other.name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the anchor's name by using this anchor's handle, instead of the anchor's Name. This will update the anchor's Name.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetSpatialAnchorName()
|
||||
{
|
||||
AnchorManager.CheckFeature();
|
||||
if (AnchorManager.GetSpatialAnchorName(this, out string name))
|
||||
return name;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5dd060e5d33dc942adbd03d6b9dc9bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2e0dce69c4834e47b59f25575269566
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9a059009a1d2414c94f3b9c7e46dfe9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VIVE.OpenXR
|
||||
{
|
||||
public class MemoryTools
|
||||
{
|
||||
public static IntPtr ToIntPtr<T>(T[] array) where T : Enum
|
||||
{
|
||||
int size = Marshal.SizeOf(typeof(T)) * array.Length;
|
||||
IntPtr ptr = Marshal.AllocHGlobal(size);
|
||||
int[] intArray = new int[array.Length];
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
intArray[i] = (int)(object)array[i];
|
||||
Marshal.Copy(intArray, 0, ptr, array.Length);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Make the same size raw buffer from input array.
|
||||
public static IntPtr MakeRawMemory<T>(T[] refArray)
|
||||
{
|
||||
int size = Marshal.SizeOf(typeof(T)) * refArray.Length;
|
||||
return Marshal.AllocHGlobal(size);
|
||||
}
|
||||
|
||||
// Make the same size raw buffer from input array.
|
||||
public static void CopyFromRawMemory<T>(T[] array, IntPtr raw)
|
||||
{
|
||||
int step = Marshal.SizeOf(typeof(T));
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
array[i] = Marshal.PtrToStructure<T>(IntPtr.Add(raw, i * step));
|
||||
}
|
||||
}
|
||||
|
||||
// Make the same size raw buffer from input array. Make sure the raw has enough size.
|
||||
public static void CopyToRawMemory<T>(IntPtr raw, T[] array)
|
||||
{
|
||||
int step = Marshal.SizeOf(typeof(T));
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
Marshal.StructureToPtr<T>(array[i], IntPtr.Add(raw, i * step), false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReleaseRawMemory(IntPtr ptr)
|
||||
{
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a887cb158a37cf45b17458a4f27d7ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,373 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using VIVE.OpenXR.PlaneDetection;
|
||||
using static VIVE.OpenXR.PlaneDetection.VivePlaneDetection;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.PlaneDetection
|
||||
{
|
||||
/// <summary>
|
||||
/// The detected plane's data.
|
||||
/// See <see cref="VivePlaneDetection.XrPlaneDetectorLocationEXT"/>
|
||||
/// </summary>
|
||||
public class PlaneDetectorLocation
|
||||
{
|
||||
public ulong planeId;
|
||||
public XrSpaceLocationFlags locationFlags;
|
||||
public Pose pose;
|
||||
public Vector3 size; // Only width(X) and height(Y) are valid, Z is always 0.
|
||||
public XrPlaneDetectorOrientationEXT orientation;
|
||||
public XrPlaneDetectorSemanticTypeEXT semanticType;
|
||||
public uint polygonBufferCount;
|
||||
public XrPlaneDetectorLocationEXT locationRaw;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The information for creating Mesh.
|
||||
/// Plane's normal is facing +Z in Unity's coordination.
|
||||
/// </summary>
|
||||
public class Plane
|
||||
{
|
||||
public Vector3 scale; // Only width(X) and height(Y) are valid, Z is always 1.
|
||||
public Vector3 center; // Should always be Vector3.Zero.
|
||||
public Vector3[] verticesRaw; // The original vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/>
|
||||
public Vector3[] verticesGenerated; // generated vertices for creating Mesh.
|
||||
public Vector2[] uvsGenerated;
|
||||
public int[] indicesGenerated;
|
||||
|
||||
/// <summary>
|
||||
/// According to the input vertices, calculate the rectangle, and create the plane.
|
||||
/// </summary>
|
||||
/// <param name="vertices">The vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/></param>
|
||||
public static Plane CreateFromVertices(Vector2[] vertices)
|
||||
{
|
||||
// Assume the polygon is a rectangle.
|
||||
if (vertices.Length != 4)
|
||||
return null;
|
||||
// Check the size from vertices.
|
||||
Vector2 min, max;
|
||||
min = max = new Vector2(vertices[0].x, vertices[0].y);
|
||||
for (int i = 1; i < vertices.Length; i++)
|
||||
{
|
||||
min.x = Mathf.Min(min.x, vertices[i].x);
|
||||
min.y = Mathf.Min(min.y, vertices[i].y);
|
||||
max.x = Mathf.Max(max.x, vertices[i].x);
|
||||
max.y = Mathf.Max(max.y, vertices[i].y);
|
||||
}
|
||||
var verticesRaw = new Vector3[vertices.Length];
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
verticesRaw[i] = new Vector3(vertices[i].x, 0, vertices[i].y);
|
||||
|
||||
var verticesGenerated = new Vector3[4];
|
||||
verticesGenerated[0] = new Vector3(min.x, min.y, 0);
|
||||
verticesGenerated[1] = new Vector3(max.x, min.y, 0);
|
||||
verticesGenerated[2] = new Vector3(min.x, max.y, 0);
|
||||
verticesGenerated[3] = new Vector3(max.x, max.y, 0);
|
||||
|
||||
var indicesGenerated = new int[] { 0, 3, 2, 0, 1, 3 };
|
||||
|
||||
var uvsGenerated = new Vector2[4];
|
||||
uvsGenerated[0] = new Vector2(0, 0);
|
||||
uvsGenerated[1] = new Vector2(1, 0);
|
||||
uvsGenerated[2] = new Vector2(0, 1);
|
||||
uvsGenerated[3] = new Vector2(1, 1);
|
||||
|
||||
return new Plane()
|
||||
{
|
||||
scale = max - min,
|
||||
center = (max + min) / 2,
|
||||
verticesRaw = verticesRaw,
|
||||
verticesGenerated = verticesGenerated,
|
||||
indicesGenerated = indicesGenerated,
|
||||
uvsGenerated = uvsGenerated
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The PlaneDetector is created by <see cref="PlaneDetectionManager.CreatePlaneDetector" />.
|
||||
/// </summary>
|
||||
public class PlaneDetector
|
||||
{
|
||||
IntPtr planeDetector = IntPtr.Zero;
|
||||
VivePlaneDetection feature = null;
|
||||
|
||||
public PlaneDetector(IntPtr pd, VivePlaneDetection f)
|
||||
{
|
||||
feature = f;
|
||||
planeDetector = pd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the raw plane detector handle. See <see cref="VivePlaneDetection.CreatePlaneDetector"/>
|
||||
/// </summary>
|
||||
/// <returns>The raw plane detector handle</returns>
|
||||
public IntPtr GetDetectorRaw()
|
||||
{
|
||||
return planeDetector;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin detect plane. In VIVE's implementation, planes are predefined in Room Setup.
|
||||
/// However you have to call this function to get the plane information.
|
||||
/// See <see cref="VivePlaneDetection.BeginPlaneDetection"/>
|
||||
/// </summary>
|
||||
public XrResult BeginPlaneDetection()
|
||||
{
|
||||
if (feature == null)
|
||||
return XrResult.XR_ERROR_FEATURE_UNSUPPORTED;
|
||||
Debug.Log("BeginPlaneDetection()");
|
||||
var beginInfo = feature.MakeGetAllXrPlaneDetectorBeginInfoEXT();
|
||||
return feature.BeginPlaneDetection(planeDetector, beginInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the state of plane detection.
|
||||
/// See <see cref="VivePlaneDetection.GetPlaneDetectionState"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public XrPlaneDetectionStateEXT GetPlaneDetectionState()
|
||||
{
|
||||
if (feature == null)
|
||||
return XrPlaneDetectionStateEXT.NONE_EXT;
|
||||
Debug.Log("GetPlaneDetectionState()");
|
||||
XrPlaneDetectionStateEXT state = XrPlaneDetectionStateEXT.NONE_EXT;
|
||||
feature.GetPlaneDetectionState(planeDetector, ref state);
|
||||
return state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get result of plane detection.
|
||||
/// See <see cref="VivePlaneDetection.GetPlaneDetections"/>
|
||||
/// </summary>
|
||||
/// <param name="locations">The detected planes.</param>
|
||||
/// <returns></returns>
|
||||
public XrResult GetPlaneDetections(out List<PlaneDetectorLocation> locations)
|
||||
{
|
||||
locations = null;
|
||||
if (feature == null)
|
||||
return XrResult.XR_ERROR_FEATURE_UNSUPPORTED;
|
||||
Debug.Log("GetPlaneDetections()");
|
||||
XrPlaneDetectorGetInfoEXT info = new XrPlaneDetectorGetInfoEXT
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_GET_INFO_EXT,
|
||||
baseSpace = feature.GetTrackingSpace(),
|
||||
time = feature.GetPredictTime(),
|
||||
};
|
||||
XrPlaneDetectorLocationsEXT locationsRaw = new XrPlaneDetectorLocationsEXT
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_LOCATIONS_EXT,
|
||||
planeLocationCapacityInput = 0,
|
||||
planeLocationCountOutput = 0,
|
||||
planeLocations = IntPtr.Zero,
|
||||
};
|
||||
var ret = feature.GetPlaneDetections(planeDetector, ref info, ref locationsRaw);
|
||||
if (ret != XrResult.XR_SUCCESS || locationsRaw.planeLocationCountOutput == 0)
|
||||
return ret;
|
||||
|
||||
Debug.Log("GetPlaneDetections() locations.planeLocationCountOutput: " + locationsRaw.planeLocationCountOutput);
|
||||
var locationsArray = new XrPlaneDetectorLocationEXT[locationsRaw.planeLocationCountOutput];
|
||||
var locationsPtr = MemoryTools.MakeRawMemory(locationsArray);
|
||||
locationsRaw.planeLocationCapacityInput = locationsRaw.planeLocationCountOutput;
|
||||
locationsRaw.planeLocationCountOutput = 0;
|
||||
locationsRaw.planeLocations = locationsPtr;
|
||||
|
||||
ret = feature.GetPlaneDetections(planeDetector, ref info, ref locationsRaw);
|
||||
if (ret != XrResult.XR_SUCCESS)
|
||||
{
|
||||
MemoryTools.ReleaseRawMemory(locationsPtr);
|
||||
return ret;
|
||||
}
|
||||
MemoryTools.CopyFromRawMemory(locationsArray, locationsPtr);
|
||||
|
||||
locations = new List<PlaneDetectorLocation>();
|
||||
|
||||
// The plane's neutral pose is horizontal, and not like the plane pose in unity which is vertical.
|
||||
// Therefore, we wil perform a rotation to convert from the OpenXR's forward to unity's forward.
|
||||
// In Unity, the rotation applied order is in ZXY order.
|
||||
Quaternion forward = Quaternion.Euler(-90, 180, 0);
|
||||
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
|
||||
{
|
||||
XrPlaneDetectorLocationEXT location = locationsArray[i];
|
||||
PlaneDetectorLocation pdl = new PlaneDetectorLocation
|
||||
{
|
||||
planeId = location.planeId,
|
||||
locationFlags = location.locationFlags,
|
||||
pose = new Pose(OpenXRHelper.ToUnityVector(location.pose.position), OpenXRHelper.ToUnityQuaternion(location.pose.orientation) * forward),
|
||||
// Because the pose is converted, we will apply extent to X and Y.
|
||||
size = new Vector3(location.extents.width, location.extents.height, 0),
|
||||
orientation = location.orientation,
|
||||
semanticType = location.semanticType,
|
||||
polygonBufferCount = location.polygonBufferCount,
|
||||
locationRaw = location,
|
||||
};
|
||||
locations.Add(pdl);
|
||||
}
|
||||
|
||||
MemoryTools.ReleaseRawMemory(locationsPtr);
|
||||
|
||||
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
|
||||
{
|
||||
var location = locations[i];
|
||||
Debug.Log("GetPlaneDetections() location.planeId: " + location.planeId);
|
||||
Debug.Log("GetPlaneDetections() location.locationFlags: " + location.locationFlags);
|
||||
Debug.Log("GetPlaneDetections() location.pose.position: " + location.pose.position);
|
||||
Debug.Log("GetPlaneDetections() location.pose.rotation: " + location.pose.rotation);
|
||||
Debug.Log("GetPlaneDetections() location.pose.rotation.eulerAngles: " + location.pose.rotation.eulerAngles);
|
||||
var rot = location.locationRaw.pose.orientation;
|
||||
Debug.Log($"GetPlaneDetections() locationRaw.pose.rotation: {rot.x}, {rot.y}, {rot.z}, {rot.w}");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the vertices of the plane from extension. Because there is no triangle
|
||||
/// information from extension, it is hard to generate a mesh from only these vertices.
|
||||
/// However VIVE only have rectangle plane. In this function, it will return the
|
||||
/// <see cref="Plane"/> class which contains generated information for creating Mesh.
|
||||
/// </summary>
|
||||
/// <param name="planeId">The planeId from <see cref="PlaneDetectorLocation"/></param>
|
||||
/// <returns>The information for creating Mesh.</returns>
|
||||
public Plane GetPlane(ulong planeId)
|
||||
{
|
||||
if (feature == null)
|
||||
return null;
|
||||
|
||||
XrPlaneDetectorPolygonBufferEXT polygonBuffer = new XrPlaneDetectorPolygonBufferEXT
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_POLYGON_BUFFER_EXT,
|
||||
vertexCapacityInput = 0,
|
||||
vertexCountOutput = 0,
|
||||
vertices = IntPtr.Zero,
|
||||
};
|
||||
|
||||
var ret = feature.GetPlanePolygonBuffer(planeDetector, planeId, 0, ref polygonBuffer);
|
||||
Debug.Log("GetPlane() polygonBuffer.vertexCountOutput: " + polygonBuffer.vertexCountOutput);
|
||||
if (ret != XrResult.XR_SUCCESS || polygonBuffer.vertexCountOutput == 0)
|
||||
return null;
|
||||
var verticesArray = new Vector2[polygonBuffer.vertexCountOutput];
|
||||
var verticesPtr = MemoryTools.MakeRawMemory(verticesArray);
|
||||
polygonBuffer.vertexCapacityInput = polygonBuffer.vertexCountOutput;
|
||||
polygonBuffer.vertexCountOutput = 0;
|
||||
polygonBuffer.vertices = verticesPtr;
|
||||
if (feature.GetPlanePolygonBuffer(planeDetector, planeId, 0, ref polygonBuffer) != XrResult.XR_SUCCESS)
|
||||
{
|
||||
MemoryTools.ReleaseRawMemory(verticesPtr);
|
||||
return null;
|
||||
}
|
||||
MemoryTools.CopyFromRawMemory(verticesArray, verticesPtr);
|
||||
MemoryTools.ReleaseRawMemory(verticesPtr);
|
||||
|
||||
for (int j = 0; j < verticesArray.Length; j++)
|
||||
{
|
||||
var v = verticesArray[j];
|
||||
Debug.Log($"GetPlane() verticesArray[{j}]: ({v.x}, {v.y})");
|
||||
}
|
||||
return Plane.CreateFromVertices(verticesArray);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlaneDetectionManager
|
||||
{
|
||||
static VivePlaneDetection feature = null;
|
||||
static bool isSupported = false;
|
||||
|
||||
static void CheckFeature()
|
||||
{
|
||||
if (feature != null) return;
|
||||
feature = OpenXRSettings.Instance.GetFeature<VivePlaneDetection>();
|
||||
if (feature == null)
|
||||
throw new NotSupportedException("PlaneDetection feature is not enabled");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to get the extention feature instance.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static VivePlaneDetection GetFeature()
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckFeature();
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
Debug.LogWarning("PlaneDetection feature is not enabled");
|
||||
return null;
|
||||
}
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the extension is supported.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsSupported()
|
||||
{
|
||||
if (GetFeature() == null) return false;
|
||||
if (isSupported) return true;
|
||||
if (feature == null) return false;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
if (feature.GetProperties(out var properties) == XrResult.XR_SUCCESS)
|
||||
{
|
||||
Debug.Log("PlaneDetection: IsSupported() properties.supportedFeatures: " + properties.supportedFeatures);
|
||||
ret = (properties.supportedFeatures & CAPABILITY_PLANE_DETECTION_BIT_EXT) > 0;
|
||||
isSupported = ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("PlaneDetection: IsSupported() GetSystemProperties failed.");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is a helper function. Currently only one createInfo is available. Developepr should create their own
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static XrPlaneDetectorCreateInfoEXT MakeXrPlaneDetectorCreateInfoEXT()
|
||||
{
|
||||
return new XrPlaneDetectorCreateInfoEXT
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_PLANE_DETECTOR_CREATE_INFO_EXT,
|
||||
flags = XR_PLANE_DETECTOR_ENABLE_CONTOUR_BIT_EXT,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plane detector is a session of detect plane. You don't need to create multiple plane detector in VIVE's implemention. You need destroy it.
|
||||
/// </summary>
|
||||
/// <returns>PlaneDetector's handle</returns>
|
||||
public static PlaneDetector CreatePlaneDetector()
|
||||
{
|
||||
CheckFeature();
|
||||
if (feature == null)
|
||||
return null;
|
||||
|
||||
var createInfo = MakeXrPlaneDetectorCreateInfoEXT();
|
||||
var ret = feature.CreatePlaneDetector(createInfo, out var planeDetector);
|
||||
if (ret != XrResult.XR_SUCCESS)
|
||||
return null;
|
||||
return new PlaneDetector(planeDetector, feature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy the plane detector to release resource.
|
||||
/// </summary>
|
||||
public static void DestroyPlaneDetector(PlaneDetector pd)
|
||||
{
|
||||
if (pd == null)
|
||||
return;
|
||||
CheckFeature();
|
||||
if (feature == null)
|
||||
return;
|
||||
feature.DestroyPlaneDetector(pd.GetDetectorRaw());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f0a3b1ff9cb22f4293ff346f84a541e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa3c636a6aff8234b9c9e019b5396109
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff1e9b9f321387c4e86e6a5fa6cd65c9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
[CustomEditor(typeof(CustomGrabPose))]
|
||||
public class CustomGrabPoseEditor : UnityEditor.Editor
|
||||
{
|
||||
private CustomGrabPose m_GrabPoseDesigner;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_GrabPoseDesigner = target as CustomGrabPose;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
GUILayout.Space(10f);
|
||||
ShowGrabPosesMenu();
|
||||
}
|
||||
|
||||
private void ShowGrabPosesMenu()
|
||||
{
|
||||
if (GUILayout.Button("Save HandGrab Pose"))
|
||||
{
|
||||
m_GrabPoseDesigner.FindNearInteractable();
|
||||
m_GrabPoseDesigner.SavePoseWithCandidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c01f88bc88bb27849a723888721c96f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,246 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
[CustomEditor(typeof(HandGrabInteractable))]
|
||||
public class HandGrabInteractableEditor : UnityEditor.Editor
|
||||
{
|
||||
private SerializedProperty m_IsGrabbable, m_ForceMovable, m_FingerRequirement, m_GrabPoses, m_ShowAllIndicator;
|
||||
private SerializedProperty grabPoseName, gestureThumbPose, gestureIndexPose, gestureMiddlePose, gestureRingPose, gesturePinkyPose,
|
||||
recordedGrabRotations, isLeft, enableIndicator, autoIndicator, indicatorObject, grabOffset;
|
||||
private ReorderableList grabPoses;
|
||||
private bool showGrabPoses = false;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_IsGrabbable = serializedObject.FindProperty("m_IsGrabbable");
|
||||
m_ForceMovable = serializedObject.FindProperty("m_ForceMovable");
|
||||
m_FingerRequirement = serializedObject.FindProperty("m_FingerRequirement");
|
||||
m_GrabPoses = serializedObject.FindProperty("m_GrabPoses");
|
||||
m_ShowAllIndicator = serializedObject.FindProperty("m_ShowAllIndicator");
|
||||
|
||||
#region ReorderableList
|
||||
grabPoses = new ReorderableList(serializedObject, m_GrabPoses, true, true, true, true);
|
||||
grabPoses.drawHeaderCallback = (Rect rect) =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, "Grab Pose List");
|
||||
};
|
||||
grabPoses.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return; }
|
||||
|
||||
if (string.IsNullOrEmpty(grabPoseName.stringValue))
|
||||
{
|
||||
grabPoseName.stringValue = $"Grab Pose {index + 1}";
|
||||
}
|
||||
|
||||
Rect gestureRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
|
||||
grabPoseName.stringValue = EditorGUI.TextField(gestureRect, grabPoseName.stringValue);
|
||||
if (recordedGrabRotations.arraySize == 0)
|
||||
{
|
||||
// Draw GrabGesture fields
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
EditorGUI.PropertyField(gestureRect, gestureThumbPose);
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
EditorGUI.PropertyField(gestureRect, gestureIndexPose);
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
EditorGUI.PropertyField(gestureRect, gestureMiddlePose);
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
EditorGUI.PropertyField(gestureRect, gestureRingPose);
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
EditorGUI.PropertyField(gestureRect, gesturePinkyPose);
|
||||
}
|
||||
|
||||
// Draw Handness fields
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
bool isToggle = EditorGUI.Toggle(gestureRect, "Is Left", isLeft.boolValue);
|
||||
if (isToggle != isLeft.boolValue)
|
||||
{
|
||||
isLeft.boolValue = isToggle;
|
||||
SwitchRotations(ref recordedGrabRotations);
|
||||
}
|
||||
// Draw Indicator fields
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
enableIndicator.boolValue = EditorGUI.Toggle(gestureRect, "Show Indicator", enableIndicator.boolValue);
|
||||
if (enableIndicator.boolValue)
|
||||
{
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
autoIndicator.boolValue = EditorGUI.Toggle(gestureRect, "Auto Generator Indicator", autoIndicator.boolValue);
|
||||
if (!autoIndicator.boolValue)
|
||||
{
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
indicatorObject.objectReferenceValue = (GameObject)EditorGUI.ObjectField(gestureRect, "Indicator", (GameObject)indicatorObject.objectReferenceValue, typeof(GameObject), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ShowAllIndicator.boolValue = false;
|
||||
}
|
||||
|
||||
// Draw Mirror Pose fields
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
Rect labelRect = new Rect(gestureRect.x, gestureRect.y, EditorGUIUtility.labelWidth, gestureRect.height);
|
||||
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Mirror Pose"));
|
||||
|
||||
Rect buttonRect1 = new Rect(gestureRect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, (gestureRect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 3, gestureRect.height);
|
||||
Rect buttonRect2 = new Rect(buttonRect1.x + buttonRect1.width + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, buttonRect1.width, gestureRect.height);
|
||||
Rect buttonRect3 = new Rect(buttonRect2.x + buttonRect2.width + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, buttonRect1.width, gestureRect.height);
|
||||
if (GUI.Button(buttonRect1, "Align X axis"))
|
||||
{
|
||||
MirrorPose(ref grabOffset, Vector3.right);
|
||||
}
|
||||
if (GUI.Button(buttonRect2, "Align Y axis"))
|
||||
{
|
||||
MirrorPose(ref grabOffset, Vector3.up);
|
||||
}
|
||||
if (GUI.Button(buttonRect3, "Align Z axis"))
|
||||
{
|
||||
MirrorPose(ref grabOffset, Vector3.forward);
|
||||
}
|
||||
|
||||
// Draw Position fields
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
EditorGUI.PropertyField(gestureRect, grabOffset.FindPropertyRelative("position"));
|
||||
|
||||
// Draw Rotation fields
|
||||
SerializedProperty rotationProperty = grabOffset.FindPropertyRelative("rotation");
|
||||
Vector4 rotationVector = new Vector4(rotationProperty.quaternionValue.x, rotationProperty.quaternionValue.y, rotationProperty.quaternionValue.z, rotationProperty.quaternionValue.w);
|
||||
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
rotationVector = EditorGUI.Vector4Field(gestureRect, "Rotation", rotationVector);
|
||||
rotationProperty.quaternionValue = new Quaternion(rotationVector.x, rotationVector.y, rotationVector.z, rotationVector.w);
|
||||
};
|
||||
grabPoses.elementHeightCallback = (int index) =>
|
||||
{
|
||||
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return EditorGUIUtility.singleLineHeight; }
|
||||
|
||||
// Including Title, Handness, Show Indicator, Mirror Pose, Position, Rotation
|
||||
int minHeight = 6;
|
||||
// To Show GrabGesture
|
||||
if (recordedGrabRotations.arraySize == 0)
|
||||
{
|
||||
minHeight += 5;
|
||||
}
|
||||
if (enableIndicator.boolValue)
|
||||
{
|
||||
// To Show Auto Indicator
|
||||
minHeight += 1;
|
||||
// To Show Indicator Gameobject
|
||||
if (!autoIndicator.boolValue)
|
||||
{
|
||||
minHeight += 1;
|
||||
}
|
||||
}
|
||||
return EditorGUIUtility.singleLineHeight * minHeight + EditorGUIUtility.standardVerticalSpacing * (minHeight + 2);
|
||||
};
|
||||
grabPoses.onAddCallback = (ReorderableList list) =>
|
||||
{
|
||||
m_GrabPoses.arraySize++;
|
||||
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(list.count - 1)))
|
||||
{
|
||||
grabPoseName.stringValue = $"Grab Pose {list.count}";
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_IsGrabbable);
|
||||
EditorGUILayout.PropertyField(m_ForceMovable);
|
||||
EditorGUILayout.PropertyField(m_FingerRequirement);
|
||||
showGrabPoses = EditorGUILayout.Foldout(showGrabPoses, "Grab Pose Settings");
|
||||
if (showGrabPoses)
|
||||
{
|
||||
if(m_GrabPoses.arraySize == 0)
|
||||
{
|
||||
grabPoses.elementHeight = EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
grabPoses.DoLayoutList();
|
||||
|
||||
bool isToggle = EditorGUILayout.Toggle("Show All Indicator", m_ShowAllIndicator.boolValue);
|
||||
if (isToggle != m_ShowAllIndicator.boolValue)
|
||||
{
|
||||
m_ShowAllIndicator.boolValue = isToggle;
|
||||
for (int i = 0; i < m_GrabPoses.arraySize; i++)
|
||||
{
|
||||
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(i)))
|
||||
{
|
||||
enableIndicator.boolValue = m_ShowAllIndicator.boolValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private bool UpdateGrabPose(SerializedProperty grabPose)
|
||||
{
|
||||
SerializedProperty indicator = grabPose.FindPropertyRelative("indicator");
|
||||
if (grabPose == null || indicator == null) { return false; }
|
||||
|
||||
grabPoseName = grabPose.FindPropertyRelative("grabPoseName");
|
||||
gestureThumbPose = grabPose.FindPropertyRelative("handGrabGesture.thumbPose");
|
||||
gestureIndexPose = grabPose.FindPropertyRelative("handGrabGesture.indexPose");
|
||||
gestureMiddlePose = grabPose.FindPropertyRelative("handGrabGesture.middlePose");
|
||||
gestureRingPose = grabPose.FindPropertyRelative("handGrabGesture.ringPose");
|
||||
gesturePinkyPose = grabPose.FindPropertyRelative("handGrabGesture.pinkyPose");
|
||||
recordedGrabRotations = grabPose.FindPropertyRelative("recordedGrabRotations");
|
||||
isLeft = grabPose.FindPropertyRelative("isLeft");
|
||||
enableIndicator = indicator.FindPropertyRelative("enableIndicator");
|
||||
autoIndicator = indicator.FindPropertyRelative("autoIndicator");
|
||||
indicatorObject = indicator.FindPropertyRelative("target");
|
||||
grabOffset = grabPose.FindPropertyRelative("grabOffset");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the rotation of joints of the current hand into those of another hand.
|
||||
/// </summary>
|
||||
/// <param name="rotations">Rotation of joints of the current hand.</param>
|
||||
private void SwitchRotations(ref SerializedProperty rotations)
|
||||
{
|
||||
for (int i = 0; i < rotations.arraySize; i++)
|
||||
{
|
||||
Quaternion rotation = rotations.GetArrayElementAtIndex(i).quaternionValue;
|
||||
Quaternion newRotation = Quaternion.Euler(rotation.eulerAngles.x, -rotation.eulerAngles.y, -rotation.eulerAngles.z);
|
||||
rotations.GetArrayElementAtIndex(i).quaternionValue = newRotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors the pose properties (position and rotation) of a serialized object along a specified mirror axis.
|
||||
/// </summary>
|
||||
/// <param name="pose">The serialized property representing the pose to be mirrored.</param>
|
||||
/// <param name="mirrorAxis">The axis along which the mirroring should occur.</param>
|
||||
private void MirrorPose(ref SerializedProperty pose, Vector3 mirrorAxis)
|
||||
{
|
||||
Vector3 sourcePos = pose.FindPropertyRelative("position").vector3Value;
|
||||
Quaternion sourceRot = pose.FindPropertyRelative("rotation").quaternionValue;
|
||||
Vector3 sourceFwd = sourceRot * Vector3.forward;
|
||||
Vector3 sourceUp = sourceRot * Vector3.up;
|
||||
|
||||
// Calculate the mirrored position using Vector3.Reflect along the specified mirror axis.
|
||||
Vector3 mirroredPosition = Vector3.Reflect(sourcePos, mirrorAxis);
|
||||
// Calculate the mirrored rotation using Quaternion.LookRotation and Vector3.Reflect for the forward and up vectors.
|
||||
Quaternion mirroredRotation = Quaternion.LookRotation(Vector3.Reflect(sourceFwd, mirrorAxis), Vector3.Reflect(sourceUp, mirrorAxis));
|
||||
|
||||
pose.FindPropertyRelative("position").vector3Value = mirroredPosition;
|
||||
pose.FindPropertyRelative("rotation").quaternionValue = mirroredRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ba007edb5befc349a95aa1cfa7cb6c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
[CustomEditor(typeof(HandMeshManager))]
|
||||
public class HandMeshManagerEditor : Editor
|
||||
{
|
||||
private HandMeshManager m_HandJointManager;
|
||||
private SerializedProperty m_Handedness, m_HandGrabber, m_RootJointType, m_HandRootJoint, m_HandJoints;
|
||||
|
||||
private bool showJoints = false;
|
||||
public static readonly GUIContent findJoints = EditorGUIUtility.TrTextContent("Find Joints");
|
||||
public static readonly GUIContent clearJoints = EditorGUIUtility.TrTextContent("Clear");
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Handedness = serializedObject.FindProperty("m_Handedness");
|
||||
m_HandGrabber = serializedObject.FindProperty("m_HandGrabber");
|
||||
m_RootJointType = serializedObject.FindProperty("m_RootJointType");
|
||||
m_HandRootJoint = serializedObject.FindProperty("m_HandRootJoint");
|
||||
m_HandJoints = serializedObject.FindProperty("m_HandJoints");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_Handedness);
|
||||
EditorGUILayout.PropertyField(m_HandGrabber);
|
||||
EditorGUILayout.HelpBox("Without HandGrabber, it still works but won't stop when colliding with Immovable objects.", MessageType.Info);
|
||||
EditorGUILayout.PropertyField(m_RootJointType);
|
||||
EditorGUILayout.PropertyField(m_HandRootJoint);
|
||||
showJoints = EditorGUILayout.Foldout(showJoints, "Hand Joints");
|
||||
if (showJoints)
|
||||
{
|
||||
for (int i = 0; i < m_HandJoints.arraySize; i++)
|
||||
{
|
||||
SerializedProperty joint = m_HandJoints.GetArrayElementAtIndex(i);
|
||||
JointType jointType = (JointType)i;
|
||||
EditorGUILayout.PropertyField(joint, new GUIContent(jointType.ToString()));
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
m_HandJointManager = target as HandMeshManager;
|
||||
using (new EditorGUI.DisabledScope())
|
||||
{
|
||||
if (GUILayout.Button(findJoints))
|
||||
{
|
||||
m_HandJointManager.FindJoints();
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope())
|
||||
{
|
||||
if (GUILayout.Button(clearJoints))
|
||||
{
|
||||
m_HandJointManager.ClearJoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d606d68d81c647241b90f1060786476c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e312e5246287a2546944bb77519270c4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32007394341a17c44859030ef5b809ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 010c0098d232cb0428f42a48488a6255
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9731229184277b54ba66bffd1633169b
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b7f1f69b8b23e9459efda715941fbee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to automatically generate indicators.
|
||||
/// </summary>
|
||||
public class AutoGenIndicator : MonoBehaviour
|
||||
{
|
||||
private MeshRenderer meshRenderer;
|
||||
private MeshFilter meshFilter;
|
||||
|
||||
private readonly Color indicatorColor = new Color(1f, 0.7960785f, 0.09411766f, 1f);
|
||||
private const float k_Length = 0.05f;
|
||||
private const float k_Width = 0.05f;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
meshRenderer = transform.gameObject.AddComponent<MeshRenderer>();
|
||||
meshFilter = transform.gameObject.AddComponent<MeshFilter>();
|
||||
MeshInitialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the mesh for the indicator.
|
||||
/// </summary>
|
||||
private void MeshInitialize()
|
||||
{
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
meshRenderer.material = new Material(shader);
|
||||
meshRenderer.material.SetColor("_Color", indicatorColor);
|
||||
meshRenderer.sortingOrder = 1;
|
||||
|
||||
Mesh arrowMesh = new Mesh();
|
||||
Vector3[] vertices = new Vector3[4];
|
||||
int[] triangles = new int[3 * 2];
|
||||
|
||||
vertices[0] = new Vector3(0, 0f, 0f);
|
||||
vertices[1] = new Vector3(0f, k_Length * 0.8f, 0f);
|
||||
vertices[2] = new Vector3(-k_Width * 0.5f, k_Length, 0f);
|
||||
vertices[3] = new Vector3(k_Width * 0.5f, k_Length, 0f);
|
||||
|
||||
triangles[0] = 0;
|
||||
triangles[1] = 2;
|
||||
triangles[2] = 1;
|
||||
|
||||
triangles[3] = 0;
|
||||
triangles[4] = 1;
|
||||
triangles[5] = 3;
|
||||
|
||||
arrowMesh.vertices = vertices;
|
||||
arrowMesh.triangles = triangles;
|
||||
|
||||
arrowMesh.RecalculateNormals();
|
||||
|
||||
meshFilter.mesh = arrowMesh;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the pose of the indicator.
|
||||
/// </summary>
|
||||
/// <param name="position">The position vector to set.</param>
|
||||
/// <param name="direction">The direction vector to set.</param>
|
||||
public void SetPose(Vector3 position, Vector3 direction)
|
||||
{
|
||||
transform.position = position;
|
||||
transform.up = direction.normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4694bdada589dce4c9b7096c7169833f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,93 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to edit grab gestures.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(HandMeshManager))]
|
||||
public class CustomGrabPose : MonoBehaviour
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField]
|
||||
private HandGrabGesture m_GrabGesture;
|
||||
|
||||
private HandMeshManager jointManager;
|
||||
private Transform palmTransform;
|
||||
private HandGrabGesture currentGesture;
|
||||
private HandGrabInteractable candidate = null;
|
||||
private readonly float k_GrabDistance = 0.1f;
|
||||
|
||||
private Pose[] fingerTipPoses => new Pose[5];
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (jointManager == null)
|
||||
{
|
||||
jointManager = transform.GetComponent<HandMeshManager>();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Non-DirectPreview mode.
|
||||
if (m_GrabGesture != currentGesture)
|
||||
{
|
||||
currentGesture = m_GrabGesture;
|
||||
jointManager.SetJointsFromGrabGesture(currentGesture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the nearest interactable object to the hand.
|
||||
/// </summary>
|
||||
public void FindNearInteractable()
|
||||
{
|
||||
if (jointManager.GetJointTransform(JointType.Palm, out palmTransform))
|
||||
{
|
||||
float maxScore = 0;
|
||||
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
||||
{
|
||||
float distanceScore = interactable.CalculateDistanceScore(palmTransform.position, k_GrabDistance);
|
||||
if (distanceScore > maxScore)
|
||||
{
|
||||
maxScore = distanceScore;
|
||||
candidate = interactable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the position and rotation offset with the candidate.
|
||||
/// </summary>
|
||||
public void SavePoseWithCandidate()
|
||||
{
|
||||
if (candidate != null &&
|
||||
jointManager.GetJointTransform(JointType.Palm, out palmTransform))
|
||||
{
|
||||
Vector3 posOffset = candidate.transform.position - palmTransform.position;
|
||||
Quaternion rotOffset = palmTransform.rotation;
|
||||
GrabPose grabPose = GrabPose.Identity;
|
||||
grabPose.Update($"Grab Pose {candidate.grabPoses.Count + 1}", currentGesture, jointManager.IsLeft);
|
||||
grabPose.grabOffset = new GrabOffset(candidate.transform.position, candidate.transform.rotation, posOffset, rotOffset);
|
||||
if (!candidate.grabPoses.Contains(grabPose))
|
||||
{
|
||||
candidate.grabPoses.Add(grabPose);
|
||||
}
|
||||
GrabbablePoseRecorder.SaveChanges();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd5957dc7b39bd249885b5bb53749b7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to generate appropriately sized colliders for each joint.
|
||||
/// </summary>
|
||||
public class GrabCollider : MonoBehaviour
|
||||
{
|
||||
public enum CollisionState
|
||||
{
|
||||
start = 0,
|
||||
keep = 1,
|
||||
end = 2,
|
||||
}
|
||||
|
||||
private CapsuleCollider m_Collider = null;
|
||||
public Collider Collider => m_Collider;
|
||||
|
||||
private bool m_IsCollision = false;
|
||||
public bool IsCollision { get { return m_IsCollision; } set { m_IsCollision = value; } }
|
||||
|
||||
private const float k_ColliderRadius = 0.01f;
|
||||
private const float k_ColliderHeight = 0.01f;
|
||||
private JointType jointType = JointType.Count;
|
||||
|
||||
public delegate void CollisionHandler(JointType joint, Collision collision, CollisionState state);
|
||||
private CollisionHandler m_CollisionHandler;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Collider = transform.GetComponent<CapsuleCollider>();
|
||||
if (m_Collider == null)
|
||||
{
|
||||
m_Collider = transform.gameObject.AddComponent<CapsuleCollider>();
|
||||
}
|
||||
m_Collider.radius = k_ColliderRadius;
|
||||
m_Collider.height = k_ColliderHeight;
|
||||
m_Collider.direction = 2;
|
||||
|
||||
Rigidbody rigidbody = transform.GetComponent<Rigidbody>();
|
||||
if (rigidbody == null)
|
||||
{
|
||||
rigidbody = transform.gameObject.AddComponent<Rigidbody>();
|
||||
}
|
||||
rigidbody.useGravity = false;
|
||||
rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
rigidbody.constraints = RigidbodyConstraints.FreezeAll;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the joint id and adjust collider size..
|
||||
/// </summary>
|
||||
/// <param name="id">JointType of joint.</param>
|
||||
public void SetJointId(int id)
|
||||
{
|
||||
jointType = (JointType)id;
|
||||
if (m_Collider)
|
||||
{
|
||||
// Adjust the size and position of the collider based on jointId.
|
||||
switch (jointType)
|
||||
{
|
||||
case JointType.Thumb_Joint0:
|
||||
case JointType.Thumb_Joint1:
|
||||
m_Collider.height = 0.03f;
|
||||
break;
|
||||
case JointType.Index_Joint0:
|
||||
case JointType.Middle_Joint0:
|
||||
case JointType.Ring_Joint0:
|
||||
case JointType.Pinky_Joint0:
|
||||
m_Collider.height = 0.08f;
|
||||
m_Collider.center = new Vector3(0f, 0f, 0.02f);
|
||||
break;
|
||||
case JointType.Index_Joint1:
|
||||
case JointType.Middle_Joint1:
|
||||
case JointType.Ring_Joint1:
|
||||
case JointType.Pinky_Joint1:
|
||||
m_Collider.height = 0.05f;
|
||||
m_Collider.center = new Vector3(0f, 0f, 0.02f);
|
||||
break;
|
||||
case JointType.Index_Tip:
|
||||
case JointType.Middle_Tip:
|
||||
case JointType.Ring_Tip:
|
||||
case JointType.Pinky_Tip:
|
||||
m_Collider.radius = 0.005f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddListener(CollisionHandler handler)
|
||||
{
|
||||
m_CollisionHandler += handler;
|
||||
}
|
||||
|
||||
public void RemoveListener(CollisionHandler handler)
|
||||
{
|
||||
m_CollisionHandler -= handler;
|
||||
}
|
||||
|
||||
private void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
|
||||
{
|
||||
m_CollisionHandler.Invoke(jointType, collision, CollisionState.start);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollisionStay(Collision collision)
|
||||
{
|
||||
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
|
||||
{
|
||||
m_CollisionHandler.Invoke(jointType, collision, CollisionState.keep);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollisionExit(Collision collision)
|
||||
{
|
||||
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
|
||||
{
|
||||
m_CollisionHandler.Invoke(jointType, collision, CollisionState.end);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsGrabCollider(Collider collider)
|
||||
{
|
||||
GrabCollider grabCollider = collider.gameObject.GetComponent<GrabCollider>();
|
||||
return grabCollider != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba0bbb9482b5a5d479cf11f2253cdc3e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,486 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
public class GrabColliderManager : MonoBehaviour
|
||||
{
|
||||
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.GrabColliderManager";
|
||||
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||
|
||||
/// <summary>
|
||||
/// The struct is designed to record movable colliders,
|
||||
/// including which grabbable they belong to, which joints collisioned with, and whether they have been grabbed.
|
||||
/// </summary>
|
||||
private struct MovableHitInfo
|
||||
{
|
||||
public struct JointHitInfo
|
||||
{
|
||||
public JointType joint;
|
||||
public Vector3 hitOffset;
|
||||
public int hitTime { get; private set; }
|
||||
|
||||
public JointHitInfo(JointType in_JointType, Vector3 in_HitOffset)
|
||||
{
|
||||
joint = in_JointType;
|
||||
hitOffset = in_HitOffset;
|
||||
hitTime = Time.frameCount;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
hitTime = Time.frameCount;
|
||||
}
|
||||
}
|
||||
|
||||
public HandGrabInteractable grabbable;
|
||||
public List<JointHitInfo> jointHitInfos;
|
||||
public bool grabbed;
|
||||
public bool stopMove;
|
||||
|
||||
public MovableHitInfo(HandGrabInteractable in_Grabbable, JointType in_Joint, Vector3 in_Offset)
|
||||
{
|
||||
grabbable = in_Grabbable;
|
||||
jointHitInfos = new List<JointHitInfo>()
|
||||
{
|
||||
new JointHitInfo(in_Joint, in_Offset)
|
||||
};
|
||||
grabbed = false;
|
||||
stopMove = false;
|
||||
}
|
||||
|
||||
public void Update(bool in_Grabbed, bool in_StopMove)
|
||||
{
|
||||
grabbed = in_Grabbed;
|
||||
stopMove = in_StopMove;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
grabbed = false;
|
||||
stopMove = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a JointType. If it's already in the dictionary, update the time.
|
||||
/// </summary>
|
||||
/// <param name="joint">The joint which needs to be added.</param>
|
||||
public void AddJoint(JointType joint, Vector3 offset)
|
||||
{
|
||||
int hitId = jointHitInfos.FindIndex(x => x.joint == joint);
|
||||
if (hitId == -1)
|
||||
{
|
||||
jointHitInfos.Add(new JointHitInfo(joint, offset));
|
||||
}
|
||||
else
|
||||
{
|
||||
JointHitInfo jointHitInfo = jointHitInfos[hitId];
|
||||
jointHitInfo.Update();
|
||||
jointHitInfos[hitId] = jointHitInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a JointType and check if it has been grabbed.
|
||||
/// </summary>
|
||||
/// <param name="joint">The joint which needs to be removed.</param>
|
||||
public void RemoveJoint(JointType joint)
|
||||
{
|
||||
int hitId = jointHitInfos.FindIndex(x => x.joint == joint);
|
||||
if (hitId != -1)
|
||||
{
|
||||
jointHitInfos.RemoveAt(hitId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private HandMeshManager jointManager;
|
||||
|
||||
private GrabCollider[] jointsCollider = new GrabCollider[(int)JointType.Count];
|
||||
private Pose[] jointsPrevFramePose = new Pose[(int)JointType.Count];
|
||||
private bool isImmovableCollision = false;
|
||||
private bool isGrabbing = false;
|
||||
private List<MovableHitInfo> movableHits = new List<MovableHitInfo>();
|
||||
private Dictionary<GrabCollider, Collider> immovableHits = new Dictionary<GrabCollider, Collider>();
|
||||
public delegate void OnImmovableCollision(bool enable);
|
||||
private OnImmovableCollision immovableCollisionHandler;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (jointManager == null)
|
||||
{
|
||||
jointManager = transform.GetComponent<HandMeshManager>();
|
||||
if (jointManager == null)
|
||||
{
|
||||
ERROR("Failed to find HandJointManager.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (jointManager != null)
|
||||
{
|
||||
jointManager.HandGrabber.AddBeginGrabListener(OnGrabberBeginGrab);
|
||||
jointManager.HandGrabber.AddEndGrabListener(OnGrabberEndGrab);
|
||||
}
|
||||
|
||||
CreateJointsCollider();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (jointManager != null)
|
||||
{
|
||||
jointManager.HandGrabber.RemoveBeginGrabListener(OnGrabberBeginGrab);
|
||||
jointManager.HandGrabber.RemoveEndGrabListener(OnGrabberEndGrab);
|
||||
}
|
||||
|
||||
foreach (var collider in jointsCollider)
|
||||
{
|
||||
collider.RemoveListener(CollisionEvent);
|
||||
Destroy(collider);
|
||||
}
|
||||
Array.Clear(jointsCollider, 0, jointsCollider.Length);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (jointManager == null) { return; }
|
||||
|
||||
UpdateColliderPose();
|
||||
if (!isGrabbing)
|
||||
{
|
||||
UpdateImmovable();
|
||||
UpdateMovable();
|
||||
}
|
||||
|
||||
for (int i = 0; i < jointsCollider.Length; i++)
|
||||
{
|
||||
jointsPrevFramePose[i] = jointsCollider[i] == null ? Pose.identity : new Pose(jointsCollider[i].transform.position, jointsCollider[i].transform.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create colliders for each joint and set them do not collide with each other.
|
||||
/// </summary>
|
||||
private void CreateJointsCollider()
|
||||
{
|
||||
if (jointManager != null)
|
||||
{
|
||||
var cloneRoot = Instantiate(jointManager.HandRootJoint, jointManager.HandRootJoint.parent);
|
||||
cloneRoot.name = jointManager.HandRootJoint.name;
|
||||
List<GameObject> children = new List<GameObject>() { cloneRoot.gameObject };
|
||||
GetChildren(cloneRoot, children);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
Transform target = jointManager.HandJoints.FirstOrDefault(x => x.name == child.name);
|
||||
if (target != null)
|
||||
{
|
||||
int index = Array.IndexOf(jointManager.HandJoints, target);
|
||||
|
||||
GrabCollider grabCollider = child.AddComponent<GrabCollider>();
|
||||
grabCollider.AddListener(CollisionEvent);
|
||||
grabCollider.SetJointId(index);
|
||||
jointsCollider[index] = grabCollider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < jointsCollider.Length; i++)
|
||||
{
|
||||
if (jointsCollider[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = i + 1; j < jointsCollider.Length; j++)
|
||||
{
|
||||
if (jointsCollider[j] != null)
|
||||
{
|
||||
Physics.IgnoreCollision(jointsCollider[i].Collider, jointsCollider[j].Collider, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetChildren(Transform parent, List<GameObject> children)
|
||||
{
|
||||
foreach (Transform child in parent)
|
||||
{
|
||||
children.Add(child.gameObject);
|
||||
GetChildren(child, children);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the position of the collider using the position of the joint.
|
||||
/// </summary>
|
||||
private void UpdateColliderPose()
|
||||
{
|
||||
HandData hand = CachedHand.Get(jointManager.IsLeft);
|
||||
bool isTracked = hand.isTracked;
|
||||
if (!isTracked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parentTransform = jointManager.HandRootJoint.parent;
|
||||
var parentRotation = Matrix4x4.Rotate(parentTransform.rotation);
|
||||
Vector3 jointPosition = Vector3.zero;
|
||||
Quaternion jointRotation = Quaternion.identity;
|
||||
for (int i = 0; i < jointsCollider.Length; i++)
|
||||
{
|
||||
if (jointsCollider[i] == null) { continue; }
|
||||
|
||||
hand.GetJointPosition((JointType)i, ref jointPosition);
|
||||
hand.GetJointRotation((JointType)i, ref jointRotation);
|
||||
|
||||
if ((JointType)i == JointType.Wrist)
|
||||
{
|
||||
jointsCollider[i].transform.localPosition = jointPosition;
|
||||
jointsCollider[i].transform.localRotation = jointRotation;
|
||||
}
|
||||
jointsCollider[i].transform.rotation = (parentRotation * Matrix4x4.Rotate(jointRotation)).rotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the hand pose if a collision has already occurred with a joint.
|
||||
/// </summary>
|
||||
private void UpdateImmovable()
|
||||
{
|
||||
bool isCollision = jointsCollider.Any(x => x != null && x.IsCollision);
|
||||
foreach (var jointCollider in jointsCollider)
|
||||
{
|
||||
jointCollider.Collider.enabled = isCollision ? jointCollider.IsCollision : true;
|
||||
}
|
||||
|
||||
if (isImmovableCollision != isCollision)
|
||||
{
|
||||
isImmovableCollision = isCollision;
|
||||
immovableCollisionHandler?.Invoke(isImmovableCollision);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check all movableHits and move the object relative to the movement of the collisioned joint.
|
||||
/// </summary>
|
||||
private void UpdateMovable()
|
||||
{
|
||||
if (isImmovableCollision) { return; }
|
||||
|
||||
const int k_MinCollisionTimeDiff = 5;
|
||||
const int k_MaxCollisionTimeDiff = 50;
|
||||
|
||||
for (int i = movableHits.Count - 1; i >= 0; i--)
|
||||
{
|
||||
MovableHitInfo hit = movableHits[i];
|
||||
if (hit.stopMove) { continue; }
|
||||
|
||||
Vector3 totalPosition = Vector3.zero;
|
||||
Vector3 totalOffset = Vector3.zero;
|
||||
int validCount = 0;
|
||||
for (int j = hit.jointHitInfos.Count - 1; j >= 0; j--)
|
||||
{
|
||||
MovableHitInfo.JointHitInfo jointHit = hit.jointHitInfos[j];
|
||||
int frameCountDiff = Time.frameCount - jointHit.hitTime;
|
||||
if (frameCountDiff > k_MinCollisionTimeDiff)
|
||||
{
|
||||
if (frameCountDiff > k_MaxCollisionTimeDiff)
|
||||
{
|
||||
hit.RemoveJoint(jointHit.joint);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int jointId = (int)jointHit.joint;
|
||||
Vector3 currentPose = jointsCollider[jointId].transform.position;
|
||||
Vector3 prevPose = jointsPrevFramePose[jointId].position;
|
||||
|
||||
// Condition 1: Calculate the displacement between consecutive frames of joints, it should greater than 1E-6f as significant.
|
||||
// Condition 2: Calculate distance score relative to grabbable; the score of current pose should be greater than the previous pose.
|
||||
// Condition 3: The dot product of the vector between the current pose and the grabbable object,
|
||||
// and the vector representing finger movement direction should be less than 0.
|
||||
if (Vector3.Distance(prevPose, currentPose) > 1E-6f &&
|
||||
movableHits[i].grabbable.CalculateDistanceScore(currentPose) >= movableHits[i].grabbable.CalculateDistanceScore(prevPose) &&
|
||||
Vector3.Dot((currentPose - prevPose).normalized, (movableHits[i].grabbable.transform.position - prevPose).normalized) > 0)
|
||||
{
|
||||
validCount++;
|
||||
totalPosition += currentPose;
|
||||
totalOffset += jointHit.hitOffset;
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount > 0)
|
||||
{
|
||||
movableHits[i].grabbable.transform.position = (totalPosition - totalOffset) / validCount;
|
||||
}
|
||||
|
||||
if (hit.jointHitInfos.Count == 0)
|
||||
{
|
||||
movableHits.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
movableHits[i] = hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable or disable the collider of joints.
|
||||
/// </summary>
|
||||
/// <param name="enable">Enable (true) or disable (false) the colliders.</param>
|
||||
public void EnableCollider(bool enable)
|
||||
{
|
||||
for (int i = 0; i < jointsCollider.Length; i++)
|
||||
{
|
||||
if (jointsCollider[i] != null)
|
||||
{
|
||||
jointsCollider[i].gameObject.SetActive(enable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Collision Event
|
||||
/// <summary>
|
||||
/// Adds a listener for immovable collision events.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when an immovable collision occurs.</param>
|
||||
public void AddImmovableCollisionListener(OnImmovableCollision handler)
|
||||
{
|
||||
immovableCollisionHandler += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a listener for immovable collision events.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the immovable collision event listeners.</param>
|
||||
public void RemoveImmovableCollisionListener(OnImmovableCollision handler)
|
||||
{
|
||||
immovableCollisionHandler -= handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for when the grabber begins grabbing.
|
||||
/// </summary>
|
||||
/// <param name="grabber">The grabber of IGrabber.</param>
|
||||
private void OnGrabberBeginGrab(IGrabber grabber)
|
||||
{
|
||||
isGrabbing = true;
|
||||
for (int i = 0; i < movableHits.Count; i++)
|
||||
{
|
||||
if (grabber.grabbable is HandGrabInteractable &&
|
||||
(HandGrabInteractable)grabber.grabbable == movableHits[i].grabbable)
|
||||
{
|
||||
MovableHitInfo movableHit = movableHits[i];
|
||||
movableHit.Update(true, true);
|
||||
movableHits[i] = movableHit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGrabberEndGrab(IGrabber grabber)
|
||||
{
|
||||
isGrabbing = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter all collision events, check for grabbables, and update collision data.
|
||||
/// </summary>
|
||||
/// <param name="joint">The joint which has been collision.</param>
|
||||
/// <param name="collision">The data of Collision.</param>
|
||||
/// <param name="isColliding">True when the collision event is OnCollisionEnter or OnCollisionStay.</param>
|
||||
private void CollisionEvent(JointType joint, Collision collision, GrabCollider.CollisionState state)
|
||||
{
|
||||
bool isCollision = state != GrabCollider.CollisionState.end;
|
||||
Rigidbody rigidbody = collision.rigidbody;
|
||||
GrabManager.GetFirstHandGrabbableFromParent(collision.collider.gameObject, out HandGrabInteractable grabbable);
|
||||
if (collision.rigidbody == null && (grabbable == null || grabbable != null && !grabbable.enabled)) { return; }
|
||||
|
||||
if ((rigidbody == null || rigidbody.isKinematic) && grabbable != null && grabbable.forceMovable)
|
||||
{
|
||||
if (isCollision)
|
||||
{
|
||||
UpdateMovableHits(joint, grabbable);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveMovableHits(joint, grabbable);
|
||||
}
|
||||
}
|
||||
else if ((rigidbody != null && rigidbody.isKinematic) || (grabbable != null && !grabbable.forceMovable))
|
||||
{
|
||||
UpdateImmovableHIts(joint, collision.collider, isCollision);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateMovableHits(JointType joint, HandGrabInteractable grabbable)
|
||||
{
|
||||
int index = movableHits.FindIndex(x => x.grabbable == grabbable);
|
||||
if (index != -1)
|
||||
{
|
||||
MovableHitInfo moveable = movableHits[index];
|
||||
moveable.AddJoint(joint, jointsCollider[(int)joint].transform.position - grabbable.transform.position);
|
||||
movableHits[index] = moveable;
|
||||
}
|
||||
else
|
||||
{
|
||||
MovableHitInfo moveable = new MovableHitInfo(grabbable, joint, jointsCollider[(int)joint].transform.position - grabbable.transform.position);
|
||||
movableHits.Add(moveable);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMovableHits(JointType joint, HandGrabInteractable grabbable)
|
||||
{
|
||||
int index = movableHits.FindIndex(x => x.grabbable == grabbable);
|
||||
if (index != -1)
|
||||
{
|
||||
MovableHitInfo movable = movableHits[index];
|
||||
movable.RemoveJoint(joint);
|
||||
if (movable.jointHitInfos.Count == 0)
|
||||
{
|
||||
movableHits.Remove(movable);
|
||||
}
|
||||
else
|
||||
{
|
||||
movableHits[index] = movable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateImmovableHIts(JointType joint, Collider collider, bool isCollision)
|
||||
{
|
||||
GrabCollider grabCollider = jointsCollider[(int)joint];
|
||||
grabCollider.IsCollision = isCollision;
|
||||
|
||||
if (isCollision && !immovableHits.ContainsKey(grabCollider))
|
||||
{
|
||||
immovableHits.Add(grabCollider, collider);
|
||||
}
|
||||
else if (!isCollision && immovableHits.ContainsKey(grabCollider))
|
||||
{
|
||||
immovableHits.Remove(grabCollider);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9b8d210c92da6a49ac85755f7b15cbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to manage all Grabbers and Grabbables.
|
||||
/// </summary>
|
||||
public static class GrabManager
|
||||
{
|
||||
private static List<IGrabber> m_GrabberRegistry = new List<IGrabber>();
|
||||
public static IReadOnlyList<HandGrabInteractor> handGrabbers => m_GrabberRegistry.OfType<HandGrabInteractor>().ToList().AsReadOnly();
|
||||
|
||||
private static List<IGrabbable> m_GrabbableRegistry = new List<IGrabbable>();
|
||||
public static IReadOnlyList<HandGrabInteractable> handGrabbables => m_GrabbableRegistry.OfType<HandGrabInteractable>().ToList().AsReadOnly();
|
||||
|
||||
#region IGrabber
|
||||
/// <summary>
|
||||
/// Register the grabber in the grabber registry.
|
||||
/// </summary>
|
||||
/// <param name="grabber">The grabber to register.</param>
|
||||
/// <returns>True if the grabber is successfully registered; otherwise, false.</returns>
|
||||
public static bool RegisterGrabber(IGrabber grabber)
|
||||
{
|
||||
if (!m_GrabberRegistry.Contains(grabber))
|
||||
{
|
||||
m_GrabberRegistry.Add(grabber);
|
||||
}
|
||||
return m_GrabberRegistry.Contains(grabber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the grabber from the grabber registry.
|
||||
/// </summary>
|
||||
/// <param name="grabber">The grabber to remove.</param>
|
||||
/// <returns>True if the grabber is successfully removed; otherwise, false.</returns>
|
||||
public static bool UnregisterGrabber(IGrabber grabber)
|
||||
{
|
||||
if (m_GrabberRegistry.Contains(grabber))
|
||||
{
|
||||
m_GrabberRegistry.Remove(grabber);
|
||||
}
|
||||
return !m_GrabberRegistry.Contains(grabber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first hand grabber component found in the child hierarchy of the GameObject.
|
||||
/// </summary>
|
||||
/// <param name="target">The target whose child hierarchy to search.</param>
|
||||
/// <param name="grabber">The output parameter to store the first hand grabber component found.</param>
|
||||
/// <returns>True if a hand grabber component is found; otherwise, false.</returns>
|
||||
public static bool GetFirstHandGrabberFromChild(GameObject target, out HandGrabInteractor grabber)
|
||||
{
|
||||
grabber = TopDownFind<HandGrabInteractor>(target.transform);
|
||||
return grabber != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first hand grabber component found in the parent hierarchy of the GameObject.
|
||||
/// </summary>
|
||||
/// <param name="target">The target whose parent hierarchy to search.</param>
|
||||
/// <param name="grabber">The output parameter to store the first hand grabber component found.</param>
|
||||
/// <returns>True if a hand grabber component is found; otherwise, false.</returns>
|
||||
public static bool GetFirstHandGrabberFromParent(GameObject target, out HandGrabInteractor grabber)
|
||||
{
|
||||
grabber = BottomUpFind<HandGrabInteractor>(target.transform);
|
||||
return grabber != null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GrabInteractable
|
||||
/// <summary>
|
||||
/// Register the grabbable in the grabbable registry.
|
||||
/// </summary>
|
||||
/// <param name="grabbable">The grabbable to register.</param>
|
||||
/// <returns>True if the grabbable is successfully registered; otherwise, false.</returns>
|
||||
public static bool RegisterGrabbable(IGrabbable grabbable)
|
||||
{
|
||||
if (!m_GrabbableRegistry.Contains(grabbable))
|
||||
{
|
||||
m_GrabbableRegistry.Add(grabbable);
|
||||
}
|
||||
return m_GrabbableRegistry.Contains(grabbable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the grabbable from the grabbable registry.
|
||||
/// </summary>
|
||||
/// <param name="grabbable">The grabbable to remove.</param>
|
||||
/// <returns>True if the grabbable is successfully removed; otherwise, false.</returns>
|
||||
public static bool UnregisterGrabbable(IGrabbable grabbable)
|
||||
{
|
||||
if (m_GrabbableRegistry.Contains(grabbable))
|
||||
{
|
||||
m_GrabbableRegistry.Remove(grabbable);
|
||||
}
|
||||
return !m_GrabbableRegistry.Contains(grabbable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first hand grabbable component found in the child hierarchy of the GameObject.
|
||||
/// </summary>
|
||||
/// <param name="target">The target whose child hierarchy to search.</param>
|
||||
/// <param name="grabbable">The output parameter to store the first hand grabbable component found.</param>
|
||||
/// <returns>True if a hand grabbable component is found; otherwise, false.</returns>
|
||||
public static bool GetFirstHandGrabbableFromChild(GameObject target, out HandGrabInteractable grabbable)
|
||||
{
|
||||
grabbable = TopDownFind<HandGrabInteractable>(target.transform);
|
||||
return grabbable != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first hand grabbable component found in the parent hierarchy of the GameObject.
|
||||
/// </summary>
|
||||
/// <param name="target">The target whose parent hierarchy to search.</param>
|
||||
/// <param name="grabbable">The output parameter to store the first hand grabbable component found.</param>
|
||||
/// <returns>True if a hand grabbable component is found; otherwise, false.</returns>
|
||||
public static bool GetFirstHandGrabbableFromParent(GameObject target, out HandGrabInteractable grabbable)
|
||||
{
|
||||
grabbable = BottomUpFind<HandGrabInteractable>(target.transform);
|
||||
return grabbable != null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Find available components from self to children nodes.
|
||||
/// </summary>
|
||||
/// <param name="transform">The transform of the gameobject.</param>
|
||||
/// <returns>Value for available component.</returns>
|
||||
private static T TopDownFind<T>(Transform transform) where T : Component
|
||||
{
|
||||
T component = transform.GetComponent<T>();
|
||||
if (component != null)
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
if (transform.childCount > 0)
|
||||
{
|
||||
for (int i = 0; i < transform.childCount; i++)
|
||||
{
|
||||
T childComponent = TopDownFind<T>(transform.GetChild(i));
|
||||
if (childComponent != null)
|
||||
{
|
||||
return childComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find available components from self to parent node.
|
||||
/// </summary>
|
||||
/// <param name="transform">The transform of the gameobject.</param>
|
||||
/// <returns>Value for available component.</returns>
|
||||
private static T BottomUpFind<T>(Transform transform) where T : Component
|
||||
{
|
||||
T component = transform.GetComponent<T>();
|
||||
if (component != null)
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
if (transform.parent != null)
|
||||
{
|
||||
return BottomUpFind<T>(transform.parent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 252950eac28fb1f4cb1eae8e653f92ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,120 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the WaveVR SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// The class is designed to record all grab poses for all hand Grabbables.
|
||||
/// </summary>
|
||||
public class GrabPoseBinder : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// This struct records the grab pose for grabbable object.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private struct GrabPoseBindFormat
|
||||
{
|
||||
[SerializeField]
|
||||
public string grabbableName;
|
||||
[SerializeField]
|
||||
public List<GrabPose> grabPoses;
|
||||
|
||||
public GrabPoseBindFormat(string in_GrabbableName, List<GrabPose> in_GrabPoses)
|
||||
{
|
||||
grabbableName = in_GrabbableName;
|
||||
grabPoses = in_GrabPoses;
|
||||
}
|
||||
|
||||
public GrabPoseBindFormat Identity => new GrabPoseBindFormat(string.Empty, new List<GrabPose>());
|
||||
|
||||
public void Update(List<GrabPose> grabPoses)
|
||||
{
|
||||
this.grabPoses.Clear();
|
||||
this.grabPoses.AddRange(grabPoses);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
grabbableName = string.Empty;
|
||||
grabPoses.Clear();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is GrabPoseBindFormat grabPoseBindFormat &&
|
||||
grabbableName == grabPoseBindFormat.grabbableName &&
|
||||
grabPoses == grabPoseBindFormat.grabPoses;
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return grabbableName.GetHashCode() ^ grabPoses.GetHashCode();
|
||||
}
|
||||
public static bool operator ==(GrabPoseBindFormat source, GrabPoseBindFormat target) => source.Equals(target);
|
||||
public static bool operator !=(GrabPoseBindFormat source, GrabPoseBindFormat target) => !(source == target);
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private List<GrabPoseBindFormat> m_BindingInfos = new List<GrabPoseBindFormat>();
|
||||
|
||||
/// <summary>
|
||||
/// Update the binding information for each hand grabbable object.
|
||||
/// </summary>
|
||||
public void UpdateBindingInfos()
|
||||
{
|
||||
m_BindingInfos.Clear();
|
||||
foreach (HandGrabInteractable grabbable in GrabManager.handGrabbables)
|
||||
{
|
||||
m_BindingInfos.Add(new GrabPoseBindFormat(grabbable.name, grabbable.grabPoses));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the binding information.
|
||||
/// </summary>
|
||||
/// <returns>True if storage is successful; otherwise, false.</returns>
|
||||
public bool StorageData()
|
||||
{
|
||||
if (m_BindingInfos.Count == 0) { return false; }
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
EditorUtility.SetDirty(this);
|
||||
AssetDatabase.SaveAssets();
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds grab poses associated with the specified hand grabbable object.
|
||||
/// </summary>
|
||||
/// <param name="grabbable">The hand grabbable object to search for.</param>
|
||||
/// <param name="grabPoses">The output parameter to store the found grab poses.</param>
|
||||
/// <returns>True if grab poses are found for the grabbable object; otherwise, false.</returns>
|
||||
public bool FindGrabPosesWithGrabbable(HandGrabInteractable grabbable, out List<GrabPose> grabPoses)
|
||||
{
|
||||
grabPoses = new List<GrabPose>();
|
||||
GrabPoseBindFormat bindingInfo = m_BindingInfos.Find(x => x.grabbableName == grabbable.name);
|
||||
if (bindingInfo != null)
|
||||
{
|
||||
grabPoses = bindingInfo.grabPoses;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53503199deedf444e84f1714b700737d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the WaveVR SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// The class is designed to update the grab poses of all hand Grabbables.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public class GrabbablePoseRecorder
|
||||
{
|
||||
private static readonly string filepath = "Assets/GrabablePoseRecording.asset";
|
||||
private static readonly string metaFilepath = filepath + ".meta";
|
||||
private static bool IsFileExist => File.Exists(filepath);
|
||||
|
||||
static GrabbablePoseRecorder()
|
||||
{
|
||||
EditorApplication.playModeStateChanged += ApplyChanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply changes to grab poses when entering edit mode.
|
||||
/// </summary>
|
||||
/// <param name="state">The state of the play mode.</param>
|
||||
private static void ApplyChanges(PlayModeStateChange state)
|
||||
{
|
||||
if (IsFileExist && state == PlayModeStateChange.EnteredEditMode)
|
||||
{
|
||||
GrabPoseBinder binder = AssetDatabase.LoadAssetAtPath<GrabPoseBinder>(filepath);
|
||||
if (binder != null)
|
||||
{
|
||||
HandGrabInteractable[] grabbables = Object.FindObjectsOfType<HandGrabInteractable>();
|
||||
foreach (var grabbable in grabbables)
|
||||
{
|
||||
if (binder.FindGrabPosesWithGrabbable(grabbable, out List<GrabPose> updatedGrabPose))
|
||||
{
|
||||
for (int i = 0; i < updatedGrabPose.Count; i++)
|
||||
{
|
||||
GrabPose grabPose = updatedGrabPose[i];
|
||||
GrabPose oldGrabPose = grabbable.grabPoses.Find(x => x.grabPoseName == grabPose.grabPoseName);
|
||||
if (oldGrabPose != null)
|
||||
{
|
||||
grabPose.indicator.target = oldGrabPose.indicator.target;
|
||||
}
|
||||
updatedGrabPose[i] = grabPose;
|
||||
}
|
||||
grabbable.grabPoses.Clear();
|
||||
grabbable.grabPoses.AddRange(updatedGrabPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
File.Delete(filepath);
|
||||
File.Delete(metaFilepath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves changes to grab pose bindings.
|
||||
/// </summary>
|
||||
public static void SaveChanges()
|
||||
{
|
||||
if (!IsFileExist)
|
||||
{
|
||||
GenerateAsset();
|
||||
}
|
||||
else
|
||||
{
|
||||
GrabPoseBinder binder = AssetDatabase.LoadAssetAtPath<GrabPoseBinder>(filepath);
|
||||
if (binder != null)
|
||||
{
|
||||
binder.UpdateBindingInfos();
|
||||
binder.StorageData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new asset for storing grab pose bindings.
|
||||
/// </summary>
|
||||
private static void GenerateAsset()
|
||||
{
|
||||
GrabPoseBinder binder = ScriptableObject.CreateInstance<GrabPoseBinder>();
|
||||
binder.UpdateBindingInfos();
|
||||
AssetDatabase.CreateAsset(binder, filepath);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9119682127e09314ca250470f13db0f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,344 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to implement IHandGrabbable, allowing objects to be grabbed.
|
||||
/// </summary>
|
||||
public class HandGrabInteractable : MonoBehaviour, IHandGrabbable
|
||||
{
|
||||
#region Interface Implement
|
||||
private HandGrabInteractor m_Grabber = null;
|
||||
public IGrabber grabber => m_Grabber;
|
||||
|
||||
public bool isGrabbed => m_Grabber != null;
|
||||
|
||||
[SerializeField]
|
||||
private bool m_IsGrabbable = true;
|
||||
public bool isGrabbable { get { return m_IsGrabbable; } set { m_IsGrabbable = value; } }
|
||||
|
||||
[SerializeField]
|
||||
private bool m_ForceMovable = true;
|
||||
public bool forceMovable { get { return m_ForceMovable; } set { m_ForceMovable = value; } }
|
||||
|
||||
[SerializeField]
|
||||
private FingerRequirement m_FingerRequirement;
|
||||
public FingerRequirement fingerRequirement => m_FingerRequirement;
|
||||
#endregion
|
||||
|
||||
#region Public State
|
||||
[SerializeField]
|
||||
private List<GrabPose> m_GrabPoses = new List<GrabPose>();
|
||||
public List<GrabPose> grabPoses => m_GrabPoses;
|
||||
|
||||
public GrabPose bestGrabPose => bestGrabPoseId != -1 ? m_GrabPoses[bestGrabPoseId] : GrabPose.Identity;
|
||||
#endregion
|
||||
|
||||
[SerializeField]
|
||||
private bool m_ShowAllIndicator = false;
|
||||
private List<Collider> allColliders = new List<Collider>();
|
||||
private HandGrabInteractor closestGrabber = null;
|
||||
private int bestGrabPoseId = -1;
|
||||
private OnBeginGrabbed onBeginGrabbed;
|
||||
private OnEndGrabbed onEndGrabbed;
|
||||
|
||||
#region MonoBehaviour
|
||||
private void Awake()
|
||||
{
|
||||
allColliders.AddRange(transform.GetComponentsInChildren<Collider>(true));
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
GrabManager.RegisterGrabbable(this);
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
GrabManager.UnregisterGrabbable(this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Interface
|
||||
/// <summary>
|
||||
/// Set the grabber for the hand grabbable object.
|
||||
/// </summary>
|
||||
/// <param name="grabber">The grabber to set.</param>
|
||||
public void SetGrabber(IGrabber grabber)
|
||||
{
|
||||
if (grabber is HandGrabInteractor)
|
||||
{
|
||||
HandGrabInteractor handGrabber = grabber as HandGrabInteractor;
|
||||
m_Grabber = handGrabber;
|
||||
UpdateBestGrabPose(handGrabber.isLeft, handGrabber.handGrabState.GetJointPose(JointType.Palm));
|
||||
onBeginGrabbed?.Invoke(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Grabber = null;
|
||||
bestGrabPoseId = -1;
|
||||
onEndGrabbed?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable/Disable indicators. If enabled, display the closest indicator based on grabber position.
|
||||
/// </summary>
|
||||
/// <param name="enable">True to show the indicator, false to hide it.</param>
|
||||
/// <param name="grabber">The grabber for which to show or hide this indicator.</param>
|
||||
public void ShowIndicator(bool enable, HandGrabInteractor grabber)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
closestGrabber = grabber;
|
||||
if (m_ShowAllIndicator)
|
||||
{
|
||||
ShowAllIndicator(grabber.isLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = FindBestGrabPose(grabber.isLeft, grabber.handGrabState.GetJointPose((int)JointType.Palm));
|
||||
ShowIndicatorByIndex(index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (closestGrabber == grabber)
|
||||
{
|
||||
closestGrabber = null;
|
||||
ShowIndicatorByIndex(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the shortest distance between the grabber and the grabbable and convert it into a score based on grabDistance.
|
||||
/// </summary>
|
||||
/// <param name="grabberPos">The current pose of grabber.</param>
|
||||
/// <param name="grabDistance">The maximum grab distance between the grabber and the grabbable object.</param>
|
||||
/// <returns>The score represents the distance between the grabber and the grabbable.</returns>
|
||||
public float CalculateDistanceScore(Vector3 grabberPos, float grabDistance = 0.03f)
|
||||
{
|
||||
if (!isGrabbable || isGrabbed) { return 0; }
|
||||
Vector3 closestPoint = GetClosestPoint(grabberPos);
|
||||
float distacne = Vector3.Distance(grabberPos, closestPoint);
|
||||
return distacne > grabDistance ? 0 : 1 - (distacne / grabDistance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a listener for the event triggered when the grabbable object is grabbed.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when the grabbable object is grabbed.</param>
|
||||
public void AddBeginGrabbedListener(OnBeginGrabbed handler)
|
||||
{
|
||||
onBeginGrabbed += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a listener for the event triggered when the grabbable object is grabbed.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||
public void RemoveBeginGrabbedListener(OnBeginGrabbed handler)
|
||||
{
|
||||
onBeginGrabbed -= handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a listener for the event triggered when the grabbable object is released.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when the grabbable object is released.</param>
|
||||
public void AddEndGrabbedListener(OnEndGrabbed handler)
|
||||
{
|
||||
onEndGrabbed += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a listener for the event triggered when the grabbable object is released.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||
public void RemoveEndGrabbedListener(OnEndGrabbed handler)
|
||||
{
|
||||
onEndGrabbed -= handler;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Generate all indicators and calculate grab offsets.
|
||||
/// </summary>
|
||||
private void Initialize()
|
||||
{
|
||||
for (int i = 0; i < m_GrabPoses.Count; i++)
|
||||
{
|
||||
if (m_GrabPoses[i].indicator.enableIndicator || m_ShowAllIndicator)
|
||||
{
|
||||
if (m_GrabPoses[i].indicator.NeedGenerateIndicator())
|
||||
{
|
||||
AutoGenerateIndicator(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
GrabPose grabPose = m_GrabPoses[i];
|
||||
grabPose.indicator.CalculateGrabOffset(transform);
|
||||
m_GrabPoses[i] = grabPose;
|
||||
}
|
||||
}
|
||||
}
|
||||
ShowIndicatorByIndex(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically generate an indicator by the index of the grab pose.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the grab pose.</param>
|
||||
private void AutoGenerateIndicator(int index)
|
||||
{
|
||||
AutoGenIndicator autoGenIndicator = new GameObject($"Indicator {index}", typeof(AutoGenIndicator)).GetComponent<AutoGenIndicator>();
|
||||
|
||||
GrabPose grabPose = m_GrabPoses[index];
|
||||
// The grabPose.grabOffset was calculated as the position of the object minus the position of the hand,
|
||||
// so inverse calculation is needed here to infer the position of the hand.
|
||||
Vector3 offset = transform.rotation * Quaternion.Inverse(grabPose.grabOffset.targetRotation) * -grabPose.grabOffset.position;
|
||||
Vector3 defaultPosition = transform.position + offset;
|
||||
Vector3 closestPoint = GetClosestPoint(defaultPosition);
|
||||
autoGenIndicator.SetPose(closestPoint, offset.normalized);
|
||||
grabPose.indicator.Update(true, true, autoGenIndicator.gameObject);
|
||||
grabPose.indicator.CalculateGrabOffset(transform);
|
||||
m_GrabPoses[index] = grabPose;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the point closest to the source position.
|
||||
/// </summary>
|
||||
/// <param name="sourcePos">The position of source.</param>
|
||||
/// <returns>The position which closest to the source position.</returns>
|
||||
private Vector3 GetClosestPoint(Vector3 sourcePos)
|
||||
{
|
||||
Vector3 closestPoint = Vector3.zero;
|
||||
float shortDistance = float.MaxValue;
|
||||
foreach (var collider in allColliders)
|
||||
{
|
||||
Vector3 closePoint = collider.ClosestPointOnBounds(sourcePos);
|
||||
float distance = Vector3.Distance(sourcePos, closePoint);
|
||||
if (collider.bounds.Contains(closePoint))
|
||||
{
|
||||
Vector3 direction = (closePoint - sourcePos).normalized;
|
||||
RaycastHit[] hits = Physics.RaycastAll(sourcePos, direction, distance);
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
if (hit.collider == collider)
|
||||
{
|
||||
float hitDistnace = Vector3.Distance(sourcePos, hit.point);
|
||||
if (distance > hitDistnace)
|
||||
{
|
||||
distance = hitDistnace;
|
||||
closePoint = hit.point;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shortDistance > distance)
|
||||
{
|
||||
shortDistance = distance;
|
||||
closestPoint = closePoint;
|
||||
}
|
||||
}
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the best grab pose for the grabber and updates the bestGrabPoseId.
|
||||
/// </summary>
|
||||
/// <param name="isLeft">Whether the grabber is the left hand.</param>
|
||||
/// <param name="grabberPose">The pose of the grabber.</param>
|
||||
/// <returns>True if a best grab pose is found; otherwise, false.</returns>
|
||||
private bool UpdateBestGrabPose(bool isLeft, Pose grabberPose)
|
||||
{
|
||||
int index = FindBestGrabPose(isLeft, grabberPose);
|
||||
if (index != -1 && index < m_GrabPoses.Count)
|
||||
{
|
||||
bestGrabPoseId = index;
|
||||
return true;
|
||||
}
|
||||
index = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the best grab pose for the grabber.
|
||||
/// </summary>
|
||||
/// <param name="isLeft">Whether the grabber is the left hand.</param>
|
||||
/// <param name="grabberPose">The pose of the grabber.</param>
|
||||
/// <returns>The index of the best grab pose among the grab poses.</returns>
|
||||
private int FindBestGrabPose(bool isLeft, Pose grabberPose)
|
||||
{
|
||||
int index = -1;
|
||||
float maxDot = float.MinValue;
|
||||
Vector3 currentDirection = grabberPose.position - transform.position;
|
||||
for (int i = 0; i < m_GrabPoses.Count; i++)
|
||||
{
|
||||
if (m_GrabPoses[i].isLeft == isLeft)
|
||||
{
|
||||
Vector3 grabDirection = transform.rotation * Quaternion.Inverse(m_GrabPoses[i].grabOffset.targetRotation) * -m_GrabPoses[i].grabOffset.position;
|
||||
float dot = Vector3.Dot(currentDirection.normalized, grabDirection.normalized);
|
||||
if (dot > maxDot)
|
||||
{
|
||||
maxDot = dot;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the indicator corresponding to the specified index and hides others.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the indicator to show.</param>
|
||||
private void ShowIndicatorByIndex(int index)
|
||||
{
|
||||
foreach (var grabPose in m_GrabPoses)
|
||||
{
|
||||
grabPose.indicator.SetActive(false);
|
||||
}
|
||||
if (index >= 0 && index < m_GrabPoses.Count &&
|
||||
m_GrabPoses[index].indicator.enableIndicator)
|
||||
{
|
||||
m_GrabPoses[index].indicator.UpdatePositionAndRotation(transform);
|
||||
m_GrabPoses[index].indicator.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show all indicators corresponding to the specified hand side and hides others.
|
||||
/// </summary>
|
||||
/// <param name="isLeft">Whether the hand side is left.</param>
|
||||
private void ShowAllIndicator(bool isLeft)
|
||||
{
|
||||
foreach (var grabPose in m_GrabPoses)
|
||||
{
|
||||
grabPose.indicator.SetActive(false);
|
||||
}
|
||||
foreach (var grabPose in m_GrabPoses)
|
||||
{
|
||||
if (grabPose.isLeft == isLeft)
|
||||
{
|
||||
grabPose.indicator.UpdatePositionAndRotation(transform);
|
||||
grabPose.indicator.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1c0e40da1ab9014c89d359be00fffb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,390 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to implement IHandGrabber, allowing objects to grab grabbable objects.
|
||||
/// </summary>
|
||||
public class HandGrabInteractor : MonoBehaviour, IHandGrabber
|
||||
{
|
||||
private enum GrabState
|
||||
{
|
||||
None,
|
||||
Hover,
|
||||
Grabbing,
|
||||
};
|
||||
|
||||
#region Public States
|
||||
private HandGrabInteractable m_Grabbable = null;
|
||||
public IGrabbable grabbable => m_Grabbable;
|
||||
public bool isGrabbing => m_Grabbable != null;
|
||||
|
||||
[SerializeField]
|
||||
private Handedness m_Handedness = Handedness.Left;
|
||||
public Handedness handedness => m_Handedness;
|
||||
|
||||
private HandGrabState m_HandGrabState = null;
|
||||
public HandGrabState handGrabState => m_HandGrabState;
|
||||
|
||||
[SerializeField]
|
||||
private float m_GrabDistance = 0.03f;
|
||||
public float grabDistance { get { return m_GrabDistance; } set { m_GrabDistance = value; } }
|
||||
|
||||
[SerializeField]
|
||||
private bool m_EnableCollider = true;
|
||||
public bool enableCollider
|
||||
{
|
||||
get { return m_EnableCollider; }
|
||||
set
|
||||
{
|
||||
m_EnableCollider = value;
|
||||
if (colliderManager != null)
|
||||
{
|
||||
colliderManager.EnableCollider(m_EnableCollider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool isLeft => handedness == Handedness.Left;
|
||||
#endregion
|
||||
|
||||
[SerializeField]
|
||||
private GrabColliderManager colliderManager;
|
||||
|
||||
private readonly float MinGrabScore = 0.25f;
|
||||
private readonly float MinDistanceScore = 0.25f;
|
||||
private HandGrabInteractable currentCaidate = null;
|
||||
private GrabPose grabPose = GrabPose.Identity;
|
||||
private GrabState m_State = GrabState.None;
|
||||
private Pose[] fingerTipPoses => new Pose[]
|
||||
{
|
||||
m_HandGrabState.GetJointPose(JointType.Thumb_Tip),
|
||||
m_HandGrabState.GetJointPose(JointType.Index_Tip),
|
||||
m_HandGrabState.GetJointPose(JointType.Middle_Tip),
|
||||
m_HandGrabState.GetJointPose(JointType.Ring_Tip),
|
||||
m_HandGrabState.GetJointPose(JointType.Pinky_Tip),
|
||||
};
|
||||
private Pose palmPose => m_HandGrabState.GetJointPose(JointType.Palm);
|
||||
private Pose[] frozenPoses = new Pose[(int)JointType.Count];
|
||||
private bool isFrozen = false;
|
||||
private OnBeginGrab beginGrabHandler;
|
||||
private OnEndGrab endGrabHandler;
|
||||
|
||||
#region MonoBehaviour
|
||||
private void Awake()
|
||||
{
|
||||
m_HandGrabState = new HandGrabState(isLeft);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (colliderManager != null)
|
||||
{
|
||||
colliderManager.EnableCollider(m_EnableCollider);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
GrabManager.RegisterGrabber(this);
|
||||
if (colliderManager != null)
|
||||
{
|
||||
colliderManager.AddImmovableCollisionListener(FreezeHandPose);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
GrabManager.UnregisterGrabber(this);
|
||||
if (colliderManager != null)
|
||||
{
|
||||
colliderManager.RemoveImmovableCollisionListener(FreezeHandPose);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
m_HandGrabState.UpdateState();
|
||||
if (isFrozen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_State != GrabState.Grabbing)
|
||||
{
|
||||
FindCandidate();
|
||||
}
|
||||
|
||||
switch (m_State)
|
||||
{
|
||||
case GrabState.None:
|
||||
NoneUpdate();
|
||||
break;
|
||||
case GrabState.Hover:
|
||||
HoverUpdate();
|
||||
break;
|
||||
case GrabState.Grabbing:
|
||||
GrabbingUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Interface
|
||||
/// <summary>
|
||||
/// Get the current joint pose of the grabber.
|
||||
/// </summary>
|
||||
/// <param name="jointId">The id of the joint for which to get the pose.</param>
|
||||
/// <returns>The current pose of the specified joint.</returns>
|
||||
public Pose GetCurrentJointPose(int jointId)
|
||||
{
|
||||
if (isFrozen)
|
||||
{
|
||||
return frozenPoses[jointId];
|
||||
}
|
||||
return m_HandGrabState.GetJointPose(jointId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the rotation of the joint in the grab pose.
|
||||
/// </summary>
|
||||
/// <param name="jointId">The id of the joint for which to get the rotation.</param>
|
||||
/// <returns>The rotation of the joint in the grab pose.</returns>
|
||||
public bool GetGrabPoseJointRotation(int jointId, out Quaternion rotation)
|
||||
{
|
||||
rotation = Quaternion.identity;
|
||||
if (m_Grabbable == null) { return false; }
|
||||
if (jointId >= 0 && grabPose.recordedGrabRotations.Length > jointId)
|
||||
{
|
||||
rotation = grabPose.recordedGrabRotations[jointId];
|
||||
return true;
|
||||
}
|
||||
else if (grabPose.handGrabGesture != HandGrabGesture.Identity)
|
||||
{
|
||||
rotation = m_HandGrabState.GetDefaultJointRotationInGesture(grabPose.handGrabGesture, jointId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the specific joint is necessary for grabbing.
|
||||
/// </summary>
|
||||
/// <param name="joint">JointType of the specified joint.</param>
|
||||
/// <returns>Return true if this joint is needed for grabbing, otherwise false.</returns>
|
||||
public bool IsRequiredJoint(JointType joint)
|
||||
{
|
||||
if (m_Grabbable != null)
|
||||
{
|
||||
GetJointIndex(joint, out int group, out _);
|
||||
switch (group)
|
||||
{
|
||||
case 2: return m_Grabbable.fingerRequirement.thumb == GrabRequirement.Required;
|
||||
case 3: return m_Grabbable.fingerRequirement.index == GrabRequirement.Required;
|
||||
case 4: return m_Grabbable.fingerRequirement.middle == GrabRequirement.Required;
|
||||
case 5: return m_Grabbable.fingerRequirement.ring == GrabRequirement.Required;
|
||||
case 6: return m_Grabbable.fingerRequirement.pinky == GrabRequirement.Required;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a listener for the event triggered when the grabber begins grabbing.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when the grabber begins grabbing.</param>
|
||||
public void AddBeginGrabListener(OnBeginGrab handler)
|
||||
{
|
||||
beginGrabHandler += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a listener for the event triggered when the grabber begins grabbing.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||
public void RemoveBeginGrabListener(OnBeginGrab handler)
|
||||
{
|
||||
beginGrabHandler -= handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a listener for the event triggered when the grabber ends grabbing.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be called when the grabber ends grabbing.</param>
|
||||
public void AddEndGrabListener(OnEndGrab handler)
|
||||
{
|
||||
endGrabHandler += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a listener for the event triggered when the grabber ends grabbing.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to be removed from the event listeners.</param>
|
||||
public void RemoveEndGrabListener(OnEndGrab handler)
|
||||
{
|
||||
endGrabHandler -= handler;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Find the candidate grabbable object for grabber.
|
||||
/// </summary>
|
||||
private void FindCandidate()
|
||||
{
|
||||
float distanceScore = float.MinValue;
|
||||
if (GetClosestGrabbable(m_GrabDistance, out HandGrabInteractable grabbable, out float score) && score > distanceScore)
|
||||
{
|
||||
distanceScore = score;
|
||||
currentCaidate = grabbable;
|
||||
}
|
||||
|
||||
if (currentCaidate != null)
|
||||
{
|
||||
float grabScore = Grab.CalculateHandGrabScore(this, currentCaidate);
|
||||
if (distanceScore < MinDistanceScore || grabScore < MinGrabScore)
|
||||
{
|
||||
currentCaidate = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the closest grabbable object for grabber.
|
||||
/// </summary>
|
||||
/// <param name="grabDistance">The maximum grab distance between the grabber and the grabbable object.</param>
|
||||
/// <param name="grabbable">The closest grabbable object.</param>
|
||||
/// <param name="maxScore">The maximum score indicating the closeness of the grabbable object.</param>
|
||||
/// <returns>True if a grabbable object is found within the grab distance; otherwise, false.</returns>
|
||||
private bool GetClosestGrabbable(float grabDistance, out HandGrabInteractable grabbable, out float maxScore)
|
||||
{
|
||||
grabbable = null;
|
||||
maxScore = 0f;
|
||||
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
||||
{
|
||||
interactable.ShowIndicator(false, this);
|
||||
foreach (Pose fingerTipPose in fingerTipPoses)
|
||||
{
|
||||
float distanceScore = interactable.CalculateDistanceScore(fingerTipPose.position, grabDistance);
|
||||
if (distanceScore > maxScore)
|
||||
{
|
||||
maxScore = distanceScore;
|
||||
grabbable = interactable;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (grabbable != null)
|
||||
{
|
||||
grabbable.ShowIndicator(true, this);
|
||||
}
|
||||
return grabbable != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the state to GrabState.Hover if a candidate is found.
|
||||
/// </summary>
|
||||
private void NoneUpdate()
|
||||
{
|
||||
if (currentCaidate != null)
|
||||
{
|
||||
m_State = GrabState.Hover;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the state and related information when the grabber begins grabbing the grabbable.
|
||||
/// </summary>
|
||||
private void HoverUpdate()
|
||||
{
|
||||
if (currentCaidate == null)
|
||||
{
|
||||
m_State = GrabState.None;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Grab.HandBeginGrab(this, currentCaidate))
|
||||
{
|
||||
m_State = GrabState.Grabbing;
|
||||
|
||||
m_Grabbable = currentCaidate;
|
||||
m_Grabbable.SetGrabber(this);
|
||||
m_Grabbable.ShowIndicator(false, this);
|
||||
grabPose = m_Grabbable.bestGrabPose;
|
||||
if (grabPose == GrabPose.Identity)
|
||||
{
|
||||
Vector3 posOffset = m_Grabbable.transform.position - palmPose.position;
|
||||
Quaternion rotOffset = palmPose.rotation;
|
||||
grabPose.grabOffset = new GrabOffset(m_Grabbable.transform.position, m_Grabbable.transform.rotation, posOffset, rotOffset);
|
||||
}
|
||||
beginGrabHandler?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the position of grabbable object according to the movement of the grabber.
|
||||
/// </summary>
|
||||
private void GrabbingUpdate()
|
||||
{
|
||||
if (Grab.HandDoneGrab(this, m_Grabbable) || !Grab.HandIsGrabbing(this, m_Grabbable))
|
||||
{
|
||||
m_Grabbable.SetGrabber(null);
|
||||
m_Grabbable = null;
|
||||
m_State = GrabState.Hover;
|
||||
endGrabHandler?.Invoke(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Quaternion handRotOffset = palmPose.rotation * Quaternion.Inverse(grabPose.grabOffset.rotation);
|
||||
Vector3 currentPos = palmPose.position + handRotOffset * grabPose.grabOffset.position;
|
||||
Quaternion currentRot = handRotOffset * grabPose.grabOffset.targetRotation;
|
||||
m_Grabbable.transform.SetPositionAndRotation(currentPos, currentRot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Freezes or unfreezes the hand pose.
|
||||
/// </summary>
|
||||
/// <param name="enable">True to freeze the hand pose; False to unfreeze.</param>
|
||||
private void FreezeHandPose(bool enable)
|
||||
{
|
||||
isFrozen = enable;
|
||||
if (isFrozen)
|
||||
{
|
||||
for (int i = 0; i < frozenPoses.Length; i++)
|
||||
{
|
||||
frozenPoses[i] = m_HandGrabState.GetJointPose(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the position of a specific joint.
|
||||
/// </summary>
|
||||
/// <param name="joint">The type of joint to get.</param>
|
||||
/// <param name="position">The reference to store the position of the joint.</param>
|
||||
/// <returns>True if the joint position is successfully retrieved; otherwise, false.</returns>
|
||||
private void GetJointIndex(JointType joint, out int group, out int index)
|
||||
{
|
||||
int jointId = (int)joint + 1;
|
||||
group = 0;
|
||||
index = jointId;
|
||||
|
||||
// palm, wrist, thumb, index, middle, ring, pinky
|
||||
int[] fingerGroup = { 1, 1, 4, 5, 5, 5, 5 };
|
||||
while (index > fingerGroup[group])
|
||||
{
|
||||
index -= fingerGroup[group];
|
||||
group += 1;
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3155365f073fdb45ba7a61887f8cf06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35c93bc2a4bb4334e9fc24c4dbac1b63
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to manage the positions of various joint nodes in the hand model.
|
||||
/// </summary>
|
||||
public class HandMeshManager : MonoBehaviour
|
||||
{
|
||||
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager";
|
||||
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
|
||||
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
|
||||
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
|
||||
|
||||
private enum RootType
|
||||
{
|
||||
Palm = JointType.Palm,
|
||||
Wrist = JointType.Wrist,
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private Handedness m_Handedness;
|
||||
public bool IsLeft { get { return m_Handedness == Handedness.Left; } }
|
||||
[SerializeField]
|
||||
private HandGrabInteractor m_HandGrabber;
|
||||
public HandGrabInteractor HandGrabber { get { return m_HandGrabber; } }
|
||||
[SerializeField]
|
||||
private RootType m_RootJointType;
|
||||
public JointType RootJointType { get { return (JointType)m_RootJointType; } }
|
||||
[SerializeField]
|
||||
private Transform m_HandRootJoint;
|
||||
public Transform HandRootJoint { get { return m_HandRootJoint; } }
|
||||
[SerializeField]
|
||||
private Transform[] m_HandJoints = new Transform[(int)JointType.Count];
|
||||
public Transform[] HandJoints { get { return m_HandJoints; } }
|
||||
|
||||
private const int k_JointCount = 26;
|
||||
private const int k_JointChildCount = 6;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (m_HandGrabber == null)
|
||||
{
|
||||
WARNING("Not to set HandGrabInteractor so it won't stop when colliding with Immovable objects.");
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
HandData hand = CachedHand.Get(IsLeft);
|
||||
if (!hand.isTracked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parentTransform = m_HandRootJoint.parent;
|
||||
|
||||
int rootId = m_RootJointType == RootType.Palm ? 0 : 1;
|
||||
Pose jointPose = GetJointPose(hand, rootId);
|
||||
m_HandJoints[rootId].rotation = parentTransform.rotation * jointPose.rotation;
|
||||
m_HandJoints[rootId].localPosition = jointPose.position;
|
||||
m_HandJoints[rootId].localRotation = jointPose.rotation;
|
||||
|
||||
for (int i = 0; i < m_HandJoints.Length; i++)
|
||||
{
|
||||
if (m_HandJoints[i] == null || i == rootId) { continue; }
|
||||
|
||||
jointPose = GetJointPose(hand, i);
|
||||
m_HandJoints[i].rotation = parentTransform.rotation * jointPose.rotation;
|
||||
if (m_HandGrabber != null && m_HandGrabber.isGrabbing &&
|
||||
m_HandGrabber.GetGrabPoseJointRotation(i, out Quaternion localStaticRot))
|
||||
{
|
||||
Quaternion currentRotation = m_HandJoints[i].rotation;
|
||||
Quaternion maxRotation = m_HandJoints[i].parent.rotation * localStaticRot;
|
||||
if (m_HandGrabber.IsRequiredJoint((JointType)i) ||
|
||||
OverFlex(currentRotation, maxRotation) >= 0 ||
|
||||
FlexAngle(currentRotation, maxRotation) >= 100)
|
||||
{
|
||||
m_HandJoints[i].rotation = maxRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate whether the current rotation exceeds the maximum rotation.
|
||||
/// If the product is greater than 0, it exceeds.
|
||||
/// </summary>
|
||||
/// <param name="currentRot">Current rotation</param>
|
||||
/// <param name="maxRot">Maximum rotation</param>
|
||||
/// <returns>The return value represents the dot product between the cross product of two rotations and the -x axis direction of the current rotation.</returns>
|
||||
private float OverFlex(Quaternion currentRot, Quaternion maxRot)
|
||||
{
|
||||
Vector3 currFwd = currentRot * Vector3.forward;
|
||||
Vector3 maxFwd = maxRot * Vector3.forward;
|
||||
return Vector3.Dot(currentRot * Vector3.left, Vector3.Cross(currFwd, maxFwd));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the angle between the y-axis directions of two rotations.
|
||||
/// </summary>
|
||||
/// <param name="currentRot">Current rotation</param>
|
||||
/// <param name="maxRot">Maximum rotation</param>
|
||||
/// <returns>The return value represents the angle between the up directions of the two rotation</returns>
|
||||
private float FlexAngle(Quaternion currentRot, Quaternion maxRot)
|
||||
{
|
||||
Vector3 currFwd = currentRot * Vector3.up;
|
||||
Vector3 maxFwd = maxRot * Vector3.up;
|
||||
return Mathf.Acos(Vector3.Dot(currFwd, maxFwd) / (currFwd.magnitude * maxFwd.magnitude)) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the pose of the joint based on the joint ID.
|
||||
/// </summary>
|
||||
/// <param name="hand">The current result of hand tracking.</param>
|
||||
/// <param name="jointId">ID of the specified joint.</param>
|
||||
/// <returns>Return the pose of the specified joint.</returns>
|
||||
private Pose GetJointPose(HandData hand, int jointId)
|
||||
{
|
||||
if (m_HandGrabber != null)
|
||||
{
|
||||
return m_HandGrabber.GetCurrentJointPose(jointId);
|
||||
}
|
||||
|
||||
Vector3 jointPosition = Vector3.zero;
|
||||
Quaternion jointRotation = Quaternion.identity;
|
||||
hand.GetJointPosition((JointType)jointId, ref jointPosition);
|
||||
hand.GetJointRotation((JointType)jointId, ref jointRotation);
|
||||
return new Pose(jointPosition, jointRotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the transform of the joint.
|
||||
/// </summary>
|
||||
/// <param name="joint">JointType of the specified joint.</param>
|
||||
/// <param name="jointTransform">Output the transform of the joint.</param>
|
||||
/// <returns>Return true if successfully get the transform, otherwise false.</returns>
|
||||
public bool GetJointTransform(JointType joint, out Transform jointTransform)
|
||||
{
|
||||
jointTransform = null;
|
||||
int id = (int)joint;
|
||||
if (id >= 0 && id < m_HandJoints.Length)
|
||||
{
|
||||
if (m_HandJoints[id] != null)
|
||||
{
|
||||
jointTransform = m_HandJoints[id];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set all joints through gesture.
|
||||
/// </summary>
|
||||
/// <param name="handGrabGesture">The gesture of grabbing.</param>
|
||||
/// <returns>Return true if successfully set the gesture, otherwise false.</returns>
|
||||
public bool SetJointsFromGrabGesture(HandGrabGesture handGrabGesture)
|
||||
{
|
||||
if (m_HandGrabber == null) { return false; }
|
||||
|
||||
for (int i = 0; i < m_HandJoints.Length; i++)
|
||||
{
|
||||
m_HandJoints[i].localRotation = m_HandGrabber.handGrabState.GetDefaultJointRotationInGesture(handGrabGesture, i);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
public void FindJoints()
|
||||
{
|
||||
if (m_HandRootJoint != null)
|
||||
{
|
||||
int fingerJoint = (int)JointType.Thumb_Joint0;
|
||||
if (m_HandRootJoint.childCount == k_JointChildCount)
|
||||
{
|
||||
for (int i = 0; i < m_HandRootJoint.childCount; i++)
|
||||
{
|
||||
Transform child = m_HandRootJoint.GetChild(i);
|
||||
switch (child.childCount)
|
||||
{
|
||||
case 0:
|
||||
if (m_RootJointType == RootType.Palm)
|
||||
{
|
||||
m_HandJoints[(int)JointType.Palm] = m_HandRootJoint;
|
||||
m_HandJoints[(int)JointType.Wrist] = child;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_HandJoints[(int)JointType.Palm] = child;
|
||||
m_HandJoints[(int)JointType.Wrist] = m_HandRootJoint;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
for (int j = 0; j < child.childCount; j++)
|
||||
{
|
||||
m_HandJoints[fingerJoint] = child.GetChild(j);
|
||||
fingerJoint++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fingerJoint = RecursiveFind(fingerJoint, child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_HandRootJoint.childCount == k_JointCount - 1)
|
||||
{
|
||||
Transform child = m_HandRootJoint.GetChild(0);
|
||||
if (m_RootJointType == RootType.Palm)
|
||||
{
|
||||
m_HandJoints[(int)JointType.Palm] = m_HandRootJoint;
|
||||
m_HandJoints[(int)JointType.Wrist] = child;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_HandJoints[(int)JointType.Palm] = child;
|
||||
m_HandJoints[(int)JointType.Wrist] = m_HandRootJoint;
|
||||
}
|
||||
for (int i = 1; i < m_HandRootJoint.childCount; i++)
|
||||
{
|
||||
m_HandJoints[i + 1] = m_HandRootJoint.GetChild(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int RecursiveFind(int jointId, Transform transform)
|
||||
{
|
||||
m_HandJoints[jointId] = transform;
|
||||
jointId++;
|
||||
if (transform.childCount > 0)
|
||||
{
|
||||
for (int i = 0; i < transform.childCount; i++)
|
||||
{
|
||||
jointId = RecursiveFind(jointId, transform.GetChild(i));
|
||||
}
|
||||
}
|
||||
return jointId;
|
||||
}
|
||||
|
||||
public void ClearJoints()
|
||||
{
|
||||
Array.Clear(m_HandJoints, 0, m_HandJoints.Length);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 062de99b5e677f34b8f4f6429d5178cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user