Initialer Upload neues Unity-Projekt
This commit is contained in:
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using Oculus.Interaction.PoseDetection;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace Oculus.Interaction.GrabAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// This Finger API uses the curl value of the fingers to detect if they are grabbing
|
||||
/// </summary>
|
||||
public class FingerPalmGrabAPI : IFingerAPI
|
||||
{
|
||||
// Temporary structure used to pass data to and from native components
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class HandData
|
||||
{
|
||||
private const int NumHandJoints = 24;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NumHandJoints * 7, ArraySubType = UnmanagedType.R4)]
|
||||
private float[] jointValues;
|
||||
private float _rootRotX;
|
||||
private float _rootRotY;
|
||||
private float _rootRotZ;
|
||||
private float _rootRotW;
|
||||
private float _rootPosX;
|
||||
private float _rootPosY;
|
||||
private float _rootPosZ;
|
||||
private int _handedness;
|
||||
|
||||
public HandData()
|
||||
{
|
||||
jointValues = new float[NumHandJoints * 7];
|
||||
}
|
||||
|
||||
public void SetData(IReadOnlyList<Pose> joints, Pose root, Handedness handedness)
|
||||
{
|
||||
Assert.AreEqual(NumHandJoints, joints.Count);
|
||||
int jointValueIndex = 0;
|
||||
for (int jointIndex = 0; jointIndex < NumHandJoints; jointIndex++)
|
||||
{
|
||||
Pose joint = joints[jointIndex];
|
||||
jointValues[jointValueIndex++] = joint.rotation.x;
|
||||
jointValues[jointValueIndex++] = joint.rotation.y;
|
||||
jointValues[jointValueIndex++] = joint.rotation.z;
|
||||
jointValues[jointValueIndex++] = joint.rotation.w;
|
||||
jointValues[jointValueIndex++] = joint.position.x;
|
||||
jointValues[jointValueIndex++] = joint.position.y;
|
||||
jointValues[jointValueIndex++] = joint.position.z;
|
||||
}
|
||||
this._rootRotX = root.rotation.x;
|
||||
this._rootRotY = root.rotation.y;
|
||||
this._rootRotZ = root.rotation.z;
|
||||
this._rootRotW = root.rotation.w;
|
||||
this._rootPosX = root.position.x;
|
||||
this._rootPosY = root.position.y;
|
||||
this._rootPosZ = root.position.z;
|
||||
this._handedness = (int)handedness;
|
||||
}
|
||||
}
|
||||
|
||||
#region DLLImports
|
||||
enum ReturnValue { Success = 0, Failure = -1 };
|
||||
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern int isdk_FingerPalmGrabAPI_Create();
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPalmGrabAPI_UpdateHandData(int handle, [In] HandData data);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPalmGrabAPI_GetFingerIsGrabbing(int handle, HandFinger finger, out bool grabbing);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPalmGrabAPI_GetFingerIsGrabbingChanged(int handle, HandFinger finger, bool targetGrabState, out bool changed);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPalmGrabAPI_GetFingerGrabScore(int handle, HandFinger finger, out float score);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPalmGrabAPI_GetCenterOffset(int handle, out Vector3 score);
|
||||
|
||||
#endregion
|
||||
|
||||
private int apiHandle_ = -1;
|
||||
private HandData handData_;
|
||||
|
||||
public FingerPalmGrabAPI()
|
||||
{
|
||||
handData_ = new HandData();
|
||||
}
|
||||
|
||||
private int GetHandle()
|
||||
{
|
||||
|
||||
if (apiHandle_ == -1)
|
||||
{
|
||||
apiHandle_ = isdk_FingerPalmGrabAPI_Create();
|
||||
Debug.Assert(apiHandle_ != -1, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_Create failed");
|
||||
}
|
||||
|
||||
return apiHandle_;
|
||||
}
|
||||
|
||||
public bool GetFingerIsGrabbing(HandFinger finger)
|
||||
{
|
||||
ReturnValue rv = isdk_FingerPalmGrabAPI_GetFingerIsGrabbing(GetHandle(), finger, out bool grabbing);
|
||||
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_GetFingerIsGrabbing failed");
|
||||
return grabbing;
|
||||
}
|
||||
|
||||
public bool GetFingerIsGrabbingChanged(HandFinger finger, bool targetGrabState)
|
||||
{
|
||||
ReturnValue rv = isdk_FingerPalmGrabAPI_GetFingerIsGrabbingChanged(GetHandle(), finger, targetGrabState, out bool grabbing);
|
||||
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_GetFingerIsGrabbingChanged failed");
|
||||
return grabbing;
|
||||
}
|
||||
|
||||
public float GetFingerGrabScore(HandFinger finger)
|
||||
{
|
||||
ReturnValue rv = isdk_FingerPalmGrabAPI_GetFingerGrabScore(GetHandle(), finger, out float score);
|
||||
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_GetFingerGrabScore failed");
|
||||
return score;
|
||||
}
|
||||
|
||||
public void Update(IHand hand)
|
||||
{
|
||||
if (!hand.GetRootPose(out Pose rootPose))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hand.GetJointPosesFromWrist(out ReadOnlyHandJointPoses poses))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
handData_.SetData(poses, rootPose, hand.Handedness);
|
||||
ReturnValue rv = isdk_FingerPalmGrabAPI_UpdateHandData(GetHandle(), handData_);
|
||||
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_UpdateHandData failed");
|
||||
}
|
||||
|
||||
public Vector3 GetWristOffsetLocal()
|
||||
{
|
||||
ReturnValue rv = isdk_FingerPalmGrabAPI_GetCenterOffset(GetHandle(), out Vector3 center);
|
||||
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_GetCenterOffset failed");
|
||||
return center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df4b2f3ffa2d8a04eb0badc7bfd33a41
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Assertions.Must;
|
||||
|
||||
namespace Oculus.Interaction.GrabAPI
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class HandPinchData
|
||||
{
|
||||
private const int NumHandJoints = 24;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NumHandJoints * 3, ArraySubType = UnmanagedType.R4)]
|
||||
private readonly float[] _jointPositions;
|
||||
|
||||
public HandPinchData()
|
||||
{
|
||||
int floatCount = NumHandJoints * 3;
|
||||
_jointPositions = new float[floatCount];
|
||||
}
|
||||
|
||||
public void SetJoints(IReadOnlyList<Pose> poses)
|
||||
{
|
||||
Assert.AreEqual(NumHandJoints, poses.Count);
|
||||
int floatIndex = 0;
|
||||
for (int jointIndex = 0; jointIndex < NumHandJoints; jointIndex++)
|
||||
{
|
||||
Vector3 position = poses[jointIndex].position;
|
||||
_jointPositions[floatIndex++] = position.x;
|
||||
_jointPositions[floatIndex++] = position.y;
|
||||
_jointPositions[floatIndex++] = position.z;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetJoints(IReadOnlyList<Vector3> positions)
|
||||
{
|
||||
Assert.AreEqual(NumHandJoints, positions.Count);
|
||||
int floatIndex = 0;
|
||||
for (int jointIndex = 0; jointIndex < NumHandJoints; jointIndex++)
|
||||
{
|
||||
Vector3 position = positions[jointIndex];
|
||||
_jointPositions[floatIndex++] = position.x;
|
||||
_jointPositions[floatIndex++] = position.y;
|
||||
_jointPositions[floatIndex++] = position.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This Finger API uses an advanced calculation for the pinch value of the fingers
|
||||
/// to detect if they are grabbing
|
||||
/// </summary>
|
||||
public class FingerPinchGrabAPI : IFingerAPI
|
||||
{
|
||||
enum ReturnValue { Success = 0, Failure = -1 };
|
||||
|
||||
#region DLLImports
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern int isdk_FingerPinchGrabAPI_Create();
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_UpdateHandData(int handle, [In] HandPinchData data);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_UpdateHandWristHMDData(int handle, [In] HandPinchData data, in Vector3 wristForward, in Vector3 hmdForward);
|
||||
|
||||
[DllImport("InteractionSdk", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern bool isdk_FingerPinchGrabAPI_GetString(int handle, [MarshalAs(UnmanagedType.LPStr)] string name, out IntPtr val);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_GetFingerIsGrabbing(int handle, int index, out bool grabbing);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_GetFingerPinchPercent(int handle, int index, out float pinchPercent);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_GetFingerPinchDistance(int handle, int index, out float pinchDistance);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_GetFingerIsGrabbingChanged(int handle, int index, bool targetState, out bool grabbing);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_GetFingerGrabScore(int handle, HandFinger finger, out float outScore);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_GetCenterOffset(int handle, out Vector3 outCenter);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern int isdk_Common_GetVersion(out IntPtr versionStringPtr);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_GetPinchGrabParam(int handle, PinchGrabParam paramId, out float outParam);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_SetPinchGrabParam(int handle, PinchGrabParam paramId, float param);
|
||||
|
||||
[DllImport("InteractionSdk")]
|
||||
private static extern ReturnValue isdk_FingerPinchGrabAPI_IsPinchVisibilityGood(int handle, out bool outVal);
|
||||
#endregion
|
||||
|
||||
private int _fingerPinchGrabApiHandle = -1;
|
||||
private HandPinchData _pinchData = new HandPinchData();
|
||||
|
||||
private IHmd _hmd = null;
|
||||
|
||||
public FingerPinchGrabAPI(IHmd hmd = null)
|
||||
{
|
||||
_hmd = hmd;
|
||||
}
|
||||
|
||||
private int GetHandle()
|
||||
{
|
||||
if (_fingerPinchGrabApiHandle == -1)
|
||||
{
|
||||
_fingerPinchGrabApiHandle = isdk_FingerPinchGrabAPI_Create();
|
||||
Debug.Assert(_fingerPinchGrabApiHandle != -1, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_Create failed");
|
||||
}
|
||||
|
||||
return _fingerPinchGrabApiHandle;
|
||||
}
|
||||
|
||||
public void SetPinchGrabParam(PinchGrabParam paramId, float paramVal)
|
||||
{
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_SetPinchGrabParam(GetHandle(), paramId, paramVal);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_SetPinchGrabParam failed");
|
||||
}
|
||||
|
||||
public float GetPinchGrabParam(PinchGrabParam paramId)
|
||||
{
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_GetPinchGrabParam(GetHandle(), paramId, out float paramVal);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_GetPinchGrabParam failed");
|
||||
return paramVal;
|
||||
}
|
||||
|
||||
public bool GetIsPinchVisibilityGood()
|
||||
{
|
||||
bool b;
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_IsPinchVisibilityGood(GetHandle(), out b);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_GetIsPinchVisibilityGood failed");
|
||||
return b;
|
||||
}
|
||||
|
||||
public bool GetFingerIsGrabbing(HandFinger finger)
|
||||
{
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_GetFingerIsGrabbing(GetHandle(), (int)finger, out bool grabbing);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_GetFingerIsGrabbing failed");
|
||||
return grabbing;
|
||||
}
|
||||
|
||||
public float GetFingerPinchPercent(HandFinger finger)
|
||||
{
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_GetFingerPinchPercent(GetHandle(), (int)finger, out float pinchPercent);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_GetFingerPinchPercent failed");
|
||||
return pinchPercent;
|
||||
}
|
||||
|
||||
public float GetFingerPinchDistance(HandFinger finger)
|
||||
{
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_GetFingerPinchDistance(GetHandle(), (int)finger, out float pinchDistance);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_GetFingerPinchDistance failed");
|
||||
return pinchDistance;
|
||||
}
|
||||
|
||||
public Vector3 GetWristOffsetLocal()
|
||||
{
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_GetCenterOffset(GetHandle(), out Vector3 center);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_GetCenterOffset failed");
|
||||
return center;
|
||||
}
|
||||
|
||||
public bool GetFingerIsGrabbingChanged(HandFinger finger, bool targetPinchState)
|
||||
{
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_GetFingerIsGrabbingChanged(GetHandle(), (int)finger, targetPinchState, out bool changed);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_GetFingerIsGrabbingChanged failed");
|
||||
return changed;
|
||||
}
|
||||
|
||||
public float GetFingerGrabScore(HandFinger finger)
|
||||
{
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_GetFingerGrabScore(GetHandle(), finger, out float score);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_GetFingerGrabScore failed");
|
||||
return score;
|
||||
}
|
||||
|
||||
public void Update(IHand hand)
|
||||
{
|
||||
hand.GetJointPosesFromWrist(out ReadOnlyHandJointPoses poses);
|
||||
|
||||
if (poses.Count > 0)
|
||||
{
|
||||
_pinchData.SetJoints(poses);
|
||||
Vector3 wristForward = Vector3.forward;
|
||||
Vector3 hmdForward = Vector3.forward;
|
||||
|
||||
if (_hmd != null &&
|
||||
hand.GetJointPose(HandJointId.HandWristRoot, out Pose wristPose) &&
|
||||
_hmd.TryGetRootPose(out Pose centerEyePose))
|
||||
{
|
||||
wristForward = -1.0f * wristPose.forward;
|
||||
hmdForward = -1.0f * centerEyePose.forward;
|
||||
if (hand.Handedness == Handedness.Right)
|
||||
{
|
||||
wristForward = -wristForward;
|
||||
}
|
||||
}
|
||||
|
||||
ReturnValue rc = isdk_FingerPinchGrabAPI_UpdateHandWristHMDData(GetHandle(), _pinchData, wristForward, hmdForward);
|
||||
Debug.Assert(rc != ReturnValue.Failure, "FingerPinchGrabAPI: isdk_FingerPinchGrabAPI_UpdateHandWristHMDData failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 983d470de27b8db4aae01497168de89f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.GrabAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// This FingerAPI uses the the Pinch value as it comes from the Hand data to detect
|
||||
/// if they are grabbing. It is specially useful with Controllers As Hands since this
|
||||
/// value is directly driven by the trigger presses.
|
||||
/// </summary>
|
||||
public class FingerRawPinchAPI : IFingerAPI
|
||||
{
|
||||
private class FingerPinchData
|
||||
{
|
||||
private readonly HandFinger _finger;
|
||||
private readonly HandJointId _tipId;
|
||||
|
||||
public float PinchStrength;
|
||||
public bool IsPinching;
|
||||
public bool IsPinchingChanged { get; private set; }
|
||||
public Vector3 TipPosition { get; private set; }
|
||||
|
||||
public FingerPinchData(HandFinger fingerId)
|
||||
{
|
||||
_finger = fingerId;
|
||||
_tipId = HandJointUtils.GetHandFingerTip(fingerId);
|
||||
}
|
||||
|
||||
private void UpdateTipPosition(IHand hand)
|
||||
{
|
||||
if (hand.GetJointPoseFromWrist(_tipId, out Pose pose))
|
||||
{
|
||||
TipPosition = pose.position;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateIsPinching(IHand hand)
|
||||
{
|
||||
UpdateTipPosition(hand);
|
||||
PinchStrength = hand.GetFingerPinchStrength(_finger);
|
||||
bool isPinching = hand.GetFingerIsPinching(_finger);
|
||||
if(isPinching != IsPinching)
|
||||
{
|
||||
IsPinchingChanged = true;
|
||||
}
|
||||
IsPinching = isPinching;
|
||||
}
|
||||
|
||||
public void ClearState()
|
||||
{
|
||||
IsPinchingChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly FingerPinchData[] _fingersPinchData =
|
||||
{
|
||||
new FingerPinchData(HandFinger.Thumb),
|
||||
new FingerPinchData(HandFinger.Index),
|
||||
new FingerPinchData(HandFinger.Middle),
|
||||
new FingerPinchData(HandFinger.Ring),
|
||||
new FingerPinchData(HandFinger.Pinky)
|
||||
};
|
||||
|
||||
public bool GetFingerIsGrabbing(HandFinger finger)
|
||||
{
|
||||
return _fingersPinchData[(int)finger].IsPinching;
|
||||
}
|
||||
|
||||
public Vector3 GetWristOffsetLocal()
|
||||
{
|
||||
float maxStrength = float.NegativeInfinity;
|
||||
Vector3 thumbTip = _fingersPinchData[0].TipPosition;
|
||||
Vector3 center = thumbTip;
|
||||
|
||||
for (int i = 1; i < Constants.NUM_FINGERS; ++i)
|
||||
{
|
||||
float strength = _fingersPinchData[i].PinchStrength;
|
||||
if (strength > maxStrength)
|
||||
{
|
||||
maxStrength = strength;
|
||||
Vector3 fingerTip = _fingersPinchData[i].TipPosition;
|
||||
center = (thumbTip + fingerTip) * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
public bool GetFingerIsGrabbingChanged(HandFinger finger, bool targetPinchState)
|
||||
{
|
||||
return _fingersPinchData[(int)finger].IsPinchingChanged &&
|
||||
_fingersPinchData[(int)finger].IsPinching == targetPinchState;
|
||||
}
|
||||
|
||||
public float GetFingerGrabScore(HandFinger finger)
|
||||
{
|
||||
return _fingersPinchData[(int)finger].PinchStrength;
|
||||
}
|
||||
|
||||
public void Update(IHand hand)
|
||||
{
|
||||
ClearState();
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; ++i)
|
||||
{
|
||||
_fingersPinchData[i].UpdateIsPinching(hand);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearState()
|
||||
{
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; ++i)
|
||||
{
|
||||
_fingersPinchData[i].ClearState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e600c87acd7d57041b18b00b86842221
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.GrabAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this component with a HandGrabAPI so it uses the Raw pinch detector
|
||||
/// instead of the standard Pinch and Palm finger APIS. Specially useful for
|
||||
/// ControllersAsHands since it uses the same value as the trigger presses.
|
||||
/// </summary>
|
||||
public class FingerRawPinchInjector : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private HandGrabAPI _handGrabAPI;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_handGrabAPI.InjectOptionalFingerPinchAPI(new FingerRawPinchAPI());
|
||||
_handGrabAPI.InjectOptionalFingerGrabAPI(new FingerRawPinchAPI());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77279ac005404794d8e29e385f50c9c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.Grab
|
||||
{
|
||||
[Flags]
|
||||
public enum GrabTypeFlags
|
||||
{
|
||||
None = 0,
|
||||
Pinch = 1 << 0,
|
||||
Palm = 1 << 1,
|
||||
All = (1 << 2) - 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 654ba27bf15ed6b40a251d7b6baafd79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
174
Assets/Oculus/Interaction/Runtime/Scripts/Grab/GrabbingRule.cs
Normal file
174
Assets/Oculus/Interaction/Runtime/Scripts/Grab/GrabbingRule.cs
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.GrabAPI
|
||||
{
|
||||
public enum FingerRequirement
|
||||
{
|
||||
Ignored,
|
||||
Optional,
|
||||
Required
|
||||
}
|
||||
|
||||
public enum FingerUnselectMode
|
||||
{
|
||||
AllReleased,
|
||||
AnyReleased
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct indicates which fingers should be taken in count
|
||||
/// for performing an interaction (typically a hand-grab).
|
||||
/// All required fingers must be in use in order to start the interaction
|
||||
/// while any of the optional fingers is needed.
|
||||
/// For finishing the action it support either releasing all fingers or
|
||||
/// any of them.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct GrabbingRule
|
||||
{
|
||||
[SerializeField]
|
||||
private FingerRequirement _thumbRequirement;
|
||||
[SerializeField]
|
||||
private FingerRequirement _indexRequirement;
|
||||
[SerializeField]
|
||||
private FingerRequirement _middleRequirement;
|
||||
[SerializeField]
|
||||
private FingerRequirement _ringRequirement;
|
||||
[SerializeField]
|
||||
private FingerRequirement _pinkyRequirement;
|
||||
|
||||
[SerializeField]
|
||||
private FingerUnselectMode _unselectMode;
|
||||
|
||||
public FingerUnselectMode UnselectMode => _unselectMode;
|
||||
|
||||
public bool SelectsWithOptionals
|
||||
{
|
||||
get
|
||||
{
|
||||
return _thumbRequirement != FingerRequirement.Required
|
||||
&& _indexRequirement != FingerRequirement.Required
|
||||
&& _middleRequirement != FingerRequirement.Required
|
||||
&& _ringRequirement != FingerRequirement.Required
|
||||
&& _pinkyRequirement != FingerRequirement.Required;
|
||||
}
|
||||
}
|
||||
|
||||
public FingerRequirement this[HandFinger fingerID]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (fingerID)
|
||||
{
|
||||
case HandFinger.Thumb: return _thumbRequirement;
|
||||
case HandFinger.Index: return _indexRequirement;
|
||||
case HandFinger.Middle: return _middleRequirement;
|
||||
case HandFinger.Ring: return _ringRequirement;
|
||||
case HandFinger.Pinky: return _pinkyRequirement;
|
||||
}
|
||||
return FingerRequirement.Ignored;
|
||||
}
|
||||
set
|
||||
{
|
||||
switch (fingerID)
|
||||
{
|
||||
case HandFinger.Thumb: _thumbRequirement = value; break;
|
||||
case HandFinger.Index: _indexRequirement = value; break;
|
||||
case HandFinger.Middle: _middleRequirement = value; break;
|
||||
case HandFinger.Ring: _ringRequirement = value; break;
|
||||
case HandFinger.Pinky: _pinkyRequirement = value; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StripIrrelevant(ref HandFingerFlags fingerFlags)
|
||||
{
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; i++)
|
||||
{
|
||||
HandFinger finger = (HandFinger)i;
|
||||
if (this[finger] == FingerRequirement.Ignored)
|
||||
{
|
||||
fingerFlags = (HandFingerFlags)((int)fingerFlags & ~(1 << i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GrabbingRule(HandFingerFlags mask, in GrabbingRule otherRule)
|
||||
{
|
||||
_thumbRequirement = (mask & HandFingerFlags.Thumb) != 0 ?
|
||||
otherRule._thumbRequirement : FingerRequirement.Ignored;
|
||||
|
||||
_indexRequirement = (mask & HandFingerFlags.Index) != 0 ?
|
||||
otherRule._indexRequirement : FingerRequirement.Ignored;
|
||||
|
||||
_middleRequirement = (mask & HandFingerFlags.Middle) != 0 ?
|
||||
otherRule._middleRequirement : FingerRequirement.Ignored;
|
||||
|
||||
_ringRequirement = (mask & HandFingerFlags.Ring) != 0 ?
|
||||
otherRule._ringRequirement : FingerRequirement.Ignored;
|
||||
|
||||
_pinkyRequirement = (mask & HandFingerFlags.Pinky) != 0 ?
|
||||
otherRule._pinkyRequirement : FingerRequirement.Ignored;
|
||||
|
||||
_unselectMode = otherRule.UnselectMode;
|
||||
}
|
||||
|
||||
#region Defaults
|
||||
|
||||
public static GrabbingRule DefaultPalmRule { get; } = new GrabbingRule()
|
||||
{
|
||||
_thumbRequirement = FingerRequirement.Optional,
|
||||
_indexRequirement = FingerRequirement.Required,
|
||||
_middleRequirement = FingerRequirement.Required,
|
||||
_ringRequirement = FingerRequirement.Required,
|
||||
_pinkyRequirement = FingerRequirement.Optional,
|
||||
|
||||
_unselectMode = FingerUnselectMode.AllReleased
|
||||
};
|
||||
|
||||
public static GrabbingRule DefaultPinchRule { get; } = new GrabbingRule()
|
||||
{
|
||||
_thumbRequirement = FingerRequirement.Optional,
|
||||
_indexRequirement = FingerRequirement.Optional,
|
||||
_middleRequirement = FingerRequirement.Optional,
|
||||
_ringRequirement = FingerRequirement.Ignored,
|
||||
_pinkyRequirement = FingerRequirement.Ignored,
|
||||
|
||||
_unselectMode = FingerUnselectMode.AllReleased
|
||||
};
|
||||
|
||||
public static GrabbingRule FullGrab { get; } = new GrabbingRule()
|
||||
{
|
||||
_thumbRequirement = FingerRequirement.Required,
|
||||
_indexRequirement = FingerRequirement.Required,
|
||||
_middleRequirement = FingerRequirement.Required,
|
||||
_ringRequirement = FingerRequirement.Required,
|
||||
_pinkyRequirement = FingerRequirement.Required,
|
||||
|
||||
_unselectMode = FingerUnselectMode.AllReleased
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73275021f6e6b1444b1fed2f2dab865e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
429
Assets/Oculus/Interaction/Runtime/Scripts/Grab/HandGrabAPI.cs
Normal file
429
Assets/Oculus/Interaction/Runtime/Scripts/Grab/HandGrabAPI.cs
Normal file
@ -0,0 +1,429 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.GrabAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// The HandGrabAPI wraps under the hood several IFingerAPIs to detect if
|
||||
/// the fingers are grabbing or not. It differentiates between pinch and
|
||||
/// palm grabs but via Inject it is possible to modify the detectors.
|
||||
/// </summary>
|
||||
public class HandGrabAPI : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Interface(typeof(IHand))]
|
||||
private UnityEngine.Object _hand;
|
||||
|
||||
public IHand Hand { get; private set; }
|
||||
|
||||
[SerializeField, Interface(typeof(IHmd)), Optional]
|
||||
private UnityEngine.Object _hmd;
|
||||
|
||||
public IHmd Hmd { get; private set; } = null;
|
||||
|
||||
private IFingerAPI _fingerPinchGrabAPI = null;
|
||||
private IFingerAPI _fingerPalmGrabAPI = null;
|
||||
|
||||
private bool _started = false;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Hand = _hand as IHand;
|
||||
Hmd = _hmd as IHmd;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
this.AssertField(Hand, nameof(Hand));
|
||||
if (_fingerPinchGrabAPI == null)
|
||||
{
|
||||
_fingerPinchGrabAPI = new FingerPinchGrabAPI(Hmd);
|
||||
}
|
||||
if (_fingerPalmGrabAPI == null)
|
||||
{
|
||||
_fingerPalmGrabAPI = new FingerPalmGrabAPI();
|
||||
}
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
Hand.WhenHandUpdated += OnHandUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
Hand.WhenHandUpdated -= OnHandUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHandUpdated()
|
||||
{
|
||||
_fingerPinchGrabAPI.Update(Hand);
|
||||
_fingerPalmGrabAPI.Update(Hand);
|
||||
}
|
||||
|
||||
public HandFingerFlags HandPinchGrabbingFingers()
|
||||
{
|
||||
return HandGrabbingFingers(_fingerPinchGrabAPI);
|
||||
}
|
||||
|
||||
public HandFingerFlags HandPalmGrabbingFingers()
|
||||
{
|
||||
return HandGrabbingFingers(_fingerPalmGrabAPI);
|
||||
}
|
||||
|
||||
private HandFingerFlags HandGrabbingFingers(IFingerAPI fingerAPI)
|
||||
{
|
||||
HandFingerFlags grabbingFingers = HandFingerFlags.None;
|
||||
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; i++)
|
||||
{
|
||||
HandFinger finger = (HandFinger)i;
|
||||
|
||||
bool isGrabbing = fingerAPI.GetFingerIsGrabbing(finger);
|
||||
if (isGrabbing)
|
||||
{
|
||||
grabbingFingers |= (HandFingerFlags)(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
return grabbingFingers;
|
||||
}
|
||||
|
||||
public bool IsHandPinchGrabbing(in GrabbingRule fingers)
|
||||
{
|
||||
HandFingerFlags pinchFingers = HandPinchGrabbingFingers();
|
||||
return IsSustainingGrab(fingers, pinchFingers);
|
||||
}
|
||||
|
||||
public bool IsHandPalmGrabbing(in GrabbingRule fingers)
|
||||
{
|
||||
HandFingerFlags palmFingers = HandPalmGrabbingFingers();
|
||||
return IsSustainingGrab(fingers, palmFingers);
|
||||
}
|
||||
|
||||
public bool IsSustainingGrab(in GrabbingRule fingers, HandFingerFlags grabbingFingers)
|
||||
{
|
||||
bool anyHolding = false;
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; i++)
|
||||
{
|
||||
HandFinger finger = (HandFinger)i;
|
||||
HandFingerFlags fingerFlag = (HandFingerFlags)(1 << i);
|
||||
|
||||
bool isFingerGrabbing = (grabbingFingers & fingerFlag) != 0;
|
||||
if (fingers[finger] == FingerRequirement.Required)
|
||||
{
|
||||
anyHolding |= isFingerGrabbing;
|
||||
if (fingers.UnselectMode == FingerUnselectMode.AnyReleased
|
||||
&& !isFingerGrabbing)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fingers.UnselectMode == FingerUnselectMode.AllReleased
|
||||
&& isFingerGrabbing)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (fingers[finger] == FingerRequirement.Optional)
|
||||
{
|
||||
anyHolding |= isFingerGrabbing;
|
||||
}
|
||||
}
|
||||
|
||||
return anyHolding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether the state of any of the finger pinches have changed this frame to
|
||||
/// the target pinching state (on/off).
|
||||
/// </summary>
|
||||
/// <param name="fingers">Finger rules to check.</param>
|
||||
public bool IsHandSelectPinchFingersChanged(in GrabbingRule fingers)
|
||||
{
|
||||
return IsHandSelectFingersChanged(fingers, _fingerPinchGrabAPI);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether the state of any of the finger grabs have changed this frame to
|
||||
/// the target grabbing state (on/off).
|
||||
/// </summary>
|
||||
/// <param name="fingers">Finger rules to check.</param>
|
||||
public bool IsHandSelectPalmFingersChanged(in GrabbingRule fingers)
|
||||
{
|
||||
return IsHandSelectFingersChanged(fingers, _fingerPalmGrabAPI);
|
||||
}
|
||||
|
||||
public bool IsHandUnselectPinchFingersChanged(in GrabbingRule fingers)
|
||||
{
|
||||
return IsHandUnselectFingersChanged(fingers, _fingerPinchGrabAPI);
|
||||
}
|
||||
|
||||
public bool IsHandUnselectPalmFingersChanged(in GrabbingRule fingers)
|
||||
{
|
||||
return IsHandUnselectFingersChanged(fingers, _fingerPalmGrabAPI);
|
||||
}
|
||||
|
||||
private bool IsHandSelectFingersChanged(in GrabbingRule fingers, IFingerAPI fingerAPI)
|
||||
{
|
||||
bool selectsWithOptionals = fingers.SelectsWithOptionals;
|
||||
bool anyFingerBeganGrabbing = false;
|
||||
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; i++)
|
||||
{
|
||||
HandFinger finger = (HandFinger)i;
|
||||
if (fingers[finger] == FingerRequirement.Required)
|
||||
{
|
||||
if (!fingerAPI.GetFingerIsGrabbing(finger))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fingerAPI.GetFingerIsGrabbingChanged(finger, true))
|
||||
{
|
||||
anyFingerBeganGrabbing = true;
|
||||
}
|
||||
}
|
||||
else if (selectsWithOptionals
|
||||
&& fingers[finger] == FingerRequirement.Optional)
|
||||
{
|
||||
if (fingerAPI.GetFingerIsGrabbingChanged(finger, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return anyFingerBeganGrabbing;
|
||||
}
|
||||
|
||||
private bool IsHandUnselectFingersChanged(in GrabbingRule fingers, IFingerAPI fingerAPI)
|
||||
{
|
||||
bool isAnyFingerGrabbing = false;
|
||||
bool anyFingerStoppedGrabbing = false;
|
||||
bool selectsWithOptionals = fingers.SelectsWithOptionals;
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; i++)
|
||||
{
|
||||
HandFinger finger = (HandFinger)i;
|
||||
if (fingers[finger] == FingerRequirement.Ignored)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
isAnyFingerGrabbing |= fingerAPI.GetFingerIsGrabbing(finger);
|
||||
if (fingers[finger] == FingerRequirement.Required)
|
||||
{
|
||||
if (fingerAPI.GetFingerIsGrabbingChanged(finger, false))
|
||||
{
|
||||
anyFingerStoppedGrabbing = true;
|
||||
if (fingers.UnselectMode == FingerUnselectMode.AnyReleased)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (fingers[finger] == FingerRequirement.Optional)
|
||||
{
|
||||
if (fingerAPI.GetFingerIsGrabbingChanged(finger, false))
|
||||
{
|
||||
anyFingerStoppedGrabbing = true;
|
||||
if (fingers.UnselectMode == FingerUnselectMode.AnyReleased
|
||||
&& selectsWithOptionals)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !isAnyFingerGrabbing && anyFingerStoppedGrabbing;
|
||||
}
|
||||
|
||||
public Vector3 GetPinchCenter()
|
||||
{
|
||||
Vector3 localOffset = _fingerPinchGrabAPI.GetWristOffsetLocal();
|
||||
return WristOffsetToWorldPoint(localOffset);
|
||||
}
|
||||
|
||||
public Vector3 GetPalmCenter()
|
||||
{
|
||||
Vector3 localOffset = _fingerPalmGrabAPI.GetWristOffsetLocal();
|
||||
return WristOffsetToWorldPoint(localOffset);
|
||||
}
|
||||
|
||||
private Vector3 WristOffsetToWorldPoint(Vector3 localOffset)
|
||||
{
|
||||
if (!Hand.GetJointPose(HandJointId.HandWristRoot, out Pose wristPose))
|
||||
{
|
||||
return localOffset * Hand.Scale;
|
||||
}
|
||||
|
||||
return wristPose.position + wristPose.rotation * localOffset * Hand.Scale;
|
||||
}
|
||||
|
||||
public float GetHandPinchScore(in GrabbingRule fingers,
|
||||
bool includePinching = true)
|
||||
{
|
||||
return GetHandGrabScore(fingers, includePinching, _fingerPinchGrabAPI);
|
||||
}
|
||||
|
||||
public float GetHandPalmScore(in GrabbingRule fingers,
|
||||
bool includeGrabbing = true)
|
||||
{
|
||||
return GetHandGrabScore(fingers, includeGrabbing, _fingerPalmGrabAPI);
|
||||
}
|
||||
|
||||
public float GetFingerPinchStrength(HandFinger finger)
|
||||
{
|
||||
return _fingerPinchGrabAPI.GetFingerGrabScore(finger);
|
||||
}
|
||||
|
||||
public float GetFingerPinchPercent(HandFinger finger)
|
||||
{
|
||||
if (_fingerPinchGrabAPI is FingerPinchGrabAPI)
|
||||
{
|
||||
FingerPinchGrabAPI pinchGrab = _fingerPinchGrabAPI as FingerPinchGrabAPI;
|
||||
return pinchGrab.GetFingerPinchPercent(finger);
|
||||
}
|
||||
Debug.LogWarning("GetFingerPinchPercent is not applicable");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public float GetFingerPinchDistance(HandFinger finger)
|
||||
{
|
||||
if (_fingerPinchGrabAPI is FingerPinchGrabAPI)
|
||||
{
|
||||
FingerPinchGrabAPI pinchGrab = _fingerPinchGrabAPI as FingerPinchGrabAPI;
|
||||
return pinchGrab.GetFingerPinchDistance(finger);
|
||||
}
|
||||
Debug.LogWarning("GetFingerPinchDistance is not applicable");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public float GetFingerPalmStrength(HandFinger finger)
|
||||
{
|
||||
return _fingerPalmGrabAPI.GetFingerGrabScore(finger);
|
||||
}
|
||||
|
||||
private float GetHandGrabScore(in GrabbingRule fingers,
|
||||
bool includeGrabbing, IFingerAPI fingerAPI)
|
||||
{
|
||||
float requiredMin = 1.0f;
|
||||
float optionalMax = 0f;
|
||||
bool anyRequired = false;
|
||||
bool usesOptionals = fingers.SelectsWithOptionals;
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; i++)
|
||||
{
|
||||
HandFinger finger = (HandFinger)i;
|
||||
if (!includeGrabbing && fingerAPI.GetFingerIsGrabbing((HandFinger)i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fingers[finger] == FingerRequirement.Ignored)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fingers[finger] == FingerRequirement.Optional)
|
||||
{
|
||||
optionalMax = Mathf.Max(optionalMax, fingerAPI.GetFingerGrabScore(finger));
|
||||
}
|
||||
else if (fingers[finger] == FingerRequirement.Required)
|
||||
{
|
||||
anyRequired = true;
|
||||
requiredMin = Mathf.Min(requiredMin, fingerAPI.GetFingerGrabScore(finger));
|
||||
}
|
||||
}
|
||||
|
||||
return usesOptionals ? optionalMax : anyRequired ? requiredMin : 0f;
|
||||
}
|
||||
|
||||
public void SetPinchGrabParam(PinchGrabParam paramId, float paramVal)
|
||||
{
|
||||
FingerPinchGrabAPI pinchGrab = _fingerPinchGrabAPI as FingerPinchGrabAPI;
|
||||
if (pinchGrab != null)
|
||||
{
|
||||
pinchGrab.SetPinchGrabParam(paramId, paramVal);
|
||||
}
|
||||
}
|
||||
|
||||
public float GetPinchGrabParam(PinchGrabParam paramId)
|
||||
{
|
||||
FingerPinchGrabAPI pinchGrab = _fingerPinchGrabAPI as FingerPinchGrabAPI;
|
||||
if (pinchGrab != null)
|
||||
{
|
||||
return pinchGrab.GetPinchGrabParam(paramId);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool GetFingerIsGrabbing(HandFinger finger)
|
||||
{
|
||||
return _fingerPinchGrabAPI.GetFingerIsGrabbing(finger);
|
||||
}
|
||||
|
||||
public bool GetFingerIsPalmGrabbing(HandFinger finger)
|
||||
{
|
||||
return _fingerPalmGrabAPI.GetFingerIsGrabbing(finger);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllHandGrabAPI(IHand hand)
|
||||
{
|
||||
InjectHand(hand);
|
||||
}
|
||||
|
||||
public void InjectHand(IHand hand)
|
||||
{
|
||||
_hand = hand as UnityEngine.Object;
|
||||
Hand = hand;
|
||||
}
|
||||
|
||||
public void InjectOptionalHmd(IHmd hmd)
|
||||
{
|
||||
Hmd = hmd;
|
||||
_hmd = hmd as UnityEngine.Object;
|
||||
}
|
||||
|
||||
public void InjectOptionalFingerPinchAPI(IFingerAPI fingerPinchAPI)
|
||||
{
|
||||
_fingerPinchGrabAPI = fingerPinchAPI;
|
||||
}
|
||||
|
||||
public void InjectOptionalFingerGrabAPI(IFingerAPI fingerGrabAPI)
|
||||
{
|
||||
_fingerPalmGrabAPI = fingerGrabAPI;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae43f25c06eb2d9408a201216b4c3ed7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
39
Assets/Oculus/Interaction/Runtime/Scripts/Grab/IFingerAPI.cs
Normal file
39
Assets/Oculus/Interaction/Runtime/Scripts/Grab/IFingerAPI.cs
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is used for tracking the grabbing strength of the fingers.
|
||||
/// Since said strength can be measured with different methods: pinching, curl,
|
||||
/// trigger presses, etc. Multiple implementations are needed.
|
||||
/// </summary>
|
||||
public interface IFingerAPI
|
||||
{
|
||||
bool GetFingerIsGrabbing(HandFinger finger);
|
||||
bool GetFingerIsGrabbingChanged(HandFinger finger, bool targetPinchState);
|
||||
float GetFingerGrabScore(HandFinger finger);
|
||||
Vector3 GetWristOffsetLocal();
|
||||
void Update(IHand hand);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 629a079b5a23e404faee62377bf2620d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66d82fc01fa658f40b9399eec55a1c7a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction.Grab.GrabSurfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a series of points that specify paths along with the snapping pose is valid.
|
||||
/// The points can be connected to the previous and/or next one, even looping, or completely isolated.
|
||||
/// When two or more consecutive points are connected, they define a continuous path. The Tangent can be
|
||||
/// used to specify the curve between two points. It is also possible to connect the last point with the
|
||||
/// first one to create a closed loop. The rotation at a point in the path is specified by interpolating
|
||||
/// the rotation of the starting and ending point of the current segment.
|
||||
/// When a point is not connected to any other point, it is considered as a single point in space.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class BezierGrabSurface : MonoBehaviour, IGrabSurface
|
||||
{
|
||||
[SerializeField]
|
||||
private List<BezierControlPoint> _controlPoints = new List<BezierControlPoint>();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Transform used as a reference to measure the local data of the grab surface")]
|
||||
private Transform _relativeTo;
|
||||
|
||||
public List<BezierControlPoint> ControlPoints => _controlPoints;
|
||||
|
||||
private const float MAX_PLANE_DOT = 0.95f;
|
||||
|
||||
#region editor events
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_relativeTo = this.GetComponentInParent<IRelativeToRef>()?.RelativeTo;
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.AssertCollectionField(ControlPoints, nameof(ControlPoints));
|
||||
this.AssertField(_relativeTo, nameof(_relativeTo));
|
||||
}
|
||||
|
||||
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
|
||||
in PoseMeasureParameters scoringModifier, Transform relativeTo)
|
||||
{
|
||||
Pose testPose = Pose.identity;
|
||||
Pose smallestRotationPose = Pose.identity;
|
||||
bestPose = targetPose;
|
||||
GrabPoseScore bestScore = GrabPoseScore.Max;
|
||||
for (int i = 0; i < _controlPoints.Count; i++)
|
||||
{
|
||||
BezierControlPoint currentControlPoint = _controlPoints[i];
|
||||
BezierControlPoint nextControlPoint = _controlPoints[(i + 1) % _controlPoints.Count];
|
||||
|
||||
if (!currentControlPoint.Disconnected
|
||||
&& nextControlPoint.Disconnected)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GrabPoseScore score;
|
||||
if ((currentControlPoint.Disconnected && nextControlPoint.Disconnected)
|
||||
|| _controlPoints.Count == 1)
|
||||
{
|
||||
Pose worldPose = currentControlPoint.GetPose(relativeTo);
|
||||
testPose.CopyFrom(worldPose);
|
||||
score = new GrabPoseScore(targetPose, testPose, scoringModifier.PositionRotationWeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
Pose start = currentControlPoint.GetPose(relativeTo);
|
||||
Pose end = nextControlPoint.GetPose(relativeTo);
|
||||
Vector3 tangent = currentControlPoint.GetTangent(relativeTo);
|
||||
|
||||
NearestPointInTriangle(targetPose.position, start.position, tangent, end.position, out float positionT);
|
||||
float rotationT = ProgressForRotation(targetPose.rotation, start.rotation, end.rotation);
|
||||
|
||||
score = GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, out testPose, scoringModifier, relativeTo,
|
||||
(in Pose target, Transform relativeTo) =>
|
||||
{
|
||||
Pose result;
|
||||
result.position = EvaluateBezier(start.position, tangent, end.position, positionT);
|
||||
result.rotation = Quaternion.Slerp(start.rotation, end.rotation, positionT);
|
||||
return result;
|
||||
|
||||
},
|
||||
(in Pose target, Transform relativeTo) =>
|
||||
{
|
||||
Pose result;
|
||||
result.position = EvaluateBezier(start.position, tangent, end.position, rotationT);
|
||||
result.rotation = Quaternion.Slerp(start.rotation, end.rotation, rotationT);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
if (score.IsBetterThan(bestScore))
|
||||
{
|
||||
bestScore = score;
|
||||
bestPose.CopyFrom(testPose);
|
||||
}
|
||||
}
|
||||
return bestScore;
|
||||
}
|
||||
|
||||
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose, Transform relativeTo)
|
||||
{
|
||||
Pose testPose = Pose.identity;
|
||||
Pose targetPose = Pose.identity;
|
||||
bestPose = Pose.identity;
|
||||
bool poseFound = false;
|
||||
GrabPoseScore bestScore = GrabPoseScore.Max;
|
||||
for (int i = 0; i < _controlPoints.Count; i++)
|
||||
{
|
||||
BezierControlPoint currentControlPoint = _controlPoints[i];
|
||||
BezierControlPoint nextControlPoint = _controlPoints[(i + 1) % _controlPoints.Count];
|
||||
|
||||
if (!currentControlPoint.Disconnected
|
||||
&& nextControlPoint.Disconnected)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((currentControlPoint.Disconnected && nextControlPoint.Disconnected)
|
||||
|| _controlPoints.Count == 1)
|
||||
{
|
||||
Pose worldPose = currentControlPoint.GetPose(relativeTo);
|
||||
Plane plane = new Plane(-targetRay.direction, worldPose.position);
|
||||
if (!plane.Raycast(targetRay, out float enter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
targetPose.position = targetRay.GetPoint(enter);
|
||||
testPose.CopyFrom(worldPose);
|
||||
}
|
||||
else
|
||||
{
|
||||
Pose start = currentControlPoint.GetPose(relativeTo);
|
||||
Pose end = nextControlPoint.GetPose(relativeTo);
|
||||
Vector3 tangent = currentControlPoint.GetTangent(relativeTo);
|
||||
Plane plane = GenerateRaycastPlane(start.position, tangent, end.position, -targetRay.direction);
|
||||
if (!plane.Raycast(targetRay, out float enter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
targetPose.position = targetRay.GetPoint(enter);
|
||||
NearestPointInTriangle(targetPose.position, start.position, tangent, end.position, out float t);
|
||||
testPose.position = EvaluateBezier(start.position, tangent, end.position, t);
|
||||
testPose.rotation = Quaternion.Slerp(start.rotation, end.rotation, t);
|
||||
}
|
||||
|
||||
GrabPoseScore score = new GrabPoseScore(targetPose.position, testPose.position);
|
||||
if (score.IsBetterThan(bestScore))
|
||||
{
|
||||
bestScore = score;
|
||||
bestPose.CopyFrom(testPose);
|
||||
poseFound = true;
|
||||
}
|
||||
}
|
||||
return poseFound;
|
||||
}
|
||||
|
||||
private Plane GenerateRaycastPlane(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 fallbackDir)
|
||||
{
|
||||
Vector3 line0 = (p1 - p0).normalized;
|
||||
Vector3 line1 = (p2 - p0).normalized;
|
||||
|
||||
Plane plane;
|
||||
if (Mathf.Abs(Vector3.Dot(line0, line1)) > MAX_PLANE_DOT)
|
||||
{
|
||||
plane = new Plane(fallbackDir, (p0 + p2 + p1) / 3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
plane = new Plane(p0, p1, p2);
|
||||
}
|
||||
return plane;
|
||||
}
|
||||
|
||||
private float ProgressForRotation(Quaternion targetRotation, Quaternion from, Quaternion to)
|
||||
{
|
||||
Vector3 targetForward = targetRotation * Vector3.forward;
|
||||
Vector3 fromForward = from * Vector3.forward;
|
||||
Vector3 toForward = to * Vector3.forward;
|
||||
Vector3 axis = Vector3.Cross(fromForward, toForward).normalized;
|
||||
|
||||
float angleFrom = Vector3.SignedAngle(targetForward, fromForward, axis);
|
||||
float angleTo = Vector3.SignedAngle(targetForward, toForward, axis);
|
||||
|
||||
if (angleFrom < 0 && angleTo < 0)
|
||||
{
|
||||
return 1f;
|
||||
}
|
||||
if (angleFrom > 0 && angleTo > 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
return Mathf.Abs(angleFrom) / Vector3.Angle(fromForward, toForward);
|
||||
}
|
||||
|
||||
private Vector3 NearestPointInTriangle(Vector3 point, Vector3 p0, Vector3 p1, Vector3 p2, out float t)
|
||||
{
|
||||
Vector3 centroid = (p0 + p1 + p2) / 3f;
|
||||
|
||||
Vector3 pointInMedian0 = NearestPointToSegment(point, p0, centroid, out float t0);
|
||||
Vector3 pointInMedian1 = NearestPointToSegment(point, centroid, p2, out float t1);
|
||||
|
||||
float median0 = Vector3.Distance(p0, centroid);
|
||||
float median2 = Vector3.Distance(p2, centroid);
|
||||
float alpha = median2 / (median0 + median2);
|
||||
|
||||
float distance0 = (pointInMedian0 - point).sqrMagnitude;
|
||||
float distance1 = (pointInMedian1 - point).sqrMagnitude;
|
||||
if (distance0 < distance1)
|
||||
{
|
||||
t = t0 * alpha;
|
||||
return pointInMedian0;
|
||||
}
|
||||
else
|
||||
{
|
||||
t = alpha + t1 * (1f - alpha);
|
||||
return pointInMedian1;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 NearestPointToSegment(Vector3 point, Vector3 start, Vector3 end, out float progress)
|
||||
{
|
||||
Vector3 segment = end - start;
|
||||
Vector3 projection = Vector3.Project(point - start, segment.normalized);
|
||||
Vector3 pointInSegment;
|
||||
if (Vector3.Dot(segment, projection) <= 0)
|
||||
{
|
||||
pointInSegment = start;
|
||||
progress = 0;
|
||||
}
|
||||
else if (projection.sqrMagnitude >= segment.sqrMagnitude)
|
||||
{
|
||||
pointInSegment = end;
|
||||
progress = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
pointInSegment = start + projection;
|
||||
progress = projection.magnitude / segment.magnitude;
|
||||
}
|
||||
|
||||
return pointInSegment;
|
||||
}
|
||||
|
||||
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
|
||||
{
|
||||
BezierGrabSurface surface = gameObject.AddComponent<BezierGrabSurface>();
|
||||
surface._controlPoints = new List<BezierControlPoint>(_controlPoints);
|
||||
return surface;
|
||||
}
|
||||
|
||||
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
|
||||
{
|
||||
BezierGrabSurface mirroredSurface = gameObject.AddComponent<BezierGrabSurface>();
|
||||
mirroredSurface._controlPoints = new List<BezierControlPoint>();
|
||||
foreach (BezierControlPoint controlPoint in _controlPoints)
|
||||
{
|
||||
Pose pose = controlPoint.GetPose(_relativeTo);
|
||||
pose.rotation = pose.rotation * Quaternion.Euler(180f, 180f, 0f);
|
||||
controlPoint.SetPose(pose, _relativeTo);
|
||||
mirroredSurface._controlPoints.Add(controlPoint);
|
||||
|
||||
}
|
||||
return mirroredSurface;
|
||||
}
|
||||
|
||||
public Pose MirrorPose(in Pose gripPose, Transform relativeTo)
|
||||
{
|
||||
return gripPose;
|
||||
}
|
||||
|
||||
public static Vector3 EvaluateBezier(Vector3 start, Vector3 middle, Vector3 end, float t)
|
||||
{
|
||||
t = Mathf.Clamp01(t);
|
||||
float oneMinusT = 1f - t;
|
||||
return (oneMinusT * oneMinusT * start)
|
||||
+ (2f * oneMinusT * t * middle)
|
||||
+ (t * t * end);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
public void InjectAllBezierSurface(List<BezierControlPoint> controlPoints,
|
||||
Transform relativeTo)
|
||||
{
|
||||
InjectControlPoints(controlPoints);
|
||||
InjectRelativeTo(relativeTo);
|
||||
}
|
||||
|
||||
public void InjectControlPoints(List<BezierControlPoint> controlPoints)
|
||||
{
|
||||
_controlPoints = controlPoints;
|
||||
}
|
||||
|
||||
public void InjectRelativeTo(Transform relativeTo)
|
||||
{
|
||||
_relativeTo = relativeTo;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct BezierControlPoint
|
||||
{
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("pose")]
|
||||
private Pose _pose;
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("tangentPoint")]
|
||||
private Vector3 _tangentPoint;
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("disconnected")]
|
||||
private bool _disconnected;
|
||||
|
||||
public bool Disconnected
|
||||
{
|
||||
get
|
||||
{
|
||||
return _disconnected;
|
||||
}
|
||||
set
|
||||
{
|
||||
_disconnected = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Pose GetPose(Transform relativeTo)
|
||||
{
|
||||
return PoseUtils.GlobalPoseScaled(relativeTo, _pose);
|
||||
}
|
||||
|
||||
public void SetPose(in Pose worldSpacePose, Transform relativeTo)
|
||||
{
|
||||
_pose = PoseUtils.DeltaScaled(relativeTo, worldSpacePose);
|
||||
}
|
||||
|
||||
public Vector3 GetTangent(Transform relativeTo)
|
||||
{
|
||||
return relativeTo.TransformPoint(_tangentPoint);
|
||||
}
|
||||
|
||||
public void SetTangent(in Vector3 tangent, Transform relativeTo)
|
||||
{
|
||||
_tangentPoint = relativeTo.InverseTransformPoint(tangent);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe16fe5c237c19b45bc8c5843cfc4934
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.Grab.GrabSurfaces
|
||||
{
|
||||
[Serializable]
|
||||
public class BoxGrabSurfaceData : ICloneable
|
||||
{
|
||||
public object Clone()
|
||||
{
|
||||
BoxGrabSurfaceData clone = new BoxGrabSurfaceData();
|
||||
clone.widthOffset = this.widthOffset;
|
||||
clone.snapOffset = this.snapOffset;
|
||||
clone.size = this.size;
|
||||
clone.eulerAngles = this.eulerAngles;
|
||||
return clone;
|
||||
}
|
||||
|
||||
public BoxGrabSurfaceData Mirror()
|
||||
{
|
||||
BoxGrabSurfaceData mirror = Clone() as BoxGrabSurfaceData;
|
||||
mirror.snapOffset = new Vector4(
|
||||
-mirror.snapOffset.y, -mirror.snapOffset.x,
|
||||
-mirror.snapOffset.w, -mirror.snapOffset.z);
|
||||
|
||||
return mirror;
|
||||
}
|
||||
|
||||
[Range(0f, 1f)]
|
||||
public float widthOffset = 0.5f;
|
||||
public Vector4 snapOffset;
|
||||
public Vector3 size = new Vector3(0.1f, 0f, 0.1f);
|
||||
public Vector3 eulerAngles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This GrabSurface defines a Rectangle around which the grip point is valid.
|
||||
/// Since the grip point might be offset from the fingers, a valid range for each opposite
|
||||
/// side of the rectangle can be set so the grabbing fingers are constrained to the object bounds.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class BoxGrabSurface : MonoBehaviour, IGrabSurface
|
||||
{
|
||||
[SerializeField]
|
||||
protected BoxGrabSurfaceData _data = new BoxGrabSurfaceData();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Transform used as a reference to measure the local data of the grab surface")]
|
||||
private Transform _relativeTo;
|
||||
|
||||
private Pose RelativePose => PoseUtils.DeltaScaled(_relativeTo, this.transform);
|
||||
|
||||
/// <summary>
|
||||
/// The origin pose of the surface. This is the point from which
|
||||
/// the base of the box must start.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Pose in world space</returns>
|
||||
public Pose GetReferencePose(Transform relativeTo)
|
||||
{
|
||||
return PoseUtils.GlobalPoseScaled(relativeTo, RelativePose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The lateral displacement of the grip point in the main side.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Lateral displacement in world space</returns>
|
||||
public float GetWidthOffset(Transform relativeTo)
|
||||
{
|
||||
return _data.widthOffset * relativeTo.lossyScale.x;
|
||||
}
|
||||
public void SetWidthOffset(float widthOffset, Transform relativeTo)
|
||||
{
|
||||
_data.widthOffset = widthOffset / relativeTo.lossyScale.x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The range at which the sides are constrained.
|
||||
/// X,Y for the back and forward sides range.
|
||||
/// Z,W for the left and right sides range.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Offsets in world space</returns>
|
||||
public Vector4 GetSnapOffset(Transform relativeTo)
|
||||
{
|
||||
return _data.snapOffset * relativeTo.lossyScale.x;
|
||||
}
|
||||
public void SetSnapOffset(Vector4 snapOffset, Transform relativeTo)
|
||||
{
|
||||
_data.snapOffset = snapOffset / relativeTo.lossyScale.x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The size of the rectangle. Y is ignored.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Size in world space</returns>
|
||||
public Vector3 GetSize(Transform relativeTo)
|
||||
{
|
||||
return _data.size * relativeTo.lossyScale.x;
|
||||
}
|
||||
public void SetSize(Vector3 size, Transform relativeTo)
|
||||
{
|
||||
_data.size = size / relativeTo.lossyScale.x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rotation of the rectangle from the Grip point
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Rotation in world space</returns>
|
||||
public Quaternion GetRotation(Transform relativeTo)
|
||||
{
|
||||
return relativeTo.rotation * Quaternion.Euler(_data.eulerAngles);
|
||||
}
|
||||
public void SetRotation(Quaternion rotation, Transform relativeTo)
|
||||
{
|
||||
_data.eulerAngles = (Quaternion.Inverse(relativeTo.rotation) * rotation).eulerAngles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The forward direction of the rectangle (based on its rotation)
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Direction in world space</returns>
|
||||
public Vector3 GetDirection(Transform relativeTo)
|
||||
{
|
||||
return GetRotation(relativeTo) * Vector3.forward;
|
||||
}
|
||||
|
||||
#region editor events
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_relativeTo = this.GetComponentInParent<IRelativeToRef>()?.RelativeTo;
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.AssertField(_data, nameof(_data));
|
||||
this.AssertField(_relativeTo, nameof(_relativeTo));
|
||||
}
|
||||
|
||||
public Pose MirrorPose(in Pose pose, Transform relativeTo)
|
||||
{
|
||||
Quaternion rotation = GetRotation(relativeTo);
|
||||
Vector3 normal = Quaternion.Inverse(relativeTo.rotation) * rotation * Vector3.forward;
|
||||
Vector3 tangent = Quaternion.Inverse(relativeTo.rotation) * (rotation * Vector3.up);
|
||||
return pose.MirrorPoseRotation(normal, tangent);
|
||||
}
|
||||
|
||||
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
|
||||
{
|
||||
BoxGrabSurface surface = gameObject.AddComponent<BoxGrabSurface>();
|
||||
surface._data = _data.Mirror();
|
||||
return surface;
|
||||
}
|
||||
|
||||
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
|
||||
{
|
||||
BoxGrabSurface surface = gameObject.AddComponent<BoxGrabSurface>();
|
||||
surface._data = _data;
|
||||
return surface;
|
||||
}
|
||||
|
||||
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
|
||||
in PoseMeasureParameters scoringModifier, Transform relativeTo)
|
||||
{
|
||||
return GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, out bestPose,
|
||||
scoringModifier, relativeTo,
|
||||
MinimalTranslationPoseAtSurface, MinimalRotationPoseAtSurface);
|
||||
}
|
||||
|
||||
private void CalculateCorners(out Vector3 bottomLeft, out Vector3 bottomRight, out Vector3 topLeft, out Vector3 topRight,
|
||||
Transform relativeTo)
|
||||
{
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
Vector3 size = GetSize(relativeTo);
|
||||
float widthOffset = GetWidthOffset(relativeTo);
|
||||
Vector3 rightRot = GetRotation(relativeTo) * Vector3.right;
|
||||
bottomLeft = referencePose.position - rightRot * size.x * (1f - widthOffset);
|
||||
bottomRight = referencePose.position + rightRot * size.x * (widthOffset);
|
||||
Vector3 forwardOffset = GetRotation(relativeTo) * Vector3.forward * size.z;
|
||||
topLeft = bottomLeft + forwardOffset;
|
||||
topRight = bottomRight + forwardOffset;
|
||||
}
|
||||
|
||||
private Vector3 ProjectOnSegment(Vector3 point, (Vector3, Vector3) segment)
|
||||
{
|
||||
Vector3 line = segment.Item2 - segment.Item1;
|
||||
Vector3 projection = Vector3.Project(point - segment.Item1, line);
|
||||
if (Vector3.Dot(projection, line) < 0f)
|
||||
{
|
||||
projection = segment.Item1;
|
||||
}
|
||||
else if (projection.magnitude > line.magnitude)
|
||||
{
|
||||
projection = segment.Item2;
|
||||
}
|
||||
else
|
||||
{
|
||||
projection += segment.Item1;
|
||||
}
|
||||
return projection;
|
||||
}
|
||||
|
||||
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose, Transform relativeTo)
|
||||
{
|
||||
Pose recordedPose = GetReferencePose(relativeTo);
|
||||
Plane plane = new Plane(GetRotation(relativeTo) * Vector3.up, this.transform.position);
|
||||
plane.Raycast(targetRay, out float rayDistance);
|
||||
Vector3 proximalPoint = targetRay.origin + targetRay.direction * rayDistance;
|
||||
|
||||
Vector3 surfacePoint = NearestPointInSurface(proximalPoint, relativeTo);
|
||||
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
|
||||
bestPose = MinimalTranslationPoseAtSurface(desiredPose, relativeTo);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Vector3 NearestPointInSurface(Vector3 targetPosition, Transform relativeTo)
|
||||
{
|
||||
NearestPointAndAngleInSurface(targetPosition, out Vector3 surfacePoint, out float angle, relativeTo);
|
||||
return surfacePoint;
|
||||
}
|
||||
|
||||
private void NearestPointAndAngleInSurface(Vector3 targetPosition, out Vector3 surfacePoint, out float angle, Transform relativeTo)
|
||||
{
|
||||
Quaternion rotation = GetRotation(relativeTo);
|
||||
Vector4 snappOffset = GetSnapOffset(relativeTo);
|
||||
Vector3 rightDir = rotation * Vector3.right;
|
||||
Vector3 forwardDir = rotation * Vector3.forward;
|
||||
Vector3 bottomLeft, bottomRight, topLeft, topRight;
|
||||
CalculateCorners(out bottomLeft, out bottomRight, out topLeft, out topRight, relativeTo);
|
||||
|
||||
Vector3 bottomP = ProjectOnSegment(targetPosition,
|
||||
(bottomLeft + rightDir * snappOffset.y, bottomRight + rightDir * snappOffset.x));
|
||||
Vector3 topP = ProjectOnSegment(targetPosition,
|
||||
(topLeft - rightDir * snappOffset.x, topRight - rightDir * snappOffset.y));
|
||||
Vector3 leftP = ProjectOnSegment(targetPosition,
|
||||
(bottomLeft - forwardDir * snappOffset.z, topLeft - forwardDir * snappOffset.w));
|
||||
Vector3 rightP = ProjectOnSegment(targetPosition,
|
||||
(bottomRight + forwardDir * snappOffset.w, topRight + forwardDir * snappOffset.z));
|
||||
|
||||
float bottomDistance = (bottomP - targetPosition).sqrMagnitude;
|
||||
float topDistance = (topP - targetPosition).sqrMagnitude;
|
||||
float leftDistance = (leftP - targetPosition).sqrMagnitude;
|
||||
float rightDistance = (rightP - targetPosition).sqrMagnitude;
|
||||
|
||||
float minDistance = Mathf.Min(bottomDistance,
|
||||
Mathf.Min(topDistance,
|
||||
Mathf.Min(leftDistance, rightDistance)));
|
||||
if (bottomDistance == minDistance)
|
||||
{
|
||||
surfacePoint = bottomP;
|
||||
angle = 0f;
|
||||
return;
|
||||
}
|
||||
if (topDistance == minDistance)
|
||||
{
|
||||
surfacePoint = topP;
|
||||
angle = 180f;
|
||||
return;
|
||||
}
|
||||
if (leftDistance == minDistance)
|
||||
{
|
||||
surfacePoint = leftP;
|
||||
angle = 90f;
|
||||
return;
|
||||
}
|
||||
surfacePoint = rightP;
|
||||
angle = -90f;
|
||||
}
|
||||
|
||||
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, Transform relativeTo)
|
||||
{
|
||||
Quaternion rotation = GetRotation(relativeTo);
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
Vector4 snappOffset = GetSnapOffset(relativeTo);
|
||||
Vector3 desiredPos = userPose.position;
|
||||
Quaternion baseRot = referencePose.rotation;
|
||||
Quaternion desiredRot = userPose.rotation;
|
||||
Vector3 up = rotation * Vector3.up;
|
||||
|
||||
Quaternion bottomRot = baseRot;
|
||||
Quaternion topRot = Quaternion.AngleAxis(180f, up) * baseRot;
|
||||
Quaternion leftRot = Quaternion.AngleAxis(90f, up) * baseRot;
|
||||
Quaternion rightRot = Quaternion.AngleAxis(-90f, up) * baseRot;
|
||||
|
||||
float bottomDot = RotationalScore(bottomRot, desiredRot);
|
||||
float topDot = RotationalScore(topRot, desiredRot);
|
||||
float leftDot = RotationalScore(leftRot, desiredRot);
|
||||
float rightDot = RotationalScore(rightRot, desiredRot);
|
||||
|
||||
Vector3 rightDir = rotation * Vector3.right;
|
||||
Vector3 forwardDir = rotation * Vector3.forward;
|
||||
Vector3 bottomLeft, bottomRight, topLeft, topRight;
|
||||
CalculateCorners(out bottomLeft, out bottomRight, out topLeft, out topRight, relativeTo);
|
||||
|
||||
float maxDot = Mathf.Max(bottomDot, Mathf.Max(topDot, Mathf.Max(leftDot, rightDot)));
|
||||
if (bottomDot == maxDot)
|
||||
{
|
||||
Vector3 projBottom = ProjectOnSegment(desiredPos, (bottomLeft + rightDir * snappOffset.y, bottomRight + rightDir * snappOffset.x));
|
||||
return new Pose(projBottom, bottomRot);
|
||||
}
|
||||
if (topDot == maxDot)
|
||||
{
|
||||
Vector3 projTop = ProjectOnSegment(desiredPos, (topLeft - rightDir * snappOffset.x, topRight - rightDir * snappOffset.y));
|
||||
return new Pose(projTop, topRot);
|
||||
}
|
||||
if (leftDot == maxDot)
|
||||
{
|
||||
Vector3 projLeft = ProjectOnSegment(desiredPos, (bottomLeft - forwardDir * snappOffset.z, topLeft - forwardDir * snappOffset.w));
|
||||
return new Pose(projLeft, leftRot);
|
||||
}
|
||||
Vector3 projRight = ProjectOnSegment(desiredPos, (bottomRight + forwardDir * snappOffset.w, topRight + forwardDir * snappOffset.z));
|
||||
return new Pose(projRight, rightRot);
|
||||
}
|
||||
|
||||
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, Transform relativeTo)
|
||||
{
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
Quaternion rotation = GetRotation(relativeTo);
|
||||
Vector3 desiredPos = userPose.position;
|
||||
Quaternion baseRot = referencePose.rotation;
|
||||
NearestPointAndAngleInSurface(desiredPos, out Vector3 surfacePoint, out float surfaceAngle, relativeTo);
|
||||
Quaternion surfaceRotation = Quaternion.AngleAxis(surfaceAngle, rotation * Vector3.up) * baseRot;
|
||||
return new Pose(surfacePoint, surfaceRotation);
|
||||
}
|
||||
|
||||
private static float RotationalScore(in Quaternion from, in Quaternion to)
|
||||
{
|
||||
float forwardDifference = Vector3.Dot(from * Vector3.forward, to * Vector3.forward) * 0.5f + 0.5f;
|
||||
float upDifference = Vector3.Dot(from * Vector3.up, to * Vector3.up) * 0.5f + 0.5f;
|
||||
return (forwardDifference * upDifference);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllBoxSurface(BoxGrabSurfaceData data, Transform relativeTo)
|
||||
{
|
||||
InjectData(data);
|
||||
InjectRelativeTo(relativeTo);
|
||||
}
|
||||
|
||||
public void InjectData(BoxGrabSurfaceData data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public void InjectRelativeTo(Transform relativeTo)
|
||||
{
|
||||
_relativeTo = relativeTo;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbe6418317e852d4e8fd122a4149acba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.Grab.GrabSurfaces
|
||||
{
|
||||
public class ColliderGrabSurface : MonoBehaviour, IGrabSurface
|
||||
{
|
||||
[SerializeField]
|
||||
private Collider _collider;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.AssertField(_collider, nameof(_collider));
|
||||
}
|
||||
|
||||
private Vector3 NearestPointInSurface(Vector3 targetPosition)
|
||||
{
|
||||
if (_collider.bounds.Contains(targetPosition))
|
||||
{
|
||||
targetPosition = _collider.ClosestPointOnBounds(targetPosition);
|
||||
}
|
||||
return _collider.ClosestPoint(targetPosition);
|
||||
}
|
||||
|
||||
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
|
||||
in PoseMeasureParameters scoringModifier, Transform relativeTo)
|
||||
{
|
||||
Vector3 surfacePoint = NearestPointInSurface(targetPose.position);
|
||||
bestPose = new Pose(surfacePoint, targetPose.rotation);
|
||||
return new GrabPoseScore(surfacePoint, targetPose.position);
|
||||
}
|
||||
|
||||
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose, Transform relativeTo)
|
||||
{
|
||||
if (_collider.Raycast(targetRay, out RaycastHit hit, Mathf.Infinity))
|
||||
{
|
||||
bestPose.position = hit.point;
|
||||
bestPose.rotation = relativeTo.rotation;
|
||||
return true;
|
||||
}
|
||||
bestPose = Pose.identity;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public Pose MirrorPose(in Pose gripPose, Transform relativeTo)
|
||||
{
|
||||
return gripPose;
|
||||
}
|
||||
|
||||
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
|
||||
{
|
||||
return CreateDuplicatedSurface(gameObject);
|
||||
}
|
||||
|
||||
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
|
||||
{
|
||||
ColliderGrabSurface colliderSurface = gameObject.AddComponent<ColliderGrabSurface>();
|
||||
colliderSurface.InjectAllColliderGrabSurface(_collider);
|
||||
return colliderSurface;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
public void InjectAllColliderGrabSurface(Collider collider)
|
||||
{
|
||||
InjectCollider(collider);
|
||||
}
|
||||
|
||||
public void InjectCollider(Collider collider)
|
||||
{
|
||||
_collider = collider;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8951a656a7c00e74094166ef415cdce5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,450 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction.Grab.GrabSurfaces
|
||||
{
|
||||
[Serializable]
|
||||
public class CylinderSurfaceData : ICloneable
|
||||
{
|
||||
public object Clone()
|
||||
{
|
||||
CylinderSurfaceData clone = new CylinderSurfaceData();
|
||||
clone.startPoint = this.startPoint;
|
||||
clone.endPoint = this.endPoint;
|
||||
clone.arcOffset = this.arcOffset;
|
||||
clone.arcLength = this.arcLength;
|
||||
return clone;
|
||||
}
|
||||
|
||||
public CylinderSurfaceData Mirror()
|
||||
{
|
||||
CylinderSurfaceData mirror = Clone() as CylinderSurfaceData;
|
||||
return mirror;
|
||||
}
|
||||
|
||||
public Vector3 startPoint = new Vector3(0f, 0.1f, 0f);
|
||||
public Vector3 endPoint = new Vector3(0f, -0.1f, 0f);
|
||||
|
||||
[Range(0f, 360f)]
|
||||
public float arcOffset = 0f;
|
||||
[Range(0f, 360f)]
|
||||
[FormerlySerializedAs("angle")]
|
||||
public float arcLength = 360f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This type of surface defines a cylinder in which the grip pose is valid around an object.
|
||||
/// An angle and offset can be used to constrain the cylinder and not use a full circle.
|
||||
/// The radius is automatically specified as the distance from the axis of the cylinder to the original grip position.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class CylinderGrabSurface : MonoBehaviour, IGrabSurface
|
||||
{
|
||||
[SerializeField]
|
||||
protected CylinderSurfaceData _data = new CylinderSurfaceData();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Transform used as a reference to measure the local data of the grab surface")]
|
||||
private Transform _relativeTo;
|
||||
|
||||
private Pose RelativePose => PoseUtils.DeltaScaled(_relativeTo, this.transform);
|
||||
private const float Epsilon = 0.000001f;
|
||||
/// <summary>
|
||||
/// The reference pose of the surface. It defines the radius of the cylinder
|
||||
/// as the point from the relative transform to the reference pose to ensure
|
||||
/// that the cylinder covers this pose.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Pose in world space</returns>
|
||||
public Pose GetReferencePose(Transform relativeTo)
|
||||
{
|
||||
return PoseUtils.GlobalPoseScaled(relativeTo, RelativePose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Degrees from the starting radius from which the arc section starts
|
||||
/// </summary>
|
||||
public float ArcOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.arcOffset;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != 0 && value % 360f == 0)
|
||||
{
|
||||
_data.arcOffset = 360f;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data.arcOffset = Mathf.Repeat(value, 360f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum angle for the surface of the cylinder, starting from the ArcOffset.
|
||||
/// To invert the direction of the angle, swap the caps order.
|
||||
/// </summary>
|
||||
public float ArcLength
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.arcLength;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != 0 && value % 360f == 0)
|
||||
{
|
||||
_data.arcLength = 360f;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data.arcLength = Mathf.Repeat(value, 360f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The direction of the main radius of the cylinder. This is the
|
||||
/// radius from the center of the cylinder to the reference position.
|
||||
/// </summary>
|
||||
private Vector3 LocalPerpendicularDir
|
||||
{
|
||||
get
|
||||
{
|
||||
return Vector3.ProjectOnPlane(RelativePose.position - _data.startPoint, LocalDirection).normalized;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The direction of the central axis of the cylinder in local space.
|
||||
/// </summary>
|
||||
private Vector3 LocalDirection
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector3 dir = (_data.endPoint - _data.startPoint);
|
||||
if (dir.sqrMagnitude <= Epsilon)
|
||||
{
|
||||
return Vector3.up;
|
||||
}
|
||||
return dir.normalized;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direction from the axis of the cylinder to the original grip position.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Direction in world space</returns>
|
||||
public Vector3 GetPerpendicularDir(Transform relativeTo)
|
||||
{
|
||||
return relativeTo.TransformDirection(LocalPerpendicularDir);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direction from the axis of the cylinder to the minimum angle allowance.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Direction in world space</returns>
|
||||
public Vector3 GetStartArcDir(Transform relativeTo)
|
||||
{
|
||||
Vector3 localStartArcDir = Quaternion.AngleAxis(ArcOffset, LocalDirection) * LocalPerpendicularDir;
|
||||
return relativeTo.TransformDirection(localStartArcDir);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direction from the axis of the cylinder to the maximum angle allowance.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Direction in world space</returns>
|
||||
public Vector3 GetEndArcDir(Transform relativeTo)
|
||||
{
|
||||
Vector3 localEndArcDir = Quaternion.AngleAxis(ArcLength, LocalDirection) *
|
||||
Quaternion.AngleAxis(ArcOffset, LocalDirection) * LocalPerpendicularDir;
|
||||
return relativeTo.TransformDirection(localEndArcDir);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base cap of the cylinder, in world coordinates.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Position in world space</returns>
|
||||
public Vector3 GetStartPoint(Transform relativeTo)
|
||||
{
|
||||
return relativeTo.TransformPoint(_data.startPoint);
|
||||
}
|
||||
public void SetStartPoint(Vector3 point, Transform relativeTo)
|
||||
{
|
||||
_data.startPoint = relativeTo.InverseTransformPoint(point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End cap of the cylinder, in world coordinates.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Position in world space</returns>
|
||||
public Vector3 GetEndPoint(Transform relativeTo)
|
||||
{
|
||||
return relativeTo.TransformPoint(_data.endPoint);
|
||||
}
|
||||
public void SetEndPoint(Vector3 point, Transform relativeTo)
|
||||
{
|
||||
_data.endPoint = relativeTo.InverseTransformPoint(point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The generated radius of the cylinder.
|
||||
/// Represents the distance from the axis of the cylinder to the original grip position.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Distance in world space</returns>
|
||||
public float GetRadius(Transform relativeTo)
|
||||
{
|
||||
Vector3 start = GetStartPoint(relativeTo);
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
Vector3 direction = GetDirection(relativeTo);
|
||||
Vector3 projectedPoint = start + Vector3.Project(referencePose.position - start, direction);
|
||||
return Vector3.Distance(projectedPoint, referencePose.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direction of the cylinder, from the start cap to the end cap.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Direction in world space</returns>
|
||||
public Vector3 GetDirection(Transform relativeTo)
|
||||
{
|
||||
return relativeTo.TransformDirection(LocalDirection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Length of the cylinder, from the start cap to the end cap.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Distance in world space</returns>
|
||||
private float GetHeight(Transform relativeTo)
|
||||
{
|
||||
Vector3 start = GetStartPoint(relativeTo);
|
||||
Vector3 end = GetEndPoint(relativeTo);
|
||||
return Vector3.Distance(start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rotation of the central axis of the cylinder.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Rotation in world space</returns>
|
||||
private Quaternion GetRotation(Transform relativeTo)
|
||||
{
|
||||
if (_data.startPoint == _data.endPoint)
|
||||
{
|
||||
return relativeTo.rotation;
|
||||
}
|
||||
return relativeTo.rotation * Quaternion.LookRotation(LocalPerpendicularDir, LocalDirection);
|
||||
}
|
||||
|
||||
#region editor events
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_relativeTo = this.GetComponentInParent<IRelativeToRef>()?.RelativeTo;
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.AssertField(_data, nameof(_data));
|
||||
this.AssertField(_relativeTo, nameof(_relativeTo));
|
||||
}
|
||||
|
||||
public Pose MirrorPose(in Pose pose, Transform relativeTo)
|
||||
{
|
||||
Vector3 normal = Quaternion.Inverse(relativeTo.rotation) * GetPerpendicularDir(relativeTo);
|
||||
Vector3 tangent = Quaternion.Inverse(relativeTo.rotation) * GetDirection(relativeTo);
|
||||
|
||||
return pose.MirrorPoseRotation(normal, tangent);
|
||||
}
|
||||
|
||||
private Vector3 PointAltitude(Vector3 point, Transform relativeTo)
|
||||
{
|
||||
Vector3 start = GetStartPoint(relativeTo);
|
||||
Vector3 direction = GetDirection(relativeTo);
|
||||
Vector3 projectedPoint = start + Vector3.Project(point - start, direction);
|
||||
return projectedPoint;
|
||||
}
|
||||
|
||||
|
||||
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
|
||||
in PoseMeasureParameters scoringModifier, Transform relativeTo)
|
||||
{
|
||||
return GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, out bestPose,
|
||||
scoringModifier, relativeTo,
|
||||
MinimalTranslationPoseAtSurface,
|
||||
MinimalRotationPoseAtSurface);
|
||||
}
|
||||
|
||||
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
|
||||
{
|
||||
CylinderGrabSurface surface = gameObject.AddComponent<CylinderGrabSurface>();
|
||||
surface._data = _data.Mirror();
|
||||
return surface;
|
||||
}
|
||||
|
||||
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
|
||||
{
|
||||
CylinderGrabSurface surface = gameObject.AddComponent<CylinderGrabSurface>();
|
||||
surface._data = _data.Clone() as CylinderSurfaceData;
|
||||
return surface;
|
||||
}
|
||||
|
||||
protected Vector3 NearestPointInSurface(Vector3 targetPosition, Transform relativeTo)
|
||||
{
|
||||
Vector3 start = GetStartPoint(relativeTo);
|
||||
Vector3 dir = GetDirection(relativeTo);
|
||||
Vector3 projectedVector = Vector3.Project(targetPosition - start, dir);
|
||||
float height = GetHeight(relativeTo);
|
||||
if (projectedVector.magnitude > height)
|
||||
{
|
||||
projectedVector = projectedVector.normalized * height;
|
||||
}
|
||||
if (Vector3.Dot(projectedVector, dir) < 0f)
|
||||
{
|
||||
projectedVector = Vector3.zero;
|
||||
}
|
||||
|
||||
Vector3 projectedPoint = start + projectedVector;
|
||||
Vector3 targetDirection = Vector3.ProjectOnPlane((targetPosition - projectedPoint), dir).normalized;
|
||||
|
||||
Vector3 startArcDir = GetStartArcDir(relativeTo);
|
||||
float desiredAngle = Mathf.Repeat(Vector3.SignedAngle(startArcDir, targetDirection, dir), 360f);
|
||||
if (desiredAngle > ArcLength)
|
||||
{
|
||||
if (Mathf.Abs(desiredAngle - ArcLength) >= Mathf.Abs(360f - desiredAngle))
|
||||
{
|
||||
targetDirection = startArcDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetDirection = GetEndArcDir(relativeTo);
|
||||
}
|
||||
}
|
||||
Vector3 surfacePoint = projectedPoint + targetDirection * GetRadius(relativeTo);
|
||||
return surfacePoint;
|
||||
}
|
||||
|
||||
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose,
|
||||
Transform relativeTo)
|
||||
{
|
||||
Pose recordedPose = GetReferencePose(relativeTo);
|
||||
Vector3 start = GetStartPoint(relativeTo);
|
||||
Vector3 direction = GetDirection(relativeTo);
|
||||
Vector3 lineToCylinder = start - targetRay.origin;
|
||||
|
||||
float perpendiculiarity = Vector3.Dot(targetRay.direction, direction);
|
||||
float rayToLineDiff = Vector3.Dot(lineToCylinder, targetRay.direction);
|
||||
float cylinderToLineDiff = Vector3.Dot(lineToCylinder, direction);
|
||||
|
||||
float determinant = 1f / (perpendiculiarity * perpendiculiarity - 1f);
|
||||
|
||||
float lineOffset = (perpendiculiarity * cylinderToLineDiff - rayToLineDiff) * determinant;
|
||||
float cylinderOffset = (cylinderToLineDiff - perpendiculiarity * rayToLineDiff) * determinant;
|
||||
|
||||
float radius = GetRadius(relativeTo);
|
||||
Vector3 pointInLine = targetRay.origin + targetRay.direction * lineOffset;
|
||||
Vector3 pointInCylinder = start + direction * cylinderOffset;
|
||||
float distanceToSurface = Mathf.Max(Vector3.Distance(pointInCylinder, pointInLine) - radius);
|
||||
if (distanceToSurface < radius)
|
||||
{
|
||||
float adjustedDistance = Mathf.Sqrt(radius * radius - distanceToSurface * distanceToSurface);
|
||||
pointInLine -= targetRay.direction * adjustedDistance;
|
||||
}
|
||||
Vector3 surfacePoint = NearestPointInSurface(pointInLine, relativeTo);
|
||||
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
|
||||
bestPose = MinimalTranslationPoseAtSurface(desiredPose, relativeTo);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, Transform relativeTo)
|
||||
{
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
Vector3 direction = GetDirection(relativeTo);
|
||||
Quaternion rotation = GetRotation(relativeTo);
|
||||
float radius = GetRadius(relativeTo);
|
||||
Vector3 desiredPos = userPose.position;
|
||||
Quaternion desiredRot = userPose.rotation;
|
||||
Quaternion baseRot = referencePose.rotation;
|
||||
Quaternion rotDif = (desiredRot) * Quaternion.Inverse(baseRot);
|
||||
Vector3 desiredDirection = (rotDif * rotation) * Vector3.forward;
|
||||
Vector3 projectedDirection = Vector3.ProjectOnPlane(desiredDirection, direction).normalized;
|
||||
Vector3 altitudePoint = PointAltitude(desiredPos, relativeTo);
|
||||
Vector3 surfacePoint = NearestPointInSurface(altitudePoint + projectedDirection * radius, relativeTo);
|
||||
Quaternion surfaceRotation = CalculateRotationOffset(surfacePoint, relativeTo) * baseRot;
|
||||
return new Pose(surfacePoint, surfaceRotation);
|
||||
}
|
||||
|
||||
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, Transform relativeTo)
|
||||
{
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
Vector3 desiredPos = userPose.position;
|
||||
Quaternion baseRot = referencePose.rotation;
|
||||
Vector3 surfacePoint = NearestPointInSurface(desiredPos, relativeTo);
|
||||
Quaternion surfaceRotation = CalculateRotationOffset(surfacePoint, relativeTo) * baseRot;
|
||||
|
||||
return new Pose(surfacePoint, surfaceRotation);
|
||||
}
|
||||
|
||||
protected Quaternion CalculateRotationOffset(Vector3 surfacePoint, Transform relativeTo)
|
||||
{
|
||||
Vector3 start = GetStartPoint(relativeTo);
|
||||
Vector3 direction = GetDirection(relativeTo);
|
||||
Vector3 referenceDir = GetPerpendicularDir(relativeTo);
|
||||
Vector3 recordedDirection = Vector3.ProjectOnPlane(referenceDir, direction);
|
||||
Vector3 desiredDirection = Vector3.ProjectOnPlane(surfacePoint - start, direction);
|
||||
return Quaternion.FromToRotation(recordedDirection, desiredDirection);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllCylinderSurface(CylinderSurfaceData data,
|
||||
Transform relativeTo)
|
||||
{
|
||||
InjectData(data);
|
||||
InjectRelativeTo(relativeTo);
|
||||
}
|
||||
|
||||
public void InjectData(CylinderSurfaceData data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public void InjectRelativeTo(Transform relativeTo)
|
||||
{
|
||||
_relativeTo = relativeTo;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ab0e0bf5507fad4a9def20ce63299e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.Grab
|
||||
{
|
||||
public static class GrabPoseHelper
|
||||
{
|
||||
public delegate Pose PoseCalculator(in Pose desiredPose, Transform relativeTo);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the best pose comparing the one that requires the minimum rotation
|
||||
/// and minimum translation.
|
||||
/// </summary>
|
||||
/// <param name="desiredPose">Pose to measure from.</param>
|
||||
/// <param name="bestPose">Nearest pose to the desired one at the hand grab pose.</param>
|
||||
/// <param name="scoringModifier">Modifiers for the score based in rotation and distance.</param>
|
||||
/// <param name="relativeTo">The reference transform to apply the calculators to</param>
|
||||
/// <param name="minimalTranslationPoseCalculator">Delegate to calculate the nearest, by position, pose at a hand grab pose.</param>
|
||||
/// <param name="minimalRotationPoseCalculator">Delegate to calculate the nearest, by rotation, pose at a hand grab pose.</param>
|
||||
/// <returns>The score, normalized, of the best pose.</returns>
|
||||
public static GrabPoseScore CalculateBestPoseAtSurface(in Pose desiredPose, out Pose bestPose,
|
||||
in PoseMeasureParameters scoringModifier, Transform relativeTo,
|
||||
PoseCalculator minimalTranslationPoseCalculator, PoseCalculator minimalRotationPoseCalculator)
|
||||
{
|
||||
if (scoringModifier.PositionRotationWeight == 1f)
|
||||
{
|
||||
bestPose = minimalRotationPoseCalculator(desiredPose, relativeTo);
|
||||
return new GrabPoseScore(desiredPose, bestPose, 1f);
|
||||
}
|
||||
|
||||
if (scoringModifier.PositionRotationWeight == 0f)
|
||||
{
|
||||
bestPose = minimalTranslationPoseCalculator(desiredPose, relativeTo);
|
||||
return new GrabPoseScore(desiredPose, bestPose, 0f);
|
||||
}
|
||||
|
||||
Pose minimalTranslationPose = minimalTranslationPoseCalculator(desiredPose, relativeTo);
|
||||
Pose minimalRotationPose = minimalRotationPoseCalculator(desiredPose, relativeTo);
|
||||
bestPose = SelectBestPose(minimalRotationPose, minimalTranslationPose,
|
||||
desiredPose, scoringModifier, out GrabPoseScore bestScore);
|
||||
return bestScore;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two poses to a reference and returns the most similar one
|
||||
/// </summary>
|
||||
/// <param name="poseA">First Pose to compare with the reference.</param>
|
||||
/// <param name="poseB">Second Pose to compare with the reference.</param>
|
||||
/// <param name="reference">Reference pose to measure from.</param>
|
||||
/// <param name="scoringModifier">Modifiers for the score based in rotation and distance.</param>
|
||||
/// <param name="bestScore">Out value with the score of the best pose.</param>
|
||||
/// <returns>The most similar pose to reference out of the poses</returns>
|
||||
public static Pose SelectBestPose(in Pose poseA, in Pose poseB, in Pose reference,
|
||||
PoseMeasureParameters scoringModifier, out GrabPoseScore bestScore)
|
||||
{
|
||||
GrabPoseScore poseAScore = new GrabPoseScore(reference, poseA,
|
||||
scoringModifier.PositionRotationWeight);
|
||||
GrabPoseScore poseBScore = new GrabPoseScore(reference, poseB,
|
||||
scoringModifier.PositionRotationWeight);
|
||||
|
||||
if (poseAScore.IsBetterThan(poseBScore))
|
||||
{
|
||||
bestScore = poseAScore;
|
||||
return poseA;
|
||||
}
|
||||
else
|
||||
{
|
||||
bestScore = poseBScore;
|
||||
return poseB;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score from a point to a set of colliders.
|
||||
/// When the point is outside the colliders the further from their surface means the
|
||||
/// lower the score.
|
||||
/// When the point is inside any of the colliders the score is always higher.
|
||||
/// </summary>
|
||||
/// <param name="position">Position to measure against the colliders</param>
|
||||
/// <param name="colliders">Group of colliders to measure the score</param>
|
||||
/// <param name="hitPoint">Output point in the surface or inside the colliders that is near the position</param>
|
||||
/// <returns>A GrabPoseScore value containing the score of the position in reference to the colliders</returns>
|
||||
public static GrabPoseScore CollidersScore(Vector3 position, Collider[] colliders,
|
||||
out Vector3 hitPoint)
|
||||
{
|
||||
GrabPoseScore bestScore = GrabPoseScore.Max;
|
||||
GrabPoseScore score;
|
||||
hitPoint = position;
|
||||
foreach (Collider collider in colliders)
|
||||
{
|
||||
bool isPointInsideCollider = Collisions.IsPointWithinCollider(position, collider);
|
||||
Vector3 measuringPoint = isPointInsideCollider ? collider.bounds.center
|
||||
: collider.ClosestPoint(position);
|
||||
|
||||
score = new GrabPoseScore(position, measuringPoint,
|
||||
isPointInsideCollider);
|
||||
|
||||
if (score.IsBetterThan(bestScore))
|
||||
{
|
||||
hitPoint = isPointInsideCollider ? position : measuringPoint;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cada41609b5284144aef14e522e6351a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.Grab.GrabSurfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface defines the method needed to use grab surfaces. They allow finding the
|
||||
/// nearest poses at the surface to a given set of parameters as well as duplicating and
|
||||
/// mirroring the surface.
|
||||
/// </summary>
|
||||
public interface IGrabSurface
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the Pose at the surface that is the closest to the given pose.
|
||||
/// </summary>
|
||||
/// <param name="targetPose">The pose to find the nearest to.</param>
|
||||
/// <param name="bestPose">The best found pose at the surface.<</param>
|
||||
/// <param name="scoringModifier">Weight used to decide which target pose to select</param>
|
||||
/// <param name="relativeTo">Reference transform to measure the poses against</param>
|
||||
/// <returns>The score indicating how good the found pose was, -1 for invalid result.</returns>
|
||||
GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
|
||||
in PoseMeasureParameters scoringModifier, Transform relativeTo);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the Pose at the surface that is the closest to the given ray.
|
||||
/// </summary>
|
||||
/// <param name="targetRay">Ray searching for the nearest snap pose</param>
|
||||
/// <param name="bestPose">The best found pose at the surface.</param>
|
||||
/// <param name="relativeTo">Reference transform to measure the poses against</param>
|
||||
/// <returns>True if the pose was found</returns>
|
||||
bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose,
|
||||
Transform relativeTo);
|
||||
|
||||
/// <summary>
|
||||
/// Method for mirroring a Pose around the surface.
|
||||
/// Different surfaces will prefer mirroring along different axis.
|
||||
/// </summary>
|
||||
/// <param name="gripPose">The Pose to be mirrored.</param>
|
||||
/// <param name="relativeTo">Reference transform to mirror the pose around</param>
|
||||
/// <returns>A new pose mirrored at this surface.</returns>
|
||||
Pose MirrorPose(in Pose gripPose, Transform relativeTo);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new IGrabSurface under the selected gameobject
|
||||
/// that is a mirror version of the current.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The gameobject in which to place the new IGrabSurface.</param>
|
||||
/// <returns>A mirror of this IGrabSurface.</returns>
|
||||
IGrabSurface CreateMirroredSurface(GameObject gameObject);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new IGrabSurface under the selected gameobject
|
||||
/// with the same data as this one.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The gameobject in which to place the new IGrabSurface.</param>
|
||||
/// <returns>A clone of this IGrabSurface.</returns>
|
||||
IGrabSurface CreateDuplicatedSurface(GameObject gameObject);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b84868e1fec742746b4f97a01bf836af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.Grab.GrabSurfaces
|
||||
{
|
||||
[Serializable]
|
||||
public class SphereGrabSurfaceData : ICloneable
|
||||
{
|
||||
public object Clone()
|
||||
{
|
||||
SphereGrabSurfaceData clone = new SphereGrabSurfaceData();
|
||||
clone.centre = this.centre;
|
||||
return clone;
|
||||
}
|
||||
|
||||
public SphereGrabSurfaceData Mirror()
|
||||
{
|
||||
SphereGrabSurfaceData mirror = Clone() as SphereGrabSurfaceData;
|
||||
return mirror;
|
||||
}
|
||||
|
||||
public Vector3 centre = Vector3.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies an entire sphere around an object in which the grip point is valid.
|
||||
/// One of the main advantages of spheres is that the rotation of the hand pose does
|
||||
/// not really matters, as it will always fit the surface correctly.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SphereGrabSurface : MonoBehaviour, IGrabSurface
|
||||
{
|
||||
[SerializeField]
|
||||
protected SphereGrabSurfaceData _data = new SphereGrabSurfaceData();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Transform used as a reference to measure the local data of the grab surface")]
|
||||
private Transform _relativeTo;
|
||||
|
||||
private Pose RelativePose => PoseUtils.DeltaScaled(_relativeTo, this.transform);
|
||||
|
||||
/// <summary>
|
||||
/// The reference pose of the surface. It defines the radius of the sphere
|
||||
/// as the point from the relative transform to the reference pose to ensure
|
||||
/// that the sphere covers this pose.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Pose in world space</returns>
|
||||
public Pose GetReferencePose(Transform relativeTo)
|
||||
{
|
||||
return PoseUtils.GlobalPoseScaled(relativeTo, RelativePose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The center of the sphere in world coordinates.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Position in world space</returns>
|
||||
public Vector3 GetCentre(Transform relativeTo)
|
||||
{
|
||||
return relativeTo.TransformPoint(_data.centre);
|
||||
}
|
||||
public void SetCentre(Vector3 point, Transform relativeTo)
|
||||
{
|
||||
_data.centre = relativeTo.InverseTransformPoint(point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the sphere, this is automatically calculated as the distance between
|
||||
/// the center and the original grip pose.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Distance in world space</returns>
|
||||
public float GetRadius(Transform relativeTo)
|
||||
{
|
||||
Vector3 centre = GetCentre(relativeTo);
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
return Vector3.Distance(centre, referencePose.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The direction of the sphere, measured from the center to the original grip position.
|
||||
/// </summary>
|
||||
/// <param name="relativeTo">The reference transform to apply the surface to</param>
|
||||
/// <returns>Direction in world space</returns>
|
||||
public Vector3 GetDirection(Transform relativeTo)
|
||||
{
|
||||
Vector3 centre = GetCentre(relativeTo);
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
return (referencePose.position - centre).normalized;
|
||||
}
|
||||
|
||||
#region editor events
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_relativeTo = this.GetComponentInParent<IRelativeToRef>()?.RelativeTo;
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.AssertField(_data, nameof(_data));
|
||||
this.AssertField(_relativeTo, nameof(_relativeTo));
|
||||
}
|
||||
|
||||
public Pose MirrorPose(in Pose pose, Transform relativeTo)
|
||||
{
|
||||
Vector3 normal = Quaternion.Inverse(relativeTo.rotation) * GetDirection(relativeTo);
|
||||
Vector3 tangent = Vector3.Cross(normal, Vector3.up);
|
||||
return pose.MirrorPoseRotation(normal, tangent);
|
||||
}
|
||||
|
||||
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose, Transform relativeTo)
|
||||
{
|
||||
Vector3 centre = GetCentre(relativeTo);
|
||||
Vector3 projection = Vector3.Project(centre - targetRay.origin, targetRay.direction);
|
||||
Vector3 nearestCentre = targetRay.origin + projection;
|
||||
float radius = GetRadius(relativeTo);
|
||||
float distanceToSurface = Mathf.Max(Vector3.Distance(centre, nearestCentre) - radius);
|
||||
if (distanceToSurface < radius)
|
||||
{
|
||||
float adjustedDistance = Mathf.Sqrt(radius * radius - distanceToSurface * distanceToSurface);
|
||||
nearestCentre -= targetRay.direction * adjustedDistance;
|
||||
}
|
||||
|
||||
Pose recordedPose = GetReferencePose(relativeTo);
|
||||
Vector3 surfacePoint = NearestPointInSurface(nearestCentre, relativeTo);
|
||||
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
|
||||
bestPose = MinimalTranslationPoseAtSurface(desiredPose, relativeTo);
|
||||
return true;
|
||||
}
|
||||
|
||||
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
|
||||
in PoseMeasureParameters scoringModifier, Transform relativeTo)
|
||||
{
|
||||
return GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, out bestPose,
|
||||
scoringModifier, relativeTo,
|
||||
MinimalTranslationPoseAtSurface,
|
||||
MinimalRotationPoseAtSurface);
|
||||
}
|
||||
|
||||
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
|
||||
{
|
||||
SphereGrabSurface surface = gameObject.AddComponent<SphereGrabSurface>();
|
||||
surface._data = _data.Mirror();
|
||||
return surface;
|
||||
}
|
||||
|
||||
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
|
||||
{
|
||||
SphereGrabSurface surface = gameObject.AddComponent<SphereGrabSurface>();
|
||||
surface._data = _data;
|
||||
return surface;
|
||||
}
|
||||
|
||||
protected Vector3 NearestPointInSurface(Vector3 targetPosition, Transform relativeTo)
|
||||
{
|
||||
Vector3 centre = GetCentre(relativeTo);
|
||||
Vector3 direction = (targetPosition - centre).normalized;
|
||||
float radius = GetRadius(relativeTo);
|
||||
return centre + direction * radius;
|
||||
}
|
||||
|
||||
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, Transform relativeTo)
|
||||
{
|
||||
Vector3 centre = GetCentre(relativeTo);
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
float radius = GetRadius(relativeTo);
|
||||
Quaternion rotCorrection = userPose.rotation * Quaternion.Inverse(referencePose.rotation);
|
||||
Vector3 correctedDir = rotCorrection * GetDirection(relativeTo);
|
||||
Vector3 surfacePoint = NearestPointInSurface(centre + correctedDir * radius, relativeTo);
|
||||
Quaternion surfaceRotation = RotationAtPoint(surfacePoint, referencePose.rotation, userPose.rotation, relativeTo);
|
||||
|
||||
return new Pose(surfacePoint, surfaceRotation);
|
||||
}
|
||||
|
||||
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, Transform relativeTo)
|
||||
{
|
||||
Pose referencePose = GetReferencePose(relativeTo);
|
||||
Vector3 desiredPos = userPose.position;
|
||||
Quaternion baseRot = referencePose.rotation;
|
||||
Vector3 surfacePoint = NearestPointInSurface(desiredPos, relativeTo);
|
||||
Quaternion surfaceRotation = RotationAtPoint(surfacePoint, baseRot, userPose.rotation, relativeTo);
|
||||
return new Pose(surfacePoint, surfaceRotation);
|
||||
}
|
||||
|
||||
protected Quaternion RotationAtPoint(Vector3 surfacePoint,
|
||||
Quaternion baseRot, Quaternion desiredRotation,
|
||||
Transform relativeTo)
|
||||
{
|
||||
Vector3 desiredDirection = (surfacePoint - GetCentre(relativeTo)).normalized;
|
||||
Quaternion targetRotation = Quaternion.FromToRotation(GetDirection(relativeTo), desiredDirection) * baseRot;
|
||||
Vector3 targetProjected = Vector3.ProjectOnPlane(targetRotation * Vector3.forward, desiredDirection).normalized;
|
||||
Vector3 desiredProjected = Vector3.ProjectOnPlane(desiredRotation * Vector3.forward, desiredDirection).normalized;
|
||||
Quaternion rotCorrection = Quaternion.FromToRotation(targetProjected, desiredProjected);
|
||||
return rotCorrection * targetRotation;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllSphereSurface(SphereGrabSurfaceData data, Transform relativeTo)
|
||||
{
|
||||
InjectData(data);
|
||||
InjectRelativeTo(relativeTo);
|
||||
}
|
||||
|
||||
public void InjectData(SphereGrabSurfaceData data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public void InjectRelativeTo(Transform relativeTo)
|
||||
{
|
||||
_relativeTo = relativeTo;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18467ddce2adbac48aa374514e13d1ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9a4ddf491163674cb05725c06497aa7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Grab;
|
||||
using Oculus.Interaction.HandGrab;
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class GrabStrengthIndicator : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Interface(typeof(IHandGrabInteractor), typeof(IInteractor))]
|
||||
private UnityEngine.Object _handGrabInteractor;
|
||||
private IHandGrabInteractor HandGrab { get; set; }
|
||||
private IInteractor Interactor { get; set; }
|
||||
|
||||
[SerializeField]
|
||||
private MaterialPropertyBlockEditor _handMaterialPropertyBlockEditor;
|
||||
|
||||
[SerializeField]
|
||||
private float _glowLerpSpeed = 2f;
|
||||
[SerializeField]
|
||||
private float _glowColorLerpSpeed = 2f;
|
||||
|
||||
[SerializeField]
|
||||
private Color _fingerGlowColorWithInteractable;
|
||||
[SerializeField]
|
||||
private Color _fingerGlowColorWithNoInteractable;
|
||||
[SerializeField]
|
||||
private Color _fingerGlowColorHover;
|
||||
|
||||
#region public properties
|
||||
public float GlowLerpSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
return _glowLerpSpeed;
|
||||
}
|
||||
set
|
||||
{
|
||||
_glowLerpSpeed = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float GlowColorLerpSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
return _glowColorLerpSpeed;
|
||||
}
|
||||
set
|
||||
{
|
||||
_glowColorLerpSpeed = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Color FingerGlowColorWithInteractable
|
||||
{
|
||||
get
|
||||
{
|
||||
return _fingerGlowColorWithInteractable;
|
||||
}
|
||||
set
|
||||
{
|
||||
_fingerGlowColorWithInteractable = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Color FingerGlowColorWithNoInteractable
|
||||
{
|
||||
get
|
||||
{
|
||||
return _fingerGlowColorWithNoInteractable;
|
||||
}
|
||||
set
|
||||
{
|
||||
_fingerGlowColorWithNoInteractable = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Color FingerGlowColorHover
|
||||
{
|
||||
get
|
||||
{
|
||||
return _fingerGlowColorHover;
|
||||
}
|
||||
set
|
||||
{
|
||||
_fingerGlowColorHover = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private readonly int[] _handShaderGlowPropertyIds = new int[]
|
||||
{
|
||||
Shader.PropertyToID("_ThumbGlowValue"),
|
||||
Shader.PropertyToID("_IndexGlowValue"),
|
||||
Shader.PropertyToID("_MiddleGlowValue"),
|
||||
Shader.PropertyToID("_RingGlowValue"),
|
||||
Shader.PropertyToID("_PinkyGlowValue"),
|
||||
};
|
||||
|
||||
private readonly int _fingerGlowColorPropertyId = Shader.PropertyToID("_FingerGlowColor");
|
||||
|
||||
private Color _currentGlowColor;
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
HandGrab = _handGrabInteractor as IHandGrabInteractor;
|
||||
Interactor = _handGrabInteractor as IInteractor;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
|
||||
this.AssertField(_handMaterialPropertyBlockEditor, nameof(_handMaterialPropertyBlockEditor));
|
||||
this.AssertField(HandGrab, nameof(HandGrab));
|
||||
this.AssertField(Interactor, nameof(Interactor));
|
||||
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
Interactor.WhenPostprocessed += UpdateVisual;
|
||||
_currentGlowColor = _fingerGlowColorWithNoInteractable;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
Interactor.WhenPostprocessed -= UpdateVisual;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisual()
|
||||
{
|
||||
bool isSelecting = Interactor.State == InteractorState.Select;
|
||||
bool isSelectingInteractable = Interactor.HasSelectedInteractable;
|
||||
bool hasHoverTarget = Interactor.HasCandidate;
|
||||
|
||||
Color desiredGlowColor = _fingerGlowColorHover;
|
||||
if (isSelecting)
|
||||
{
|
||||
desiredGlowColor = isSelectingInteractable
|
||||
? _fingerGlowColorWithInteractable
|
||||
: _fingerGlowColorWithNoInteractable;
|
||||
}
|
||||
|
||||
_currentGlowColor = Color.Lerp(_currentGlowColor, desiredGlowColor,
|
||||
Time.deltaTime * _glowColorLerpSpeed);
|
||||
_handMaterialPropertyBlockEditor.MaterialPropertyBlock.SetColor(_fingerGlowColorPropertyId, _currentGlowColor);
|
||||
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; ++i)
|
||||
{
|
||||
if ((isSelecting && !isSelectingInteractable) ||
|
||||
(!isSelecting && !hasHoverTarget))
|
||||
{
|
||||
UpdateGlowValue(i, 0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
float glowValue = 0f;
|
||||
HandFinger finger = (HandFinger)i;
|
||||
if ((HandGrab.SupportedGrabTypes & GrabTypeFlags.Pinch) != 0
|
||||
&& HandGrab.TargetInteractable != null
|
||||
&& (HandGrab.TargetInteractable.SupportedGrabTypes & GrabTypeFlags.Pinch) != 0
|
||||
&& HandGrab.TargetInteractable.PinchGrabRules[finger] != GrabAPI.FingerRequirement.Ignored)
|
||||
{
|
||||
glowValue = Mathf.Max(glowValue, HandGrab.HandGrabApi.GetFingerPinchStrength(finger));
|
||||
}
|
||||
|
||||
if ((HandGrab.SupportedGrabTypes & GrabTypeFlags.Palm) != 0
|
||||
&& HandGrab.TargetInteractable != null
|
||||
&& (HandGrab.TargetInteractable.SupportedGrabTypes & GrabTypeFlags.Palm) != 0
|
||||
&& HandGrab.TargetInteractable.PalmGrabRules[finger] != GrabAPI.FingerRequirement.Ignored)
|
||||
{
|
||||
glowValue = Mathf.Max(glowValue, HandGrab.HandGrabApi.GetFingerPalmStrength(finger));
|
||||
}
|
||||
|
||||
UpdateGlowValue(i, glowValue);
|
||||
}
|
||||
|
||||
_handMaterialPropertyBlockEditor.UpdateMaterialPropertyBlock();
|
||||
}
|
||||
|
||||
private void UpdateGlowValue(int fingerIndex, float glowValue)
|
||||
{
|
||||
float currentGlowValue = _handMaterialPropertyBlockEditor.MaterialPropertyBlock.GetFloat(_handShaderGlowPropertyIds[fingerIndex]);
|
||||
float newGlowValue = Mathf.MoveTowards(currentGlowValue, glowValue, _glowLerpSpeed * Time.deltaTime);
|
||||
_handMaterialPropertyBlockEditor.MaterialPropertyBlock.SetFloat(_handShaderGlowPropertyIds[fingerIndex], newGlowValue);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllGrabStrengthIndicator(IHandGrabInteractor handGrabInteractor,
|
||||
MaterialPropertyBlockEditor handMaterialPropertyBlockEditor)
|
||||
{
|
||||
InjectHandGrab(handGrabInteractor);
|
||||
InjectHandMaterialPropertyBlockEditor(handMaterialPropertyBlockEditor);
|
||||
}
|
||||
|
||||
public void InjectHandGrab(IHandGrabInteractor handGrab)
|
||||
{
|
||||
_handGrabInteractor = handGrab as UnityEngine.Object;
|
||||
HandGrab = handGrab;
|
||||
Interactor = handGrab as IInteractor;
|
||||
}
|
||||
|
||||
public void InjectHandMaterialPropertyBlockEditor(MaterialPropertyBlockEditor handMaterialPropertyBlockEditor)
|
||||
{
|
||||
_handMaterialPropertyBlockEditor = handMaterialPropertyBlockEditor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 887c8dbd7b891004dbddaf2d9f439564
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandGrab.Visuals
|
||||
{
|
||||
/// <summary>
|
||||
/// A static (non-user controlled) representation of a hand. This script is used
|
||||
/// to be able to manually visualize hand grab poses.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(HandPuppet))]
|
||||
public class HandGhost : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The puppet is used to actually move the representation of the hand.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private HandPuppet _puppet;
|
||||
|
||||
/// <summary>
|
||||
/// The HandGrab point can be set so the ghost automatically
|
||||
/// adopts the desired pose of said point.
|
||||
/// </summary>
|
||||
[SerializeField, Optional]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_handGrabPoint")]
|
||||
private HandGrabPose _handGrabPose;
|
||||
|
||||
#region editor events
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_puppet = this.GetComponent<HandPuppet>();
|
||||
_handGrabPose = this.GetComponentInParent<HandGrabPose>();
|
||||
}
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
if (_puppet == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_handGrabPose == null)
|
||||
{
|
||||
HandGrabPose point = this.GetComponentInParent<HandGrabPose>();
|
||||
if (point != null)
|
||||
{
|
||||
SetPose(point);
|
||||
}
|
||||
}
|
||||
else if (_handGrabPose != null)
|
||||
{
|
||||
SetPose(_handGrabPose);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.AssertField(_puppet, nameof(_puppet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relay to the Puppet to set the ghost hand to the desired static pose
|
||||
/// </summary>
|
||||
/// <param name="handGrabPose">The point to read the HandPose from</param>
|
||||
public void SetPose(HandGrabPose handGrabPose)
|
||||
{
|
||||
HandPose userPose = handGrabPose.HandPose;
|
||||
if (userPose == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_puppet.SetJointRotations(userPose.JointRotations);
|
||||
SetRootPose(handGrabPose.RelativePose, handGrabPose.RelativeTo);
|
||||
}
|
||||
|
||||
public void SetPose(HandPose userPose, Pose rootPose)
|
||||
{
|
||||
_puppet.SetJointRotations(userPose.JointRotations);
|
||||
_puppet.SetRootPose(rootPose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the underlying puppet so the wrist point aligns with the given parameters
|
||||
/// </summary>
|
||||
/// <param name="rootPose">The relative wrist pose to align the hand to</param>
|
||||
/// <param name="relativeTo">The object to use as anchor</param>
|
||||
public void SetRootPose(Pose rootPose, Transform relativeTo)
|
||||
{
|
||||
Pose pose = rootPose;
|
||||
if (relativeTo != null)
|
||||
{
|
||||
pose = PoseUtils.GlobalPoseScaled(relativeTo, rootPose);
|
||||
}
|
||||
_puppet.SetRootPose(pose);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
public void InjectAllHandGhost(HandPuppet puppet)
|
||||
{
|
||||
InjectHandPuppet(puppet);
|
||||
}
|
||||
public void InjectHandPuppet(HandPuppet puppet)
|
||||
{
|
||||
_puppet = puppet;
|
||||
}
|
||||
public void InjectOptionalHandGrabPose(HandGrabPose handGrabPose)
|
||||
{
|
||||
_handGrabPose = handGrabPose;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d179fd81cd2e344ab2e610cd5f7260e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandGrab.Visuals
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds references to the prefabs for Ghost-Hands, so they can be instantiated
|
||||
/// in runtime to represent static poses.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Oculus/Interaction/SDK/Pose Authoring/Hand Ghost Provider")]
|
||||
public class HandGhostProvider : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The prefab for the left hand ghost.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private HandGhost _leftHand;
|
||||
/// <summary>
|
||||
/// The prefab for the right hand ghost.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private HandGhost _rightHand;
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to obtain the prototypes
|
||||
/// The result is to be instanced, not used directly.
|
||||
/// </summary>
|
||||
/// <param name="handedness">The desired handedness of the ghost prefab</param>
|
||||
/// <returns>A Ghost prefab</returns>
|
||||
public HandGhost GetHand(Handedness handedness)
|
||||
{
|
||||
return handedness == Handedness.Left ? _leftHand : _rightHand;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84de3b22a7efbab46967c1a17f5b8cda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandGrab
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a HandGrabState and applies hand visual constraints to a SyntheticHand
|
||||
/// </summary>
|
||||
public class HandGrabStateVisual : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Interface(typeof(IHandGrabState))]
|
||||
private UnityEngine.Object _handGrabState;
|
||||
|
||||
private IHandGrabState HandGrabState;
|
||||
|
||||
[SerializeField]
|
||||
private SyntheticHand _syntheticHand;
|
||||
|
||||
private bool _areFingersFree = true;
|
||||
private bool _isWristFree = true;
|
||||
private bool _wasCompletelyFree = true;
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
HandGrabState = _handGrabState as IHandGrabState;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
this.AssertField(HandGrabState, nameof(HandGrabState));
|
||||
this.AssertField(_syntheticHand, nameof(_syntheticHand));
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
ConstrainingForce(HandGrabState, out float fingersConstraint, out float wristConstraint);
|
||||
UpdateHandPose(HandGrabState, fingersConstraint, wristConstraint);
|
||||
|
||||
bool isCompletelyFree = _areFingersFree && _isWristFree;
|
||||
if (!isCompletelyFree
|
||||
|| isCompletelyFree && !_wasCompletelyFree)
|
||||
{
|
||||
_syntheticHand.MarkInputDataRequiresUpdate();
|
||||
}
|
||||
_wasCompletelyFree = isCompletelyFree;
|
||||
}
|
||||
|
||||
private void ConstrainingForce(IHandGrabState grabSource, out float fingersConstraint, out float wristConstraint)
|
||||
{
|
||||
HandGrabTarget grabData = grabSource.HandGrabTarget;
|
||||
|
||||
fingersConstraint = wristConstraint = 0;
|
||||
if (grabData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isGrabbing = grabSource.IsGrabbing;
|
||||
if (isGrabbing && grabData.HandAlignment != HandAlignType.None)
|
||||
{
|
||||
fingersConstraint = grabSource.FingersStrength;
|
||||
wristConstraint = grabSource.WristStrength;
|
||||
}
|
||||
else if (grabData.HandAlignment == HandAlignType.AttractOnHover)
|
||||
{
|
||||
fingersConstraint = grabSource.FingersStrength;
|
||||
wristConstraint = grabSource.WristStrength;
|
||||
}
|
||||
else if (grabData.HandAlignment == HandAlignType.AlignFingersOnHover)
|
||||
{
|
||||
fingersConstraint = grabSource.FingersStrength;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHandPose(IHandGrabState grabSource, float fingersConstraint, float wristConstraint)
|
||||
{
|
||||
HandGrabTarget grabTarget = grabSource.HandGrabTarget;
|
||||
|
||||
if (grabTarget == null)
|
||||
{
|
||||
FreeFingers();
|
||||
FreeWrist();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fingersConstraint > 0f
|
||||
&& grabTarget.HandPose != null)
|
||||
{
|
||||
UpdateFingers(grabTarget.HandPose, grabSource.GrabbingFingers(), fingersConstraint);
|
||||
_areFingersFree = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
FreeFingers();
|
||||
}
|
||||
|
||||
if (wristConstraint > 0f)
|
||||
{
|
||||
Pose wristPose = grabSource.GetVisualWristPose();
|
||||
_syntheticHand.LockWristPose(wristPose, wristConstraint,
|
||||
SyntheticHand.WristLockMode.Full, true);
|
||||
_isWristFree = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
FreeWrist();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the desired rotation values for each joint based on the provided HandGrabState.
|
||||
/// Apart from the rotations it also writes in the syntheticHand if it should allow rotations
|
||||
/// past that.
|
||||
/// When no snap is provided, it frees all fingers allowing unconstrained tracked motion.
|
||||
/// </summary>
|
||||
private void UpdateFingers(HandPose handPose, HandFingerFlags grabbingFingers, float strength)
|
||||
{
|
||||
Quaternion[] desiredRotations = handPose.JointRotations;
|
||||
_syntheticHand.OverrideAllJoints(desiredRotations, strength);
|
||||
|
||||
for (int fingerIndex = 0; fingerIndex < Constants.NUM_FINGERS; fingerIndex++)
|
||||
{
|
||||
int fingerFlag = 1 << fingerIndex;
|
||||
JointFreedom fingerFreedom = handPose.FingersFreedom[fingerIndex];
|
||||
if (fingerFreedom == JointFreedom.Constrained
|
||||
&& ((int)grabbingFingers & fingerFlag) != 0)
|
||||
{
|
||||
fingerFreedom = JointFreedom.Locked;
|
||||
}
|
||||
_syntheticHand.SetFingerFreedom((HandFinger)fingerIndex, fingerFreedom);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FreeFingers()
|
||||
{
|
||||
if (!_areFingersFree)
|
||||
{
|
||||
_syntheticHand.FreeAllJoints();
|
||||
_areFingersFree = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool FreeWrist()
|
||||
{
|
||||
if (!_isWristFree)
|
||||
{
|
||||
_syntheticHand.FreeWrist();
|
||||
_isWristFree = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllHandGrabInteractorVisual(IHandGrabState handGrabState, SyntheticHand syntheticHand)
|
||||
{
|
||||
InjectHandGrabState(handGrabState);
|
||||
InjectSyntheticHand(syntheticHand);
|
||||
}
|
||||
|
||||
public void InjectHandGrabState(IHandGrabState handGrabState)
|
||||
{
|
||||
HandGrabState = handGrabState;
|
||||
_handGrabState = handGrabState as UnityEngine.Object;
|
||||
}
|
||||
|
||||
public void InjectSyntheticHand(SyntheticHand syntheticHand)
|
||||
{
|
||||
_syntheticHand = syntheticHand;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ff26c21ac005534e8af75f8427be9d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandGrab.Visuals
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the translation between hand tracked data and the represented joint.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class HandJointMap
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique identifier for the joint.
|
||||
/// </summary>
|
||||
public HandJointId id;
|
||||
/// <summary>
|
||||
/// The transform that this joint drives.
|
||||
/// </summary>
|
||||
public Transform transform;
|
||||
/// <summary>
|
||||
/// The rotation offset between the hand-tracked joint, and the represented joint.
|
||||
/// </summary>
|
||||
public Vector3 rotationOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Get the rotationOffset as a Quaternion.
|
||||
/// </summary>
|
||||
public Quaternion RotationOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return Quaternion.Euler(rotationOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the raw rotation of the joint, taken from the tracking data
|
||||
/// </summary>
|
||||
public Quaternion TrackedRotation
|
||||
{
|
||||
get
|
||||
{
|
||||
return Quaternion.Inverse(RotationOffset) * transform.localRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A collection of joint maps to quick access the joints that are actually available in the hand rig.
|
||||
/// Stores an internal array of indices so it can transform from positions in the HandPose.HAND_JOINTIDS collection
|
||||
/// to the JointMap List without having to search for the (maybe unavailable) index every time.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class JointCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// List of indices of the joints in the actual rig for quick access
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private int[] _jointIndices = new int[FingersMetadata.HAND_JOINT_IDS.Length];
|
||||
|
||||
/// <summary>
|
||||
/// List of joints in the actual rig
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private List<HandJointMap> _jointMaps;
|
||||
|
||||
public JointCollection(List<HandJointMap> joints)
|
||||
{
|
||||
_jointMaps = joints;
|
||||
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; i++)
|
||||
{
|
||||
HandJointId boneId = FingersMetadata.HAND_JOINT_IDS[i];
|
||||
_jointIndices[i] = joints.FindIndex(bone => bone.id == boneId);
|
||||
}
|
||||
}
|
||||
|
||||
public HandJointMap this[int jointIndex]
|
||||
{
|
||||
get
|
||||
{
|
||||
int joint = _jointIndices[jointIndex];
|
||||
if (joint >= 0)
|
||||
{
|
||||
return _jointMaps[joint];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 890181c147a8cc94597b7ab04b4db257
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandGrab.Visuals
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is a visual representation of a rigged hand (typically a skin-mesh renderer)
|
||||
/// that can move its position/rotation and the rotations of the joints that compose it.
|
||||
/// It also can offset the rotations of the individual joints, adapting the provided
|
||||
/// data to any rig.
|
||||
/// </summary>
|
||||
public class HandPuppet : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Joints of the hand and their relative rotations compared to hand-tracking.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private List<HandJointMap> _jointMaps = new List<HandJointMap>(FingersMetadata.HAND_JOINT_IDS.Length);
|
||||
|
||||
/// <summary>
|
||||
/// General getter for the joints of the hand.
|
||||
/// </summary>
|
||||
public List<HandJointMap> JointMaps
|
||||
{
|
||||
get
|
||||
{
|
||||
return _jointMaps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current scale of the represented hand.
|
||||
/// </summary>
|
||||
public float Scale
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.transform.localScale.x;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.transform.localScale = Vector3.one * value;
|
||||
}
|
||||
}
|
||||
|
||||
private JointCollection _jointsCache;
|
||||
private JointCollection JointsCache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_jointsCache == null)
|
||||
{
|
||||
_jointsCache = new JointCollection(_jointMaps);
|
||||
}
|
||||
return _jointsCache;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates all the joints in this puppet to the desired pose.
|
||||
/// </summary>
|
||||
/// <param name="jointRotations">
|
||||
/// Array of rotations to use for the fingers. It must follow the FingersMetaData.HAND_JOINT_IDS order.
|
||||
/// </param>
|
||||
public void SetJointRotations(in Quaternion[] jointRotations)
|
||||
{
|
||||
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; ++i)
|
||||
{
|
||||
HandJointMap jointMap = JointsCache[i];
|
||||
if (jointMap != null)
|
||||
{
|
||||
Transform jointTransform = jointMap.transform;
|
||||
Quaternion targetRot = jointMap.RotationOffset * jointRotations[i];
|
||||
jointTransform.localRotation = targetRot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates and Translate the hand Wrist so it aligns with the given pose.
|
||||
/// It can apply an offset for when using controllers.
|
||||
/// </summary>
|
||||
/// <param name="rootPose">The Wrist Pose to set this puppet to.</param>
|
||||
/// </param>
|
||||
public void SetRootPose(in Pose rootPose)
|
||||
{
|
||||
this.transform.SetPose(rootPose, Space.World);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the rotations of all the joints available in the puppet
|
||||
/// as they are visually presented.
|
||||
/// Note that any missing joints are skipped.
|
||||
/// </summary>
|
||||
/// <param name="result">Structure to copy the joints to</param>
|
||||
public void CopyCachedJoints(ref HandPose result)
|
||||
{
|
||||
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; ++i)
|
||||
{
|
||||
HandJointMap jointMap = JointsCache[i];
|
||||
if (jointMap != null)
|
||||
{
|
||||
result.JointRotations[i] = jointMap.TrackedRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3bf6df4a5ac85847831e1fb5fa00ff8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user