clean project

This commit is contained in:
Helar Jaadla
2022-03-07 17:52:41 +02:00
parent a174b45bd2
commit cbeb10ec35
5100 changed files with 837159 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,61 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using System.Reflection;
using System;
namespace Oculus.Interaction
{
[AttributeUsage(AttributeTargets.Field)]
public class InspectorButtonAttribute : PropertyAttribute
{
private const float BUTTON_WIDTH = 80;
public float ButtonWidth { get; set; } = BUTTON_WIDTH;
public readonly string methodName;
public InspectorButtonAttribute(string methodName)
{
this.methodName = methodName;
}
}
#if UNITY_EDITOR
[UnityEditor.CustomPropertyDrawer(typeof(InspectorButtonAttribute))]
public class InspectorButtonPropertyDrawer : UnityEditor.PropertyDrawer
{
private MethodInfo _method = null;
public override void OnGUI(Rect positionRect, UnityEditor.SerializedProperty prop, GUIContent label)
{
InspectorButtonAttribute inspectorButtonAttribute = (InspectorButtonAttribute)attribute;
Rect rect = positionRect;
if (GUI.Button(rect, label.text))
{
Type eventType = prop.serializedObject.targetObject.GetType();
string eventName = inspectorButtonAttribute.methodName;
if (_method == null)
{
_method = eventType.GetMethod(eventName,
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance
| BindingFlags.Static);
}
_method?.Invoke(prop.serializedObject.targetObject, null);
}
}
}
#endif
}

View File

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

View File

@@ -0,0 +1,24 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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
{
/// <summary>
/// Used on a SerializedField surfaces the expectation that this field can remain empty.
/// </summary>
public class OptionalAttribute : PropertyAttribute
{
public OptionalAttribute() { }
}
}

View File

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

View File

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

View File

@@ -0,0 +1,49 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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
{
public static partial class Collisions
{
/// <summary>
/// Approximate capsule collision by doing sphere collisions down the capsule length
/// </summary>
/// <param name="p0">Capsule Start</param>
/// <param name="p1">Capsule End</param>
/// <param name="radius">Capsule Radius</param>
/// <param name="collider">Collider to check against</param>
/// <returns>Whether or not an approximate collision occured.</returns>
public static bool IsCapsuleWithinColliderApprox(Vector3 p0, Vector3 p1, float radius, Collider collider)
{
int divisions = Mathf.CeilToInt((p1 - p0).magnitude / radius) * 2;
if (divisions == 0)
{
return IsSphereWithinCollider(p0, radius, collider);
}
float tStep = 1f / divisions;
for (int i = 0; i <= divisions; i++)
{
Vector3 point = Vector3.Lerp(p0, p1, tStep * i);
if (IsSphereWithinCollider(point, radius, collider))
{
return true;
}
}
return false;
}
}
}

View File

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

View File

@@ -0,0 +1,25 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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
{
public static partial class Collisions
{
public static bool IsPointWithinCollider(Vector3 point, Collider collider)
{
Vector3 closestPoint = collider.ClosestPoint(point);
return closestPoint.Equals(point);
}
}
}

View File

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

View File

@@ -0,0 +1,27 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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
{
public static partial class Collisions
{
public static bool IsSphereWithinCollider(Vector3 point, float radius, Collider collider)
{
Vector3 closestPoint = collider.ClosestPoint(point);
if (closestPoint.Equals(point)) return true;
if (Vector3.SqrMagnitude(closestPoint - point) < radius * radius) return true;
return false;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,175 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
namespace Oculus.Interaction
{
/// <summary>
/// Non-allocating HashSet extension methods mirroring MS implementation
/// https://referencesource.microsoft.com/#system.core/system/Collections/Generic/HashSet.cs
/// </summary>
public static class HashSetExtensions
{
/// <summary>
/// Take the union of this HashSet with other. Modifies this set.
/// </summary>
/// <param name="other">HashSet with items to add</param>
public static void UnionWithNonAlloc<T>(this HashSet<T> hashSetToModify, HashSet<T> other)
{
if (hashSetToModify == null)
{
throw new ArgumentNullException(nameof(hashSetToModify));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
foreach (T item in other)
{
hashSetToModify.Add(item);
}
}
/// <summary>
/// Take the union of this HashSet with other. Modifies this set.
/// </summary>
/// <param name="other">IList with items to add</param>
public static void UnionWithNonAlloc<T>(this HashSet<T> hashSetToModify, IList<T> other)
{
if (hashSetToModify == null)
{
throw new ArgumentNullException(nameof(hashSetToModify));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
for (int i = 0; i < other.Count; ++i)
{
hashSetToModify.Add(other[i]);
}
}
/// <summary>
/// Remove items in other from this set. Modifies this set
/// </summary>
/// <param name="other">HashSet with items to remove</param>
public static void ExceptWithNonAlloc<T>(this HashSet<T> hashSetToModify, HashSet<T> other)
{
if (hashSetToModify == null)
{
throw new ArgumentNullException(nameof(hashSetToModify));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
if (hashSetToModify.Count == 0)
{
return;
}
if (other == hashSetToModify)
{
hashSetToModify.Clear();
return;
}
foreach (T element in other)
{
hashSetToModify.Remove(element);
}
}
/// <summary>
/// Remove items in other from this set. Modifies this set
/// </summary>
/// <param name="other">IList with items to remove</param>
public static void ExceptWithNonAlloc<T>(this HashSet<T> hashSetToModify, IList<T> other)
{
if (hashSetToModify == null)
{
throw new ArgumentNullException(nameof(hashSetToModify));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
if (hashSetToModify.Count == 0)
{
return;
}
for (int i = 0; i < other.Count; ++i)
{
hashSetToModify.Remove(other[i]);
}
}
/// <summary>
/// Checks if this set overlaps other (i.e. they share at least one item)
/// </summary>
/// <param name="other">HashSet to check overlap against</param>
/// <returns>true if these have at least one common element; false if disjoint</returns>
public static bool OverlapsNonAlloc<T>(this HashSet<T> hashSetToCheck, HashSet<T> other)
{
if (hashSetToCheck == null)
{
throw new ArgumentNullException(nameof(hashSetToCheck));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
if (hashSetToCheck.Count == 0)
{
return false;
}
foreach (T element in other)
{
if (hashSetToCheck.Contains(element))
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if this set overlaps other (i.e. they share at least one item)
/// </summary>
/// <param name="other">IList to check overlap against</param>
/// <returns>true if these have at least one common element; false if disjoint</returns>
public static bool OverlapsNonAlloc<T>(this HashSet<T> hashSetToCheck, IList<T> other)
{
if (hashSetToCheck == null)
{
throw new ArgumentNullException(nameof(hashSetToCheck));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
if (hashSetToCheck.Count == 0)
{
return false;
}
for (int i = 0; i < other.Count; ++i)
{
if (hashSetToCheck.Contains(other[i]))
{
return true;
}
}
return false;
}
}
}

View File

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

View File

@@ -0,0 +1,53 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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
{
/// <summary>
/// By adding BeginStart and EndStart at the beginning and end of Start, MonoBehaviours with
/// OnEnable and OnDisable logic can wrap their contents within a _started flag and effectively
/// skip over logic in those methods until after Start has been invoked.
///
/// To not bypass the Unity Lifecycle, the enabled property is used to disable the most derived
/// MonoBehaviour, invoke Start up the hierarchy chain, and finally re-enable the MonoBehaviour.
/// </summary>
public static class MonoBehaviourStartExtensions
{
public static void BeginStart(this MonoBehaviour monoBehaviour, ref bool started,
Action baseStart = null)
{
if (!started)
{
monoBehaviour.enabled = false;
started = true;
baseStart?.Invoke();
started = false;
}
else
{
baseStart?.Invoke();
}
}
public static void EndStart(this MonoBehaviour monoBehaviour, ref bool started)
{
if (!started)
{
started = true;
monoBehaviour.enabled = true;
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,163 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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 class FingerGrabAPI : IFingerAPI
{
private Vector3 _poseVolumeCenterOffset = Vector3.zero;
private Vector3 _poseVolumeCenter = Vector3.zero;
private static readonly float START_THRESHOLD = 0.75f;
private static readonly float RELEASE_THRESHOLD = 0.25f;
private static readonly float FINGER_TIP_RADIUS = 0.01f;
private static readonly float POSE_VOLUME_RADIUS = 0.05f;
private static readonly Vector3 POSE_VOLUME_OFFSET_RIGHT = new Vector3(0.07f, -0.03f, 0.0f);
private static readonly Vector3 POSE_VOLUME_OFFSET_LEFT = new Vector3(-0.07f, 0.03f, 0.0f);
private class FingerGrabData
{
private readonly HandJointId _tipId;
private Vector3 _tipPosition;
public float GrabStrength;
public bool IsGrabbing;
public bool IsGrabbingChanged;
public FingerGrabData(HandFinger fingerId)
{
_tipId = HandJointUtils.GetHandFingerTip(fingerId);
}
public void UpdateTipValues(IHand hand)
{
if (hand.GetJointPoseFromWrist(_tipId, out Pose pose))
{
_tipPosition = pose.position;
}
}
public void UpdateGrabStrength(Vector3 poseVolumeCenter)
{
float outsidePoseVolumeRadius = POSE_VOLUME_RADIUS + FINGER_TIP_RADIUS;
float insidePoseVolumeRadius = POSE_VOLUME_RADIUS - FINGER_TIP_RADIUS;
float sqrOutsidePoseVolume = outsidePoseVolumeRadius * outsidePoseVolumeRadius;
float sqrInsidePoseVolume = insidePoseVolumeRadius * insidePoseVolumeRadius;
float sqrDist = (poseVolumeCenter - _tipPosition).sqrMagnitude;
if (sqrDist >= sqrOutsidePoseVolume)
{
GrabStrength = 0.0f;
}
else if (sqrDist <= sqrInsidePoseVolume)
{
GrabStrength = 1.0f;
}
else
{
float distance = Mathf.Sqrt(sqrDist);
GrabStrength = 1.0f - Mathf.Clamp01(
(distance - insidePoseVolumeRadius) / (2.0f * FINGER_TIP_RADIUS));
}
}
public void UpdateIsGrabbing()
{
IsGrabbingChanged = false;
if (GrabStrength > START_THRESHOLD)
{
if (!IsGrabbing)
{
IsGrabbing = true;
IsGrabbingChanged = true;
}
return;
}
if (GrabStrength < RELEASE_THRESHOLD)
{
if (IsGrabbing)
{
IsGrabbing = false;
IsGrabbingChanged = true;
}
}
}
}
private readonly FingerGrabData[] _fingersGrabData = {
new FingerGrabData(HandFinger.Thumb),
new FingerGrabData(HandFinger.Index),
new FingerGrabData(HandFinger.Middle),
new FingerGrabData(HandFinger.Ring),
new FingerGrabData(HandFinger.Pinky)
};
public bool GetFingerIsGrabbing(HandFinger finger)
{
return _fingersGrabData[(int)finger].IsGrabbing;
}
public bool GetFingerIsGrabbingChanged(HandFinger finger, bool targetGrabState)
{
return _fingersGrabData[(int)finger].IsGrabbingChanged &&
_fingersGrabData[(int)finger].IsGrabbing == targetGrabState;
}
public float GetFingerGrabStrength(HandFinger finger)
{
return _fingersGrabData[(int)finger].GrabStrength;
}
public Vector3 GetCenterOffset()
{
return _poseVolumeCenterOffset;
}
public void Update(IHand hand)
{
if (hand == null || !hand.IsTrackedDataValid)
{
return;
}
UpdateVolumeCenter(hand);
for (int i = 0; i < Constants.NUM_FINGERS; ++i)
{
_fingersGrabData[i].UpdateTipValues(hand);
_fingersGrabData[i].UpdateGrabStrength(_poseVolumeCenter);
_fingersGrabData[i].UpdateIsGrabbing();
}
}
private void UpdateVolumeCenter(IHand hand)
{
if (!hand.GetJointPoseFromWrist(HandJointId.HandWristRoot, out var wristPose))
{
return;
}
Matrix4x4 wristPoseMat = Matrix4x4.TRS(wristPose.position, wristPose.rotation, Vector3.one);
_poseVolumeCenterOffset = hand.Handedness == Handedness.Left
? POSE_VOLUME_OFFSET_LEFT
: POSE_VOLUME_OFFSET_RIGHT;
_poseVolumeCenter = wristPose.position +
wristPoseMat.MultiplyVector(_poseVolumeCenterOffset);
}
}
}

View File

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

View File

@@ -0,0 +1,271 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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 class FingerPinchAPI : IFingerAPI
{
private bool _isPinchVisibilityGood;
private float DistanceStart => _isPinchVisibilityGood ? PINCH_HQ_DISTANCE_START : PINCH_DISTANCE_START;
private float DistanceStopMax => _isPinchVisibilityGood ? PINCH_HQ_DISTANCE_STOP_MAX : PINCH_DISTANCE_STOP_MAX;
private float DistanceStopOffset => _isPinchVisibilityGood ? PINCH_HQ_DISTANCE_STOP_OFFSET : PINCH_DISTANCE_STOP_OFFSET;
private const float PINCH_DISTANCE_START = 0.03f;
private const float PINCH_DISTANCE_STOP_MAX = 0.1f;
private const float PINCH_DISTANCE_STOP_OFFSET = 0.04f;
private const float PINCH_HQ_DISTANCE_START = 0.016f;
private const float PINCH_HQ_DISTANCE_STOP_MAX = 0.1f;
private const float PINCH_HQ_DISTANCE_STOP_OFFSET = 0.016f;
private const float PINCH_HQ_VIEW_ANGLE_THRESHOLD = 40f;
private HandJointId[] thumbJointList = new[]
{
HandJointId.HandThumb0,
HandJointId.HandThumb1,
HandJointId.HandThumb2,
HandJointId.HandThumb3,
HandJointId.HandThumbTip
};
private class FingerPinchData
{
private readonly HandJointId _tipId;
private float _minPinchDistance;
public Vector3 TipPosition { get; private set; }
public float PinchStrength;
public bool IsPinching;
public bool IsPinchingChanged;
public FingerPinchData(HandFinger fingerId)
{
_tipId = HandJointUtils.GetHandFingerTip(fingerId);
}
public void UpdateTipPosition(IHand hand)
{
if (hand.GetJointPoseFromWrist(_tipId, out Pose pose))
{
TipPosition = pose.position;
}
}
public void UpdateIsPinching(float distance, float start, float stopOffset, float stopMax)
{
IsPinchingChanged = false;
if (!IsPinching)
{
if (distance < start)
{
IsPinching = true;
IsPinchingChanged = true;
_minPinchDistance = distance;
}
}
else
{
_minPinchDistance = Mathf.Min(_minPinchDistance, distance);
if (distance > stopMax ||
distance > _minPinchDistance + stopOffset)
{
IsPinching = false;
IsPinchingChanged = true;
_minPinchDistance = float.MaxValue;
}
}
}
}
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)
{
if (finger == HandFinger.Thumb)
{
for (int i = 1; i < Constants.NUM_FINGERS; ++i)
{
if (_fingersPinchData[i].IsPinching)
{
return true;
}
}
return false;
}
return _fingersPinchData[(int)finger].IsPinching;
}
public Vector3 GetCenterOffset()
{
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)
{
if (finger == HandFinger.Thumb)
{
// Thumb pinching changed logic only happens on
// the first finger pinching changed when pinching,
// or any finger pinching changed when not pinching
bool pinching = GetFingerIsGrabbing(finger);
if (pinching != targetPinchState)
{
return false;
}
if (pinching)
{
for (int i = 1; i < Constants.NUM_FINGERS; ++i)
{
if (_fingersPinchData[i].IsPinching == pinching &&
!_fingersPinchData[i].IsPinchingChanged)
{
return false;
}
}
return true;
}
else
{
for (int i = 1; i < Constants.NUM_FINGERS; ++i)
{
if (_fingersPinchData[i].IsPinchingChanged)
{
return true;
}
}
return false;
}
}
return _fingersPinchData[(int)finger].IsPinchingChanged &&
_fingersPinchData[(int)finger].IsPinching == targetPinchState;
}
public float GetFingerGrabStrength(HandFinger finger)
{
if (finger == HandFinger.Thumb)
{
float max = 0.0f;
for (int i = 1; i < Constants.NUM_FINGERS; ++i)
{
max = Mathf.Max(max, _fingersPinchData[i].PinchStrength);
}
return max;
}
return _fingersPinchData[(int)finger].PinchStrength;
}
public void Update(IHand hand)
{
_isPinchVisibilityGood = PinchHasGoodVisibility(hand);
for (int i = 0; i < Constants.NUM_FINGERS; ++i)
{
_fingersPinchData[i].UpdateTipPosition(hand);
}
Vector3 thumbTipPosition = _fingersPinchData[0].TipPosition;
for (int i = 1; i < Constants.NUM_FINGERS; ++i)
{
float distance = GetClosestDistanceToThumb(hand, _fingersPinchData[i].TipPosition);
_fingersPinchData[i].UpdateIsPinching(distance,
DistanceStart, DistanceStopOffset, DistanceStopMax);
float pinchPercent = (distance - DistanceStart) /
(DistanceStopMax - DistanceStart);
_fingersPinchData[i].PinchStrength = 1f - Mathf.Clamp01(pinchPercent);
}
}
private float GetClosestDistanceToThumb(IHand hand, Vector3 position)
{
float minDistance = float.MaxValue;
for (int i = 0; i < thumbJointList.Length - 1; i++)
{
if (!hand.GetJointPoseFromWrist(thumbJointList[i], out Pose pose0))
{
return float.MaxValue;
}
if (!hand.GetJointPoseFromWrist(thumbJointList[i+1], out Pose pose1))
{
return float.MaxValue;
}
minDistance = Mathf.Min(minDistance,
ClosestDistanceToLineSegment(position, pose0.position, pose1.position));
}
return minDistance;
}
private float ClosestDistanceToLineSegment(Vector3 position, Vector3 p0, Vector3 p1)
{
Vector3 lineVec = p1 - p0;
Vector3 fromP0 = position - p0;
float normalizedProjection = Vector3.Dot(fromP0, lineVec) / Vector3.Dot(lineVec, lineVec);
float closestT = Mathf.Clamp01(normalizedProjection);
Vector3 closestPoint = p0 + closestT * lineVec;
return (closestPoint - position).magnitude;
}
private bool PinchHasGoodVisibility(IHand hand)
{
if (!hand.GetJointPose(HandJointId.HandWristRoot, out Pose wristPose)
|| !hand.GetCenterEyePose(out Pose centerEyePose))
{
return false;
}
Vector3 handVector = -1.0f * wristPose.forward;
Vector3 targetVector = -1.0f * centerEyePose.forward;
if (hand.Handedness == Handedness.Right)
{
handVector = -handVector;
}
float angle = Vector3.Angle(handVector, targetVector);
return angle <= PINCH_HQ_VIEW_ANGLE_THRESHOLD;
}
}
}

View File

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

View File

@@ -0,0 +1,108 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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 class FingerRawPinchAPI : IFingerAPI
{
private class FingerPinchData
{
private readonly HandFinger _finger;
private readonly HandJointId _tipId;
public float PinchStrength;
public bool IsPinching;
public bool IsPinchingChanged;
public Vector3 TipPosition { get; private set; }
public FingerPinchData(HandFinger fingerId)
{
_finger = fingerId;
_tipId = HandJointUtils.GetHandFingerTip(fingerId);
}
public void UpdateTipPosition(IHand hand)
{
if (hand.GetJointPoseFromWrist(_tipId, out Pose pose))
{
TipPosition = pose.position;
}
}
public void UpdateIsPinching(IHand hand)
{
PinchStrength = hand.GetFingerPinchStrength(_finger);
bool isPinching = hand.GetFingerIsPinching(_finger);
IsPinchingChanged = isPinching != IsPinching;
IsPinching = isPinching;
}
}
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 GetCenterOffset()
{
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 GetFingerGrabStrength(HandFinger finger)
{
return _fingersPinchData[(int)finger].PinchStrength;
}
public void Update(IHand hand)
{
for (int i = 0; i < Constants.NUM_FINGERS; ++i)
{
_fingersPinchData[i].UpdateIsPinching(hand);
}
}
}
}

View File

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

View File

@@ -0,0 +1,27 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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
{
public class FingerRawPinchInjector : MonoBehaviour
{
[SerializeField]
private HandGrabAPI _handGrabAPI;
protected virtual void Start()
{
_handGrabAPI.InjectOptionalFingerPinchAPI(new FingerRawPinchAPI());
}
}
}

View File

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

View File

@@ -0,0 +1,26 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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
}
}

View File

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

View File

@@ -0,0 +1,157 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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
}
[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.Required,
_indexRequirement = FingerRequirement.Optional,
_middleRequirement = FingerRequirement.Optional,
_ringRequirement = FingerRequirement.Optional,
_pinkyRequirement = FingerRequirement.Optional,
_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
}
}

View File

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

View File

@@ -0,0 +1,228 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.GrabAPI;
using Oculus.Interaction.Input;
namespace Oculus.Interaction.Grab
{
public enum GrabType
{
PinchGrab = 1 << 0,
PalmGrab = 1 << 1
}
public interface IHandGrabInteractor
{
HandGrabAPI HandGrabApi { get; }
GrabTypeFlags SupportedGrabTypes { get; }
IHandGrabInteractable TargetInteractable { get; }
}
public interface IHandGrabInteractable
{
GrabTypeFlags SupportedGrabTypes { get; }
GrabbingRule PinchGrabRules { get; }
GrabbingRule PalmGrabRules { get; }
}
public class HandGrabInteractableData : IHandGrabInteractable
{
public GrabTypeFlags SupportedGrabTypes { get; set; } = GrabTypeFlags.All;
public GrabbingRule PinchGrabRules { get; set; } = GrabbingRule.DefaultPinchRule;
public GrabbingRule PalmGrabRules { get; set; } = GrabbingRule.DefaultPalmRule;
}
public static class HandGrab
{
public static void StoreGrabData(IHandGrabInteractor interactor,
IHandGrabInteractable interactable, ref HandGrabInteractableData cache)
{
HandGrabAPI api = interactor.HandGrabApi;
cache.SupportedGrabTypes = GrabTypeFlags.None;
if (SupportsPinch(interactor, interactable))
{
HandFingerFlags pinchFingers = api.HandPinchingFinger();
if (api.IsSustainingGrab(interactable.PinchGrabRules, pinchFingers))
{
cache.SupportedGrabTypes |= GrabTypeFlags.Pinch;
cache.PinchGrabRules = new GrabbingRule(pinchFingers, interactable.PinchGrabRules);
}
}
if (SupportsPalm(interactor, interactable))
{
HandFingerFlags palmFingers = api.HandPalmGrabbingFingers();
if(api.IsSustainingGrab(interactable.PalmGrabRules, palmFingers))
{
cache.SupportedGrabTypes |= GrabTypeFlags.Palm;
cache.PalmGrabRules = new GrabbingRule(palmFingers, interactable.PalmGrabRules);
}
}
}
public static float ComputeHoverStrength(IHandGrabInteractor interactor,
IHandGrabInteractable interactable, out GrabTypeFlags hoveringGrabTypes)
{
HandGrabAPI api = interactor.HandGrabApi;
hoveringGrabTypes = GrabTypeFlags.None;
float hoverStrength = 0f;
if (SupportsPinch(interactor, interactable))
{
float pinchStrength = api.GetHandPinchStrength(interactable.PinchGrabRules, false);
if (pinchStrength > hoverStrength)
{
hoverStrength = pinchStrength;
hoveringGrabTypes = GrabTypeFlags.Pinch;
}
}
if (SupportsPalm(interactor, interactable))
{
float palmStrength = api.GetHandPalmStrength(interactable.PalmGrabRules, false);
if (palmStrength > hoverStrength)
{
hoverStrength = palmStrength;
hoveringGrabTypes = GrabTypeFlags.Palm;
}
}
return hoverStrength;
}
public static bool ComputeShouldSelect(IHandGrabInteractor interactor,
IHandGrabInteractable interactable, out GrabTypeFlags selectingGrabTypes)
{
if (interactable == null)
{
selectingGrabTypes = GrabTypeFlags.None;
return false;
}
HandGrabAPI api = interactor.HandGrabApi;
selectingGrabTypes = GrabTypeFlags.None;
if (SupportsPinch(interactor, interactable) &&
api.IsHandSelectPinchFingersChanged(interactable.PinchGrabRules))
{
selectingGrabTypes |= GrabTypeFlags.Pinch;
}
if (SupportsPalm(interactor, interactable) &&
api.IsHandSelectPalmFingersChanged(interactable.PalmGrabRules))
{
selectingGrabTypes |= GrabTypeFlags.Palm;
}
return selectingGrabTypes != GrabTypeFlags.None;
}
public static bool ComputeShouldUnselect(IHandGrabInteractor interactor,
IHandGrabInteractable interactable)
{
HandGrabAPI api = interactor.HandGrabApi;
HandFingerFlags pinchFingers = api.HandPinchingFinger();
HandFingerFlags palmFingers = api.HandPalmGrabbingFingers();
if (interactable.SupportedGrabTypes == GrabTypeFlags.None)
{
if (!api.IsSustainingGrab(GrabbingRule.FullGrab, pinchFingers) &&
!api.IsSustainingGrab(GrabbingRule.FullGrab, palmFingers))
{
return true;
}
return false;
}
bool pinchHolding = false;
bool palmHolding = false;
bool pinchReleased = false;
bool palmReleased = false;
if (SupportsPinch(interactor, interactable.SupportedGrabTypes))
{
pinchHolding = api.IsSustainingGrab(interactable.PinchGrabRules, pinchFingers);
if (api.IsHandUnselectPinchFingersChanged(interactable.PinchGrabRules))
{
pinchReleased = true;
}
}
if (SupportsPalm(interactor, interactable.SupportedGrabTypes))
{
palmHolding = api.IsSustainingGrab(interactable.PalmGrabRules, palmFingers);
if (api.IsHandUnselectPalmFingersChanged(interactable.PalmGrabRules))
{
palmReleased = true;
}
}
return !pinchHolding && !palmHolding && (pinchReleased || palmReleased);
}
public static HandFingerFlags GrabbingFingers(IHandGrabInteractor interactor,
IHandGrabInteractable interactable)
{
HandGrabAPI api = interactor.HandGrabApi;
if (interactable == null)
{
return HandFingerFlags.None;
}
HandFingerFlags fingers = HandFingerFlags.None;
if (SupportsPinch(interactor, interactable))
{
HandFingerFlags pinchingFingers = api.HandPinchingFinger();
interactable.PinchGrabRules.StripIrrelevant(ref pinchingFingers);
fingers = fingers | pinchingFingers;
}
if (SupportsPalm(interactor, interactable))
{
HandFingerFlags grabbingFingers = api.HandPalmGrabbingFingers();
interactable.PalmGrabRules.StripIrrelevant(ref grabbingFingers);
fingers = fingers | grabbingFingers;
}
return fingers;
}
private static bool SupportsPinch(IHandGrabInteractor interactor,
IHandGrabInteractable interactable)
{
return SupportsPinch(interactor, interactable.SupportedGrabTypes);
}
private static bool SupportsPalm(IHandGrabInteractor interactor,
IHandGrabInteractable interactable)
{
return SupportsPalm(interactor, interactable.SupportedGrabTypes);
}
private static bool SupportsPinch(IHandGrabInteractor interactor,
GrabTypeFlags grabTypes)
{
return (interactor.SupportedGrabTypes & GrabTypeFlags.Pinch) != 0 &&
(grabTypes & GrabTypeFlags.Pinch) != 0;
}
private static bool SupportsPalm(IHandGrabInteractor interactor,
GrabTypeFlags grabTypes)
{
return (interactor.SupportedGrabTypes & GrabTypeFlags.Palm) != 0 &&
(grabTypes & GrabTypeFlags.Palm) != 0;
}
}
}

View File

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

View File

@@ -0,0 +1,337 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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 UnityEngine.Assertions;
namespace Oculus.Interaction.GrabAPI
{
public class HandGrabAPI : MonoBehaviour
{
[SerializeField, Interface(typeof(IHand))]
private MonoBehaviour _hand;
public IHand Hand { get; private set; }
private IFingerAPI _fingerPinchAPI = new FingerPinchAPI();
private IFingerAPI _fingerPalmAPI = new FingerGrabAPI();
private bool _started;
protected virtual void Awake()
{
Hand = _hand as IHand;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
Assert.IsNotNull(Hand);
Assert.IsNotNull(_fingerPinchAPI);
Assert.IsNotNull(_fingerPalmAPI);
this.EndStart(ref _started);
}
protected virtual void Update()
{
_fingerPinchAPI.Update(Hand);
_fingerPalmAPI.Update(Hand);
}
public HandFingerFlags HandPinchingFinger()
{
return HandGrabbingFingers(_fingerPinchAPI);
}
public HandFingerFlags HandPalmGrabbingFingers()
{
return HandGrabbingFingers(_fingerPalmAPI);
}
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 = HandPinchingFinger();
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)
{
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)
{
if (fingers.UnselectMode == FingerUnselectMode.AnyReleased
&& !isFingerGrabbing)
{
return false;
}
if(fingers.UnselectMode == FingerUnselectMode.AllReleased
&& isFingerGrabbing)
{
return true;
}
}
else if (fingers[finger] == FingerRequirement.Optional)
{
if (fingers.UnselectMode == FingerUnselectMode.AnyReleased
&& !isFingerGrabbing)
{
return false;
}
if (fingers.UnselectMode == FingerUnselectMode.AllReleased
&& isFingerGrabbing)
{
return true;
}
}
}
return false;
}
/// <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, _fingerPinchAPI);
}
/// <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, _fingerPalmAPI);
}
public bool IsHandUnselectPinchFingersChanged(in GrabbingRule fingers)
{
return IsHandUnselectFingersChanged(fingers, _fingerPinchAPI);
}
public bool IsHandUnselectPalmFingersChanged(in GrabbingRule fingers)
{
return IsHandUnselectFingersChanged(fingers, _fingerPalmAPI);
}
private bool IsHandSelectFingersChanged(in GrabbingRule fingers, IFingerAPI fingerAPI)
{
bool selectsWithOptionals = fingers.SelectsWithOptionals;
bool aFingerGrabbed = 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))
{
aFingerGrabbed = true;
}
}
else if (selectsWithOptionals
&& fingers[finger] == FingerRequirement.Optional)
{
if (fingerAPI.GetFingerIsGrabbingChanged(finger, true))
{
return true;
}
}
}
return aFingerGrabbed;
}
private bool IsHandUnselectFingersChanged(in GrabbingRule fingers, IFingerAPI fingerAPI)
{
bool isAnyFingerGrabbing = false;
bool aFingerUngrabbed = 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))
{
aFingerUngrabbed = true;
if (fingers.UnselectMode == FingerUnselectMode.AnyReleased)
{
return true;
}
}
}
if (fingers[finger] == FingerRequirement.Optional)
{
if (fingerAPI.GetFingerIsGrabbingChanged(finger, false))
{
aFingerUngrabbed = true;
if (fingers.UnselectMode == FingerUnselectMode.AnyReleased
&& selectsWithOptionals)
{
return true;
}
}
}
}
return !isAnyFingerGrabbing && aFingerUngrabbed;
}
public Vector3 GetPinchCenter()
{
return WristOffsetToWorldPoint(_fingerPinchAPI.GetCenterOffset());
}
public Vector3 GetPalmCenter()
{
return WristOffsetToWorldPoint(_fingerPalmAPI.GetCenterOffset());
}
private Vector3 WristOffsetToWorldPoint(Vector3 offset)
{
if (!Hand.GetJointPose(HandJointId.HandWristRoot, out Pose wristPose))
{
return offset;
}
return wristPose.position + wristPose.rotation * offset;
}
public float GetHandPinchStrength(in GrabbingRule fingers,
bool includePinching = true)
{
return GetHandStrength(fingers, includePinching, _fingerPinchAPI);
}
public float GetHandPalmStrength(in GrabbingRule fingers,
bool includeGrabbing = true)
{
return GetHandStrength(fingers, includeGrabbing, _fingerPalmAPI);
}
public float GetFingerPinchStrength(HandFinger finger)
{
return _fingerPinchAPI.GetFingerGrabStrength(finger);
}
public float GetFingerPalmStrength(HandFinger finger)
{
return _fingerPalmAPI.GetFingerGrabStrength(finger);
}
private float GetHandStrength(in GrabbingRule fingers,
bool includeGrabbing, IFingerAPI fingerAPI)
{
float requiredMin = 1.0f;
float optionalMax = 0f;
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.GetFingerGrabStrength((HandFinger)i));
}
if (fingers[finger] == FingerRequirement.Required)
{
requiredMin = Mathf.Min(requiredMin, fingerAPI.GetFingerGrabStrength((HandFinger)i));
}
}
return Mathf.Min(requiredMin, optionalMax);
}
#region Inject
public void InjectAllHandGrabAPI(IHand hand)
{
InjectHand(hand);
}
public void InjectHand(IHand hand)
{
_hand = hand as MonoBehaviour;
Hand = hand;
}
public void InjectOptionalFingerPinchAPI(IFingerAPI fingerPinchAPI)
{
_fingerPinchAPI = fingerPinchAPI;
}
public void InjectOptionalFingerGrabAPI(IFingerAPI fingerGrabAPI)
{
_fingerPalmAPI = fingerGrabAPI;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,26 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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 interface IFingerAPI
{
bool GetFingerIsGrabbing(HandFinger finger);
bool GetFingerIsGrabbingChanged(HandFinger finger, bool targetPinchState);
float GetFingerGrabStrength(HandFinger finger);
Vector3 GetCenterOffset();
void Update(IHand hand);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,203 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Serialization;
namespace Oculus.Interaction.HandPosing.Recording
{
public class HandPoseRecordable : MonoBehaviour
{
/// <summary>
/// List of valid HandGrabInteractables in which the object can be held.
/// </summary>
[SerializeField]
private List<HandGrabInteractable> _interactables = new List<HandGrabInteractable>();
#if UNITY_EDITOR
/// <summary>
/// Creates an Inspector button to remove the HandGrabInteractable collection, destroying its
/// associated gameObjects.
/// </summary>
[InspectorButton("RemoveInteractables")]
[SerializeField]
private string _clearInteractables;
/// <summary>
/// Button to forcefully refresh the HandGrabInteractables collection during edit mode
/// </summary>
[InspectorButton("RefreshInteractables")]
[SerializeField]
private string _refreshInteractables;
[Space(20f)]
/// <summary>
/// This ScriptableObject stores the HandGrabInteractables generated at Play-Mode so it survives
/// the Play-Edit cycle.
/// Create a collection and assign it even in Play Mode and make sure to store here the
/// interactables, then restore it in Edit-Mode to be serialized.
/// </summary>
[SerializeField, Optional]
[Tooltip("Collection for storing generated HandGrabInteractables during Play-Mode, so they can be restored in Edit-Mode")]
private HandGrabInteractableDataCollection _posesCollection = null;
/// <summary>
/// Creates an Inspector button to store the current HandGrabInteractables to the posesCollection.
/// Use it during Play-Mode.
/// </summary>
[InspectorButton("SaveToAsset")]
[SerializeField]
private string _storePoses;
/// <summary>
/// Creates an Inspector button that restores the saved HandGrabInteractables inn posesCollection.
/// Use it in Edit-Mode.
/// </summary>
[InspectorButton("LoadFromAsset")]
[SerializeField]
private string _loadPoses;
#endif
/// <summary>
/// Creates a new HandGrabInteractable at the exact pose of a given hand.
/// Mostly used with Hand-Tracking at Play-Mode
/// </summary>
/// <param name="rawPose">The user controlled hand pose.</param>
/// <param name="snapPoint">The user controlled hand pose.</param>
/// <returns>The generated HandGrabPoint.</returns>
public HandGrabPoint AddHandGrabPoint(HandPose rawPose, Pose snapPoint)
{
HandGrabInteractable interactable = GenerateHandGrabInteractable();
HandGrabPointData pointData = new HandGrabPointData()
{
handPose = rawPose,
scale = 1f,
gripPose = snapPoint,
};
return interactable.LoadPoint(pointData);
}
/// <summary>
/// Creates a default HandGrabInteractable for this snap interactable.
/// </summary>
/// <returns>An non-populated HandGrabInteractable.</returns>
private HandGrabInteractable GenerateHandGrabInteractable()
{
HandGrabInteractable record = HandGrabInteractable.Create(this.transform);
_interactables.Add(record);
return record;
}
public float DistanceToObject(Vector3 point)
{
float minDistance = float.PositiveInfinity;
foreach (Renderer rend in this.GetComponentsInChildren<Renderer>())
{
if (rend.bounds.Contains(point))
{
return 0f;
}
else
{
Vector3 surfacePoint = rend.bounds.ClosestPoint(point);
float distance = Vector3.Distance(surfacePoint, point);
if (distance <= minDistance)
{
minDistance = distance;
}
}
}
return minDistance;
}
#if UNITY_EDITOR
/// <summary>
/// Creates a new HandGrabInteractable from the stored data.
/// Mostly used to restore a HandGrabInteractable that was stored during Play-Mode.
/// </summary>
/// <param name="data">The data of the HandGrabInteractable.</param>
/// <returns>The generated HandGrabInteractable.</returns>
private HandGrabInteractable LoadHandGrabInteractable(HandGrabInteractableData data)
{
HandGrabInteractable interactable = GenerateHandGrabInteractable();
interactable.LoadData(data);
return interactable;
}
/// <summary>
/// Load the HandGrabInteractable from a Collection.
///
/// This method is called from a button in the Inspector and will load the posesCollection.
/// </summary>
private void LoadFromAsset()
{
if (_posesCollection != null)
{
foreach (HandGrabInteractableData handPose in _posesCollection.InteractablesData)
{
LoadHandGrabInteractable(handPose);
}
}
}
/// <summary>
/// Stores the interactables to a SerializedObject (the empty object must be
/// provided in the inspector or one will be auto-generated). First it translates the HandGrabInteractable to a serialized
/// form HandGrabInteractableData).
///
/// This method is called from a button in the Inspector.
/// </summary>
private void SaveToAsset()
{
List<HandGrabInteractableData> savedPoses = new List<HandGrabInteractableData>();
foreach (HandGrabInteractable snap in this.GetComponentsInChildren<HandGrabInteractable>(false))
{
savedPoses.Add(snap.SaveData());
}
if (_posesCollection == null)
{
GenerateCollectionAsset();
}
_posesCollection?.StoreInteractables(savedPoses);
}
private void GenerateCollectionAsset()
{
_posesCollection = ScriptableObject.CreateInstance<HandGrabInteractableDataCollection>();
string parentDir = Path.Combine("Assets", "HandGrabInteractableDataCollection");
if (!Directory.Exists(parentDir))
{
Directory.CreateDirectory(parentDir);
}
UnityEditor.AssetDatabase.CreateAsset(_posesCollection, Path.Combine(parentDir, $"{this.name}_HandGrabCollection.asset"));
UnityEditor.AssetDatabase.SaveAssets();
}
private void RemoveInteractables()
{
foreach (HandGrabInteractable interactable in _interactables)
{
UnityEditor.Undo.DestroyObjectImmediate(interactable.gameObject);
}
_interactables.Clear();
}
/// <summary>
/// Refreshes a list of all the Active HandGrabInteractable nested under this Recordable.
/// </summary>
private void RefreshInteractables()
{
List<HandGrabInteractable> interactables = new List<HandGrabInteractable>(this.GetComponentsInChildren<HandGrabInteractable>(false));
_interactables = interactables;
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,164 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.HandPosing.Visuals;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction.HandPosing.Recording
{
/// <summary>
/// Extract the current pose of the user hand and generates a valid HandGrabInteractable in the
/// nearest HandPoseRecordable object.
/// Typically used in play-mode.
/// </summary>
public class HandPoseRecorder : MonoBehaviour
{
[SerializeField, Interface(typeof(IHand))]
private MonoBehaviour _hand;
private IHand Hand;
[SerializeField]
private Transform _gripPoint;
[SerializeField]
[Min(0f)]
private float _maxRecordingDistance = 0.05f;
#if UNITY_INPUTSYSTEM
[SerializeField]
private string _recordKey = "space";
#else
[SerializeField]
private KeyCode _recordKeyCode = KeyCode.Space;
#endif
[Space(20f)]
/// <summary>
/// References the hand prototypes used to represent the HandGrabInteractables. These are the
/// static hands placed around the interactable to visualize the different holding hand-poses.
/// Not mandatory.
/// </summary>
[SerializeField, Optional]
[Tooltip("Prototypes of the static hands (ghosts) that visualize holding poses")]
private HandGhostProvider _ghostProvider;
/// <summary>
/// Create an Inspector button for manually triggering the pose recorder.
/// </summary>
[InspectorButton("RecordPose")]
[SerializeField]
private string _record;
private HandPoseRecordable[] _allRecordables;
protected virtual void Start()
{
Hand = _hand as IHand;
Assert.IsNotNull(Hand);
_allRecordables = GameObject.FindObjectsOfType<HandPoseRecordable>();
#if UNITY_INPUTSYSTEM
UnityEngine.InputSystem.InputAction recordAction = new UnityEngine.InputSystem.InputAction("onRecordPose", binding: $"<Keyboard>/{_recordKey}");
recordAction.Enable();
recordAction.performed += ctx => RecordPose();
#endif
}
#if !UNITY_INPUTSYSTEM
protected virtual void Update()
{
if (UnityEngine.Input.GetKeyDown(_recordKeyCode))
{
RecordPose();
}
}
#endif
/// <summary>
/// Finds the nearest object that can be snapped to and adds a new HandGrabInteractable to
/// it with the user hand representation.
/// </summary>
public void RecordPose()
{
HandPoseRecordable recordable = NearestRecordable();
if (recordable == null)
{
return;
}
HandPose trackedHandPose = TrackedPose();
if(trackedHandPose == null)
{
return;
}
Pose gripPoint = recordable.transform.RelativeOffset(_gripPoint);
HandGrabPoint point = recordable.AddHandGrabPoint(trackedHandPose, gripPoint);
AttachGhost(point);
}
private void AttachGhost(HandGrabPoint point)
{
if (_ghostProvider != null)
{
HandGhost ghostPrefab = _ghostProvider.GetHand(Hand.Handedness);
HandGhost ghost = GameObject.Instantiate(ghostPrefab, point.transform);
ghost.SetPose(point);
}
}
private HandPose TrackedPose()
{
if (!Hand.GetJointPosesLocal(out ReadOnlyHandJointPoses localJoints))
{
return null;
}
HandPose result = new HandPose();
result.Handedness = Hand.Handedness;
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; ++i)
{
HandJointId jointID = FingersMetadata.HAND_JOINT_IDS[i];
result.JointRotations[i] = localJoints[jointID].rotation;
}
return result;
}
/// <summary>
/// Finds the nearest recordable (measured from its transform.position) of all the available in the scene.
/// </summary>
/// <returns></returns>
private HandPoseRecordable NearestRecordable()
{
float minDistance = float.PositiveInfinity;
HandPoseRecordable nearRecordable = null;
foreach (HandPoseRecordable recordable in _allRecordables)
{
float distanceToObject = recordable.DistanceToObject(this.transform.position);
if (distanceToObject == 0f)
{
return recordable;
}
if (distanceToObject <= _maxRecordingDistance && distanceToObject < minDistance)
{
minDistance = distanceToObject;
nearRecordable = recordable;
}
}
return nearRecordable;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,390 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using UnityEngine.Assertions;
using System;
namespace Oculus.Interaction.HandPosing.SnapSurfaces
{
[Serializable]
public class BoxSurfaceData : ICloneable
{
public object Clone()
{
BoxSurfaceData clone = new BoxSurfaceData();
clone.widthOffset = this.widthOffset;
clone.snapOffset = this.snapOffset;
clone.size = this.size;
clone.eulerAngles = this.eulerAngles;
return clone;
}
public BoxSurfaceData Mirror()
{
BoxSurfaceData mirror = Clone() as BoxSurfaceData;
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 SnapSurface 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 BoxSurface : MonoBehaviour, ISnapSurface
{
[SerializeField]
protected BoxSurfaceData _data = new BoxSurfaceData();
/// <summary>
/// Getter for the data-only version of this surface. Used so it can be stored when created
/// at Play-Mode.
/// </summary>
public BoxSurfaceData Data
{
get
{
return _data;
}
set
{
_data = value;
}
}
[SerializeField]
private Transform _relativeTo;
[SerializeField]
private Transform _gripPoint;
/// <summary>
/// Transform to which the surface refers to.
/// </summary>
public Transform RelativeTo
{
get => _relativeTo;
set => _relativeTo = value;
}
/// <summary>
/// Valid point at which the hand can snap, typically the SnapPoint position itself.
/// </summary>
public Transform GripPoint
{
get => _gripPoint;
set => _gripPoint = value;
}
/// <summary>
/// The lateral displacement of the grip point in the main side.
/// </summary>
public float WidthOffset
{
get
{
return _data.widthOffset;
}
set
{
_data.widthOffset = value;
}
}
/// <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>
public Vector4 SnapOffset
{
get
{
return _data.snapOffset;
}
set
{
_data.snapOffset = value;
}
}
/// <summary>
/// The size of the rectangle. Y is ignored.
/// </summary>
public Vector3 Size
{
get
{
return _data.size;
}
set
{
_data.size = value;
}
}
/// <summary>
/// The rotation of the rectangle from the Grip point
/// </summary>
public Quaternion Rotation
{
get
{
return this.RelativeTo.rotation * Quaternion.Euler(_data.eulerAngles);
}
set
{
_data.eulerAngles = (Quaternion.Inverse(this.RelativeTo.rotation) * value).eulerAngles;
}
}
/// <summary>
/// The forward direction of the rectangle (based on its rotation)
/// </summary>
public Vector3 Direction
{
get
{
return Rotation * Vector3.forward;
}
}
#region editor events
private void Reset()
{
_gripPoint = this.transform;
if (this.TryGetComponent(out HandGrabPoint grabPoint))
{
_relativeTo = grabPoint.RelativeTo;
}
}
#endregion
protected virtual void Start()
{
Assert.IsNotNull(_relativeTo);
Assert.IsNotNull(_gripPoint);
Assert.IsNotNull(_data);
}
public Pose MirrorPose(in Pose pose)
{
Vector3 normal = Quaternion.Inverse(this.RelativeTo.rotation) * Direction;
Vector3 tangent = Quaternion.Inverse(this.RelativeTo.rotation) * (Rotation * Vector3.up);
return pose.MirrorPoseRotation(normal, tangent);
}
public ISnapSurface CreateMirroredSurface(GameObject gameObject)
{
BoxSurface surface = gameObject.AddComponent<BoxSurface>();
surface.Data = _data.Mirror();
return surface;
}
public ISnapSurface CreateDuplicatedSurface(GameObject gameObject)
{
BoxSurface surface = gameObject.AddComponent<BoxSurface>();
surface.Data = _data;
return surface;
}
public float CalculateBestPoseAtSurface(in Pose targetPose, in Pose reference, out Pose bestPose, in PoseMeasureParameters scoringModifier)
{
return SnapSurfaceHelper.CalculateBestPoseAtSurface(targetPose, reference, out bestPose,
scoringModifier, MinimalTranslationPoseAtSurface, MinimalRotationPoseAtSurface);
}
private void CalculateCorners(out Vector3 bottomLeft, out Vector3 bottomRight, out Vector3 topLeft, out Vector3 topRight)
{
Vector3 rightRot = Rotation * Vector3.right;
bottomLeft = GripPoint.position - rightRot * _data.size.x * (1f - _data.widthOffset);
bottomRight = GripPoint.position + rightRot * _data.size.x * (_data.widthOffset);
Vector3 forwardOffset = Rotation * Vector3.forward * _data.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, in Pose recordedPose, out Pose bestPose)
{
Plane plane = new Plane(Rotation * Vector3.up, this.transform.position);
plane.Raycast(targetRay, out float rayDistance);
Vector3 proximalPoint = targetRay.origin + targetRay.direction * rayDistance;
Vector3 surfacePoint = NearestPointInSurface(proximalPoint);
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
bestPose = MinimalTranslationPoseAtSurface(desiredPose, recordedPose);
return true;
}
protected Vector3 NearestPointInSurface(Vector3 targetPosition)
{
NearestPointAndAngleInSurface(targetPosition, out Vector3 surfacePoint, out float angle);
return surfacePoint;
}
private void NearestPointAndAngleInSurface(Vector3 targetPosition, out Vector3 surfacePoint, out float angle)
{
Vector3 rightDir = Rotation * Vector3.right;
Vector3 forwardDir = Rotation * Vector3.forward;
Vector3 bottomLeft, bottomRight, topLeft, topRight;
CalculateCorners(out bottomLeft, out bottomRight, out topLeft, out topRight);
Vector3 bottomP = ProjectOnSegment(targetPosition, (bottomLeft + rightDir * SnapOffset.y, bottomRight + rightDir * SnapOffset.x));
Vector3 topP = ProjectOnSegment(targetPosition, (topLeft - rightDir * SnapOffset.x, topRight - rightDir * SnapOffset.y));
Vector3 leftP = ProjectOnSegment(targetPosition, (bottomLeft - forwardDir * SnapOffset.z, topLeft - forwardDir * SnapOffset.w));
Vector3 rightP = ProjectOnSegment(targetPosition, (bottomRight + forwardDir * SnapOffset.w, topRight + forwardDir * SnapOffset.z));
float bottomDistance = Vector3.Distance(bottomP, targetPosition);
float topDistance = Vector3.Distance(topP, targetPosition);
float leftDistance = Vector3.Distance(leftP, targetPosition);
float rightDistance = Vector3.Distance(rightP, targetPosition);
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, in Pose snapPose)
{
Vector3 desiredPos = userPose.position;
Quaternion baseRot = snapPose.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 = PoseUtils.RotationalSimilarity(bottomRot, desiredRot);
float topDot = PoseUtils.RotationalSimilarity(topRot, desiredRot);
float leftDot = PoseUtils.RotationalSimilarity(leftRot, desiredRot);
float rightDot = PoseUtils.RotationalSimilarity(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);
float maxDot = Mathf.Max(bottomDot, Mathf.Max(topDot, Mathf.Max(leftDot, rightDot)));
if (bottomDot == maxDot)
{
Vector3 projBottom = ProjectOnSegment(desiredPos, (bottomLeft + rightDir * SnapOffset.y, bottomRight + rightDir * SnapOffset.x));
return new Pose(projBottom, bottomRot);
}
if (topDot == maxDot)
{
Vector3 projTop = ProjectOnSegment(desiredPos, (topLeft - rightDir * SnapOffset.x, topRight - rightDir * SnapOffset.y));
return new Pose(projTop, topRot);
}
if (leftDot == maxDot)
{
Vector3 projLeft = ProjectOnSegment(desiredPos, (bottomLeft - forwardDir * SnapOffset.z, topLeft - forwardDir * SnapOffset.w));
return new Pose(projLeft, leftRot);
}
Vector3 projRight = ProjectOnSegment(desiredPos, (bottomRight + forwardDir * SnapOffset.w, topRight + forwardDir * SnapOffset.z));
return new Pose(projRight, rightRot);
}
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, in Pose snapPose)
{
Vector3 desiredPos = userPose.position;
Quaternion baseRot = snapPose.rotation;
Vector3 surfacePoint;
float surfaceAngle;
NearestPointAndAngleInSurface(desiredPos, out surfacePoint, out surfaceAngle);
Quaternion surfaceRotation = RotateUp(baseRot, surfaceAngle);
return new Pose(surfacePoint, surfaceRotation);
}
protected Quaternion RotateUp(Quaternion baseRot, float angle)
{
Quaternion offset = Quaternion.AngleAxis(angle, Rotation * Vector3.up);
return offset * baseRot;
}
#region Inject
public void InjectAllBoxSurface(BoxSurfaceData data,
Transform relativeTo, Transform gripPoint)
{
InjectData(data);
InjectRelativeTo(relativeTo);
InjectGripPoint(gripPoint);
}
public void InjectData(BoxSurfaceData data)
{
_data = data;
}
public void InjectRelativeTo(Transform relativeTo)
{
_relativeTo = relativeTo;
}
public void InjectGripPoint(Transform gripPoint)
{
_gripPoint = gripPoint;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,96 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using UnityEngine.Assertions;
namespace Oculus.Interaction.HandPosing.SnapSurfaces
{
public class ColliderSurface : MonoBehaviour, ISnapSurface
{
[SerializeField]
private Collider _collider;
protected virtual void Start()
{
Assert.IsNotNull(_collider);
}
private Vector3 NearestPointInSurface(Vector3 targetPosition)
{
if (_collider.bounds.Contains(targetPosition))
{
targetPosition = _collider.ClosestPointOnBounds(targetPosition);
}
return _collider.ClosestPoint(targetPosition);
}
public float CalculateBestPoseAtSurface(in Pose targetPose, in Pose snapPose, out Pose bestPose, in PoseMeasureParameters scoringModifier)
{
Vector3 surfacePoint = NearestPointInSurface(targetPose.position);
float bestScore = 1f;
if (scoringModifier.MaxDistance > 0)
{
bestScore = PoseUtils.PositionalSimilarity(surfacePoint, targetPose.position, scoringModifier.MaxDistance);
}
bestPose = new Pose(surfacePoint, targetPose.rotation);
return bestScore;
}
public bool CalculateBestPoseAtSurface(Ray targetRay, in Pose recordedPose, out Pose bestPose)
{
if (_collider.Raycast(targetRay, out RaycastHit hit, Mathf.Infinity))
{
bestPose.position = hit.point;
bestPose.rotation = recordedPose.rotation;
return true;
}
bestPose = Pose.identity;
return false;
}
public Pose MirrorPose(in Pose gripPose)
{
return gripPose;
}
public ISnapSurface CreateMirroredSurface(GameObject gameObject)
{
return CreateDuplicatedSurface(gameObject);
}
public ISnapSurface CreateDuplicatedSurface(GameObject gameObject)
{
ColliderSurface colliderSurface = gameObject.AddComponent<ColliderSurface>();
colliderSurface.InjectAllColliderSurface(_collider);
return colliderSurface;
}
#region inject
public void InjectCollider(Collider collider)
{
_collider = collider;
}
public void InjectAllColliderSurface(Collider collider)
{
InjectCollider(collider);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,426 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using UnityEngine.Assertions;
using System;
namespace Oculus.Interaction.HandPosing.SnapSurfaces
{
[Serializable]
public class CylinderSurfaceData : ICloneable
{
public object Clone()
{
CylinderSurfaceData clone = new CylinderSurfaceData();
clone.startPoint = this.startPoint;
clone.endPoint = this.endPoint;
clone.angle = this.angle;
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 angle = 120f;
}
/// <summary>
/// This type of surface defines a cylinder in which the grip pose is valid around an object.
/// An angle 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 CylinderSurface : MonoBehaviour, ISnapSurface
{
[SerializeField]
protected CylinderSurfaceData _data = new CylinderSurfaceData();
/// <summary>
/// Getter for the data-only version of this surface. Used so it can be stored when created
/// at Play-Mode.
/// </summary>
public CylinderSurfaceData Data
{
get
{
return _data;
}
set
{
_data = value;
}
}
[SerializeField]
private Transform _relativeTo;
[SerializeField]
private Transform _gripPoint;
/// <summary>
/// Transform to which the surface refers to.
/// </summary>
public Transform RelativeTo
{
get => _relativeTo;
set => _relativeTo = value;
}
/// <summary>
/// Valid point at which the hand can snap, typically the SnapPoint position itself.
/// </summary>
public Transform GripPoint
{
get => _gripPoint;
set => _gripPoint = value;
}
/// <summary>
/// Direction from the axis of the cylinder to the original grip position.
/// </summary>
public Vector3 StartAngleDir
{
get
{
if (this.GripPoint == null)
{
return Vector3.forward;
}
return Vector3.ProjectOnPlane(GripPoint.transform.position - StartPoint, Direction).normalized;
}
}
/// <summary>
/// Direction from the axis of the cylinder to the maximum angle allowance.
/// </summary>
public Vector3 EndAngleDir
{
get
{
return Quaternion.AngleAxis(Angle, Direction) * StartAngleDir;
}
}
/// <summary>
/// Base cap of the cylinder, in world coordinates.
/// </summary>
public Vector3 StartPoint
{
get
{
if (this.RelativeTo != null)
{
return this.RelativeTo.TransformPoint(_data.startPoint);
}
else
{
return _data.startPoint;
}
}
set
{
if (this.RelativeTo != null)
{
_data.startPoint = this.RelativeTo.InverseTransformPoint(value);
}
else
{
_data.startPoint = value;
}
}
}
/// <summary>
/// End cap of the cylinder, in world coordinates.
/// </summary>
public Vector3 EndPoint
{
get
{
if (this.RelativeTo != null)
{
return this.RelativeTo.TransformPoint(_data.endPoint);
}
else
{
return _data.endPoint;
}
}
set
{
if (this.RelativeTo != null)
{
_data.endPoint = this.RelativeTo.InverseTransformPoint(value);
}
else
{
_data.endPoint = value;
}
}
}
/// <summary>
/// The maximum angle for the surface of the cylinder, starting from the original grip position.
/// To invert the direction of the angle, swap the caps order.
/// </summary>
public float Angle
{
get
{
return _data.angle;
}
set
{
_data.angle = Mathf.Repeat(value, 360f);
}
}
/// <summary>
/// The generated radius of the cylinder.
/// Represents the distance from the axis of the cylinder to the original grip position.
/// </summary>
public float Radius
{
get
{
if (this.GripPoint == null)
{
return 0f;
}
Vector3 start = StartPoint;
Vector3 projectedPoint = start + Vector3.Project(this.GripPoint.position - start, Direction);
return Vector3.Distance(projectedPoint, this.GripPoint.position);
}
}
/// <summary>
/// The direction of the central axis of the cylinder.
/// </summary>
public Vector3 Direction
{
get
{
Vector3 dir = (EndPoint - StartPoint);
if (dir.sqrMagnitude == 0f)
{
return this.RelativeTo ? this.RelativeTo.up : Vector3.up;
}
return dir.normalized;
}
}
private float Height
{
get
{
return (EndPoint - StartPoint).magnitude;
}
}
/// <summary>
/// The rotation of the central axis of the cylinder.
/// </summary>
private Quaternion Rotation
{
get
{
if (_data.startPoint == _data.endPoint)
{
return Quaternion.LookRotation(Vector3.forward);
}
return Quaternion.LookRotation(StartAngleDir, Direction);
}
}
#region editor events
private void Reset()
{
_gripPoint = this.transform;
if (this.TryGetComponent(out HandGrabPoint grabPoint))
{
_relativeTo = grabPoint.RelativeTo;
}
}
#endregion
protected virtual void Start()
{
Assert.IsNotNull(_relativeTo);
Assert.IsNotNull(_gripPoint);
Assert.IsNotNull(_data);
}
public Pose MirrorPose(in Pose pose)
{
Vector3 normal = Quaternion.Inverse(this.RelativeTo.rotation) * StartAngleDir;
Vector3 tangent = Quaternion.Inverse(this.RelativeTo.rotation) * Direction;
return pose.MirrorPoseRotation(normal, tangent);
}
private Vector3 PointAltitude(Vector3 point)
{
Vector3 start = StartPoint;
Vector3 projectedPoint = start + Vector3.Project(point - start, Direction);
return projectedPoint;
}
public float CalculateBestPoseAtSurface(in Pose targetPose, in Pose reference, out Pose bestPose, in PoseMeasureParameters scoringModifier)
{
return SnapSurfaceHelper.CalculateBestPoseAtSurface(targetPose, reference, out bestPose,
scoringModifier, MinimalTranslationPoseAtSurface, MinimalRotationPoseAtSurface);
}
public ISnapSurface CreateMirroredSurface(GameObject gameObject)
{
CylinderSurface surface = gameObject.AddComponent<CylinderSurface>();
surface.Data = _data.Mirror();
return surface;
}
public ISnapSurface CreateDuplicatedSurface(GameObject gameObject)
{
CylinderSurface surface = gameObject.AddComponent<CylinderSurface>();
surface.Data = _data;
return surface;
}
protected Vector3 NearestPointInSurface(Vector3 targetPosition)
{
Vector3 start = StartPoint;
Vector3 dir = Direction;
Vector3 projectedVector = Vector3.Project(targetPosition - start, dir);
if (projectedVector.magnitude > Height)
{
projectedVector = projectedVector.normalized * Height;
}
if (Vector3.Dot(projectedVector, dir) < 0f)
{
projectedVector = Vector3.zero;
}
Vector3 projectedPoint = StartPoint + projectedVector;
Vector3 targetDirection = Vector3.ProjectOnPlane((targetPosition - projectedPoint), dir).normalized;
//clamp of the surface
float desiredAngle = Mathf.Repeat(Vector3.SignedAngle(StartAngleDir, targetDirection, dir), 360f);
if (desiredAngle > Angle)
{
if (Mathf.Abs(desiredAngle - Angle) >= Mathf.Abs(360f - desiredAngle))
{
targetDirection = StartAngleDir;
}
else
{
targetDirection = EndAngleDir;
}
}
Vector3 surfacePoint = projectedPoint + targetDirection * Radius;
return surfacePoint;
}
public bool CalculateBestPoseAtSurface(Ray targetRay, in Pose recordedPose, out Pose bestPose)
{
Vector3 lineToCylinder = StartPoint - 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;
Vector3 pointInLine = targetRay.origin + targetRay.direction * lineOffset;
Vector3 pointInCylinder = StartPoint + 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);
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
bestPose = MinimalTranslationPoseAtSurface(desiredPose, recordedPose);
return true;
}
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, in Pose snapPose)
{
Vector3 desiredPos = userPose.position;
Quaternion desiredRot = userPose.rotation;
Quaternion baseRot = snapPose.rotation;
Quaternion rotDif = (desiredRot) * Quaternion.Inverse(baseRot);
Vector3 desiredDirection = (rotDif * Rotation) * Vector3.forward;
Vector3 projectedDirection = Vector3.ProjectOnPlane(desiredDirection, Direction).normalized;
Vector3 altitudePoint = PointAltitude(desiredPos);
Vector3 surfacePoint = NearestPointInSurface(altitudePoint + projectedDirection * Radius);
Quaternion surfaceRotation = CalculateRotationOffset(surfacePoint) * baseRot;
return new Pose(surfacePoint, surfaceRotation);
}
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, in Pose snapPose)
{
Vector3 desiredPos = userPose.position;
Quaternion baseRot = snapPose.rotation;
Vector3 surfacePoint = NearestPointInSurface(desiredPos);
Quaternion surfaceRotation = CalculateRotationOffset(surfacePoint) * baseRot;
return new Pose(surfacePoint, surfaceRotation);
}
protected Quaternion CalculateRotationOffset(Vector3 surfacePoint)
{
Vector3 recordedDirection = Vector3.ProjectOnPlane(this.GripPoint.position - StartPoint, Direction);
Vector3 desiredDirection = Vector3.ProjectOnPlane(surfacePoint - StartPoint, Direction);
return Quaternion.FromToRotation(recordedDirection, desiredDirection);
}
#region Inject
public void InjectAllCylinderSurface(CylinderSurfaceData data,
Transform relativeTo, Transform gripPoint)
{
InjectData(data);
InjectRelativeTo(relativeTo);
InjectGripPoint(gripPoint);
}
public void InjectData(CylinderSurfaceData data)
{
_data = data;
}
public void InjectRelativeTo(Transform relativeTo)
{
_relativeTo = relativeTo;
}
public void InjectGripPoint(Transform gripPoint)
{
_gripPoint = gripPoint;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,66 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.HandPosing.SnapSurfaces
{
/// <summary>
/// This interface defines the method needed to use snap 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 ISnapSurface
{
/// <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="reference">The reference snap point to use for measuring at the surface.</param>
/// <param name="bestPose">The best found pose at the surface.<</param>
/// <returns>The score indicating how good the found pose was, -1 for invalid result.</returns>
float CalculateBestPoseAtSurface(in Pose targetPose, in Pose reference, out Pose bestPose, in PoseMeasureParameters scoringModifier);
/// <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="reference">The reference snap point to use for measuring at the surface.</param>
/// <param name="bestPose">The best found pose at the surface.</param>
/// <returns>True if the pose was found</returns>
bool CalculateBestPoseAtSurface(Ray targetRay, in Pose reference, out Pose bestPose);
/// <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>
/// <returns>A new pose mirrored at this surface.</returns>
Pose MirrorPose(in Pose gripPose);
/// <summary>
/// Creates a new ISnapSurface under the selected gameobject
/// that is a mirror version of the current.
/// </summary>
/// <param name="gameObject">The gameobject in which to place the new ISnapSurface.</param>
/// <returns>A mirror of this ISnapSurface.</returns>
ISnapSurface CreateMirroredSurface(GameObject gameObject);
/// <summary>
/// Creates a new ISnapSurface under the selected gameobject
/// with the same data as this one.
/// </summary>
/// <param name="gameObject">The gameobject in which to place the new ISnapSurface.</param>
/// <returns>A clone of this ISnapSurface.</returns>
ISnapSurface CreateDuplicatedSurface(GameObject gameObject);
}
}

View File

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

View File

@@ -0,0 +1,76 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.HandPosing.SnapSurfaces
{
public static class SnapSurfaceHelper
{
public delegate Pose PoseCalculator(in Pose desiredPose, in Pose snapPose);
/// <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="snapPose">Reference pose of the surface.</param>
/// <param name="bestPose">Nearest pose to the desired one at the surface.</param>
/// <param name="maxDistance">Max distance to consider for scoring.</param>
/// <param name="positionRotationWeight">Ratio of position and rotation to select between poses.</param>
/// <param name="minimalTranslationPoseCalculator">Delegate to calculate the nearest, by position, pose at a surface.</param>
/// <param name="minimalRotationPoseCalculator">Delegate to calculate the nearest, by rotation, pose at a surface.</param>
/// <returns>The score, normalized, of the best pose.</returns>
public static float CalculateBestPoseAtSurface(in Pose desiredPose, in Pose snapPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier,
PoseCalculator minimalTranslationPoseCalculator, PoseCalculator minimalRotationPoseCalculator)
{
float bestScore;
Pose minimalRotationPose = minimalRotationPoseCalculator(desiredPose, snapPose);
if (scoringModifier.MaxDistance > 0)
{
Pose minimalTranslationPose = minimalTranslationPoseCalculator(desiredPose, snapPose);
bestPose = SelectBestPose(minimalRotationPose, minimalTranslationPose, desiredPose, scoringModifier, out bestScore);
}
else
{
bestPose = minimalRotationPose;
bestScore = PoseUtils.RotationalSimilarity(desiredPose.rotation, bestPose.rotation);
}
return bestScore;
}
/// <summary>
/// Compares two poses to a reference and returns the most similar one
/// </summary>
/// <param name="a">First pose to compare with the reference.</param>
/// <param name="b">Second pose to compare with the reference.</param>
/// <param name="reference">Reference pose to measure from.</param>
/// <param name="normalizedWeight">Position to rotation scoring ratio.</param>
/// <param name="maxDistance">Max distance to measure the score.</param>
/// <param name="bestScore">Out value with the score of the best pose.</param>
/// <returns>The most similar pose to reference out of a and b</returns>
private static Pose SelectBestPose(in Pose a, in Pose b, in Pose reference, PoseMeasureParameters scoringModifier, out float bestScore)
{
float aScore = PoseUtils.Similarity(reference, a, scoringModifier);
float bScore = PoseUtils.Similarity(reference, b, scoringModifier);
if (aScore >= bScore)
{
bestScore = aScore;
return a;
}
bestScore = bScore;
return b;
}
}
}

View File

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

View File

@@ -0,0 +1,281 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using UnityEngine.Assertions;
using System;
namespace Oculus.Interaction.HandPosing.SnapSurfaces
{
[Serializable]
public class SphereSurfaceData : ICloneable
{
public object Clone()
{
SphereSurfaceData clone = new SphereSurfaceData();
clone.centre = this.centre;
return clone;
}
public SphereSurfaceData Mirror()
{
SphereSurfaceData mirror = Clone() as SphereSurfaceData;
return mirror;
}
public Vector3 centre;
}
/// <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 SphereSurface : MonoBehaviour, ISnapSurface
{
[SerializeField]
protected SphereSurfaceData _data = new SphereSurfaceData();
/// <summary>
/// Getter for the data-only version of this surface. Used so it can be stored when created
/// at Play-Mode.
/// </summary>
public SphereSurfaceData Data
{
get
{
return _data;
}
set
{
_data = value;
}
}
[SerializeField]
private Transform _relativeTo;
[SerializeField]
private Transform _gripPoint;
/// <summary>
/// Transform to which the surface refers to.
/// </summary>
public Transform RelativeTo
{
get => _relativeTo;
set => _relativeTo = value;
}
/// <summary>
/// Valid point at which the hand can snap, typically the SnapPoint position itself.
/// </summary>
public Transform GripPoint
{
get => _gripPoint;
set => _gripPoint = value;
}
/// <summary>
/// The center of the sphere in world coordinates.
/// </summary>
public Vector3 Centre
{
get
{
if (RelativeTo != null)
{
return RelativeTo.TransformPoint(_data.centre);
}
else
{
return _data.centre;
}
}
set
{
if (RelativeTo != null)
{
_data.centre = RelativeTo.InverseTransformPoint(value);
}
else
{
_data.centre = value;
}
}
}
/// <summary>
/// The radius of the sphere, this is automatically calculated as the distance between
/// the center and the original grip pose.
/// </summary>
public float Radius
{
get
{
if (this.GripPoint == null)
{
return 0f;
}
return Vector3.Distance(Centre, this.GripPoint.position);
}
}
/// <summary>
/// The direction of the sphere, measured from the center to the original grip position.
/// </summary>
public Vector3 Direction
{
get
{
return (this.GripPoint.position - Centre).normalized;
}
}
/// <summary>
/// The rotation of the sphere from the recorded grip position.
/// </summary>
public Quaternion Rotation
{
get
{
return Quaternion.LookRotation(Direction, this.GripPoint.forward);
}
}
#region editor events
private void Reset()
{
_gripPoint = this.transform;
if (this.TryGetComponent(out HandGrabPoint grabPoint))
{
_relativeTo = grabPoint.RelativeTo;
}
}
#endregion
protected virtual void Start()
{
Assert.IsNotNull(_relativeTo);
Assert.IsNotNull(_gripPoint);
Assert.IsNotNull(_data);
}
public Pose MirrorPose(in Pose pose)
{
Vector3 normal = Quaternion.Inverse(RelativeTo.rotation) * Direction;
Vector3 tangent = Vector3.Cross(normal, Vector3.up);
return pose.MirrorPoseRotation(normal, tangent);
}
public bool CalculateBestPoseAtSurface(Ray targetRay, in Pose recordedPose, out Pose bestPose)
{
Vector3 projection = Vector3.Project(Centre - targetRay.origin, targetRay.direction);
Vector3 nearestCentre = targetRay.origin + projection;
float distanceToSurface = Mathf.Max(Vector3.Distance(Centre, nearestCentre) - Radius);
if (distanceToSurface < Radius)
{
float adjustedDistance = Mathf.Sqrt(Radius * Radius - distanceToSurface * distanceToSurface);
nearestCentre -= targetRay.direction * adjustedDistance;
}
Vector3 surfacePoint = NearestPointInSurface(nearestCentre);
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
bestPose = MinimalTranslationPoseAtSurface(desiredPose, recordedPose);
return true;
}
public float CalculateBestPoseAtSurface(in Pose targetPose, in Pose reference, out Pose bestPose, in PoseMeasureParameters scoringModifier)
{
return SnapSurfaceHelper.CalculateBestPoseAtSurface(targetPose, reference, out bestPose,
scoringModifier, MinimalTranslationPoseAtSurface, MinimalRotationPoseAtSurface);
}
public ISnapSurface CreateMirroredSurface(GameObject gameObject)
{
SphereSurface surface = gameObject.AddComponent<SphereSurface>();
surface.Data = _data.Mirror();
return surface;
}
public ISnapSurface CreateDuplicatedSurface(GameObject gameObject)
{
SphereSurface surface = gameObject.AddComponent<SphereSurface>();
surface.Data = _data;
return surface;
}
protected Vector3 NearestPointInSurface(Vector3 targetPosition)
{
Vector3 direction = (targetPosition - Centre).normalized;
return Centre + direction * Radius;
}
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, in Pose snapPose)
{
Quaternion rotCorrection = Quaternion.FromToRotation(snapPose.up, Direction);
Vector3 correctedDir = (rotCorrection * userPose.up).normalized;
Vector3 surfacePoint = NearestPointInSurface(Centre + correctedDir * Radius);
Quaternion surfaceRotation = RotationAtPoint(surfacePoint, snapPose.rotation, userPose.rotation);
return new Pose(surfacePoint, surfaceRotation);
}
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, in Pose snapPose)
{
Vector3 desiredPos = userPose.position;
Quaternion baseRot = snapPose.rotation;
Vector3 surfacePoint = NearestPointInSurface(desiredPos);
Quaternion surfaceRotation = RotationAtPoint(surfacePoint, baseRot, userPose.rotation);
return new Pose(surfacePoint, surfaceRotation);
}
protected Quaternion RotationAtPoint(Vector3 surfacePoint, Quaternion baseRot, Quaternion desiredRotation)
{
Vector3 desiredDirection = (surfacePoint - Centre).normalized;
Quaternion targetRotation = Quaternion.FromToRotation(Direction, 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(SphereSurfaceData data,
Transform relativeTo, Transform gripPoint)
{
InjectData(data);
InjectRelativeTo(relativeTo);
InjectGripPoint(gripPoint);
}
public void InjectData(SphereSurfaceData data)
{
_data = data;
}
public void InjectRelativeTo(Transform relativeTo)
{
_relativeTo = relativeTo;
}
public void InjectGripPoint(Transform gripPoint)
{
_gripPoint = gripPoint;
}
#endregion
}
}

View File

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

View File

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

View File

@@ -0,0 +1,134 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using UnityEngine.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction.HandPosing.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 GripPoint of the hand. Needs to be
/// at the same position/rotation as the gripPoint used
/// by the visual HandPuppet controlled by the user.
/// </summary>
[SerializeField]
private Transform _gripPoint;
/// <summary>
/// The HandGrab point can be set so the ghost automatically
/// adopts the desired pose of said point.
/// </summary>
[SerializeField, Optional]
private HandGrabPoint _handGrabPoint;
#region editor events
protected virtual void Reset()
{
_puppet = this.GetComponent<HandPuppet>();
_handGrabPoint = this.GetComponentInParent<HandGrabPoint>();
}
protected virtual void OnValidate()
{
if (_puppet == null
|| _gripPoint == null)
{
return;
}
if (_handGrabPoint == null)
{
HandGrabPoint point = this.GetComponentInParent<HandGrabPoint>();
if (point != null)
{
SetPose(point);
}
}
else if (_handGrabPoint != null)
{
SetPose(_handGrabPoint);
}
}
#endregion
protected virtual void Start()
{
Assert.IsNotNull(_puppet);
Assert.IsNotNull(_gripPoint);
}
/// <summary>
/// Relay to the Puppet to set the ghost hand to the desired static pose
/// </summary>
/// <param name="handGrabPoint">The point to read the HandPose from</param>
public void SetPose(HandGrabPoint handGrabPoint)
{
HandPose userPose = handGrabPoint.HandPose;
if (userPose == null)
{
return;
}
Transform relativeTo = handGrabPoint.RelativeTo;
_puppet.SetJointRotations(userPose.JointRotations);
SetGripPose(handGrabPoint.RelativeGrip, relativeTo);
}
/// <summary>
/// Moves the underlying puppet so the grip point aligns with the given parameters
/// </summary>
/// <param name="gripPose">The relative grip pose to align the hand to</param>
/// <param name="relativeTo">The object to use as anchor</param>
public void SetGripPose(Pose gripPose, Transform relativeTo)
{
Pose inverseGrip = _gripPoint.RelativeOffset(this.transform);
gripPose.Premultiply(inverseGrip);
gripPose.Postmultiply(relativeTo.GetPose());
_puppet.SetRootPose(gripPose);
}
#region Inject
public void InjectHandPuppet(HandPuppet puppet)
{
_puppet = puppet;
}
public void InjectGripPoint(Transform gripPoint)
{
_gripPoint = gripPoint;
}
public void InjectOptionalHandGrabPoint(HandGrabPoint handGrabPoint)
{
_handGrabPoint = handGrabPoint;
}
public void InjectAllHandGhost(HandPuppet puppet, Transform gripPoint)
{
InjectHandPuppet(puppet);
InjectGripPoint(gripPoint);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,47 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.HandPosing.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;
}
}
}

View File

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

View File

@@ -0,0 +1,294 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using UnityEngine.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction.HandPosing.Visuals
{
/// <summary>
/// This component is used to drive the HandGrabModifier.
/// It sets the desired fingers and wrist positions of the hand structure
/// in the modifier, informing it of any changes coming from the HandGrabInteractor.
/// </summary>
public class HandGrabInteractorVisual : MonoBehaviour
{
/// <summary>
/// The HandGrabInteractor, to override the tracked hand
/// when snapping to something.
/// </summary>
[SerializeField]
[Interface(typeof(ISnapper))]
private List<MonoBehaviour> _snappers;
private List<ISnapper> Snappers;
private ITrackingToWorldTransformer Transformer;
/// <summary>
/// The modifier is part of the InputDataStack and this
/// class will set its values each frame.
/// </summary>
[SerializeField]
private SyntheticHandModifier _modifier;
private bool _areFingersFree = true;
private bool _isWristFree = true;
private ISnapper _currentSnapper;
protected bool _started = false;
#region manual initialization
public static HandGrabInteractorVisual Create(
GameObject gameObject,
List<ISnapper> snappers,
ITrackingToWorldTransformer transformer,
SyntheticHandModifier modifier)
{
HandGrabInteractorVisual component = gameObject.AddComponent<HandGrabInteractorVisual>();
component.Snappers = snappers;
component.Transformer = transformer;
component._modifier = modifier;
return component;
}
#endregion
protected virtual void Awake()
{
Snappers = _snappers.ConvertAll(mono => mono as ISnapper);
}
protected virtual void Start()
{
this.BeginStart(ref _started);
foreach (ISnapper snapper in Snappers)
{
Assert.IsNotNull(snapper);
}
Assert.IsNotNull(_modifier);
Transformer = _modifier.Config.TrackingToWorldTransformer;
Assert.IsNotNull(Transformer);
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
RegisterCallbacks(true);
}
}
protected virtual void OnDisable()
{
if (_started)
{
RegisterCallbacks(false);
}
}
private void LateUpdate()
{
UpdateHand(_currentSnapper);
_modifier.MarkInputDataRequiresUpdate();
}
private void RegisterCallbacks(bool register)
{
if (register)
{
foreach (ISnapper snapper in _snappers)
{
snapper.WhenSnapStarted += HandleSnapStarted;
snapper.WhenSnapEnded += HandleSnapEnded;
}
}
else
{
foreach (ISnapper snapper in _snappers)
{
snapper.WhenSnapStarted -= HandleSnapStarted;
snapper.WhenSnapEnded -= HandleSnapEnded;
}
}
}
private void HandleSnapStarted(ISnapper snapper)
{
_currentSnapper = snapper;
}
private void HandleSnapEnded(ISnapper snapper)
{
if (_currentSnapper == snapper)
{
_currentSnapper = null;
}
}
private void UpdateHand(ISnapper constrainingSnapper)
{
if (constrainingSnapper != null)
{
ConstrainingForce(constrainingSnapper, out float fingersConstraint, out float wristConstraint);
UpdateHandPose(constrainingSnapper, fingersConstraint, wristConstraint);
}
else
{
FreeFingers();
FreeWrist();
}
}
private void ConstrainingForce(ISnapper snapper, out float fingersConstraint, out float wristConstraint)
{
ISnapData snap = snapper.SnapData;
fingersConstraint = wristConstraint = 0;
if (snap == null || snap.HandPose == null)
{
return;
}
bool isSnapping = snapper.IsSnapping;
if (snap.SnapType == SnapType.HandToObject
|| snap.SnapType == SnapType.HandToObjectAndReturn)
{
fingersConstraint = snapper.SnapStrength;
wristConstraint = snapper.SnapStrength;
}
else if (isSnapping
&& snap.SnapType == SnapType.ObjectToHand)
{
fingersConstraint = 1f;
wristConstraint = 1f;
}
if (fingersConstraint >= 1f && !isSnapping)
{
fingersConstraint = 0;
}
if (wristConstraint >= 1f && !isSnapping)
{
wristConstraint = 0f;
}
}
private void UpdateHandPose(ISnapper snapper, float fingersConstraint, float wristConstraint)
{
ISnapData snap = snapper.SnapData;
if (fingersConstraint > 0f)
{
UpdateFingers(snap.HandPose, snapper.SnappingFingers(), fingersConstraint);
_areFingersFree = false;
}
else
{
FreeFingers();
}
if (wristConstraint > 0f)
{
Pose wristPose = GetWristPose(snap.WorldSnapPose, snapper.WristToGripOffset);
wristPose = Transformer.ToTrackingPose(wristPose);
_modifier.LockWristPose(wristPose, wristConstraint);
_isWristFree = false;
}
else
{
FreeWrist();
}
}
/// <summary>
/// Writes the desired rotation values for each joint based on the provided SnapAddress.
/// Apart from the rotations it also writes in the modifier 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;
_modifier.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;
}
_modifier.SetFingerFreedom((HandFinger)fingerIndex, fingerFreedom);
}
}
private Pose GetWristPose(Pose gripPoint, Pose wristToGripOffset)
{
Pose gripToWrist = wristToGripOffset;
gripToWrist.Invert();
gripPoint.Premultiply(gripToWrist);
return gripPoint;
}
private bool FreeFingers()
{
if (!_areFingersFree)
{
_modifier.FreeAllJoints();
_areFingersFree = true;
return true;
}
return false;
}
private bool FreeWrist()
{
if (!_isWristFree)
{
_modifier.FreeWrist();
_isWristFree = true;
return true;
}
return false;
}
#region Inject
public void InjectSnappers(List<ISnapper> snappers)
{
_snappers = snappers.ConvertAll(mono => mono as MonoBehaviour);
Snappers = snappers;
}
public void InjectModifier(SyntheticHandModifier modifier)
{
_modifier = modifier;
}
public void InjectAllHandGrabInteractorVisual(List<ISnapper> snappers, SyntheticHandModifier modifier)
{
InjectSnappers(snappers);
InjectModifier(modifier);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,106 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.HandPosing.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;
}
}
}
}

View File

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

View File

@@ -0,0 +1,122 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using UnityEngine.Serialization;
namespace Oculus.Interaction.HandPosing.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;
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,119 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Input
{
public class Controller :
DataModifier<ControllerDataAsset, ControllerDataSourceConfig>,
IController
{
public Handedness Handedness => Config.Handedness;
public bool IsConnected
{
get
{
var currentData = GetData();
return currentData.IsDataValid && currentData.IsConnected;
}
}
public bool IsPoseValid
{
get
{
var currentData = GetData();
return currentData.IsDataValid &&
currentData.RootPoseOrigin != PoseOrigin.None;
}
}
public bool IsPointerPoseValid
{
get
{
var currentData = GetData();
return currentData.IsDataValid &&
currentData.PointerPoseOrigin != PoseOrigin.None;
}
}
public event Action ControllerUpdated = delegate { };
public bool IsButtonUsageAnyActive(ControllerButtonUsage buttonUsage)
{
var currentData = GetData();
return
currentData.IsDataValid &&
(buttonUsage & currentData.ButtonUsageMask) != 0;
}
public bool IsButtonUsageAllActive(ControllerButtonUsage buttonUsage)
{
var currentData = GetData();
return currentData.IsDataValid &&
(buttonUsage & currentData.ButtonUsageMask) == buttonUsage;
}
/// <summary>
/// Retrieves the current controller pose, in world space.
/// </summary>
/// <param name="pose">Set to current pose if `IsPoseValid`; Pose.identity otherwise</param>
/// <returns>Value of `IsPoseValid`</returns>
public bool TryGetPose(out Pose pose)
{
if (!IsPoseValid)
{
pose = Pose.identity;
return false;
}
pose = Config.TrackingToWorldTransformer.ToWorldPose(GetData().RootPose);
return true;
}
/// <summary>
/// Retrieves the current controller pointer pose, in world space.
/// </summary>
/// <param name="pose">Set to current pose if `IsPoseValid`; Pose.identity otherwise</param>
/// <returns>Value of `IsPoseValid`</returns>
public bool TryGetPointerPose(out Pose pose)
{
if (!IsPointerPoseValid)
{
pose = Pose.identity;
return false;
}
pose = Config.TrackingToWorldTransformer.ToWorldPose(GetData().PointerPose);
return true;
}
public override void MarkInputDataRequiresUpdate()
{
base.MarkInputDataRequiresUpdate();
if (Started)
{
ControllerUpdated();
}
}
protected override void Apply(ControllerDataAsset data)
{
// Default implementation does nothing, to allow instantiation of this modifier directly
}
}
}

View File

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

View File

@@ -0,0 +1,47 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Input
{
[Serializable]
public class ControllerDataAsset : ICopyFrom<ControllerDataAsset>
{
public bool IsDataValid;
public bool IsConnected;
public bool IsTracked;
public ControllerButtonUsage ButtonUsageMask;
public Pose RootPose;
public PoseOrigin RootPoseOrigin;
public Pose PointerPose;
public PoseOrigin PointerPoseOrigin;
public void CopyFrom(ControllerDataAsset source)
{
IsDataValid = source.IsDataValid;
IsConnected = source.IsConnected;
IsTracked = source.IsTracked;
CopyPosesAndStateFrom(source);
}
public void CopyPosesAndStateFrom(ControllerDataAsset source)
{
ButtonUsageMask = source.ButtonUsageMask;
RootPose = source.RootPose;
RootPoseOrigin = source.RootPoseOrigin;
PointerPose = source.PointerPose;
PointerPoseOrigin = source.PointerPoseOrigin;
}
}
}

View File

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

View File

@@ -0,0 +1,24 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.
************************************************************************************/
namespace Oculus.Interaction.Input
{
/// <summary>
/// A set of constants that are passed to each child of a Controller modifier tree from the root DataSource.
/// </summary>
public class ControllerDataSourceConfig
{
public Handedness Handedness { get; set; }
public ITrackingToWorldTransformer TrackingToWorldTransformer { get; set; }
public IDataSource<HmdDataAsset, HmdDataSourceConfig> HmdData { get; set; }
}
}

View File

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

View File

@@ -0,0 +1,33 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
namespace Oculus.Interaction.Input
{
// Enum containing all values of Unity.XR.CommonUsage.
[Flags]
public enum ControllerButtonUsage
{
None = 0,
PrimaryButton = 1 << 0,
PrimaryTouch = 1 << 1,
SecondaryButton = 1 << 2,
SecondaryTouch = 1 << 3,
GripButton = 1 << 4,
TriggerButton = 1 << 5,
MenuButton = 1 << 6,
Primary2DAxisClick = 1 << 7,
Primary2DAxisTouch = 1 << 8,
Thumbrest = 1 << 9,
}
}

View File

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

View File

@@ -0,0 +1,86 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Assertions;
namespace Oculus.Interaction.Input
{
/// <summary>
/// ControllerRef is a utility component that delegates all of its IController implementation
/// to the provided Controller object.
/// </summary>
public class ControllerRef : MonoBehaviour, IController, IActiveState
{
[SerializeField, Interface(typeof(IController))]
private MonoBehaviour _controller;
private IController Controller;
protected virtual void Awake()
{
Controller = _controller as IController;
}
protected virtual void Start()
{
Assert.IsNotNull(Controller);
}
public Handedness Handedness => Controller.Handedness;
public bool IsConnected => Controller.IsConnected;
public bool IsPoseValid => Controller.IsPoseValid;
public event Action ControllerUpdated
{
add => Controller.ControllerUpdated += value;
remove => Controller.ControllerUpdated -= value;
}
public bool Active => IsConnected;
public bool TryGetPose(out Pose pose)
{
return Controller.TryGetPose(out pose);
}
public bool TryGetPointerPose(out Pose pose)
{
return Controller.TryGetPointerPose(out pose);
}
public bool IsButtonUsageAnyActive(ControllerButtonUsage buttonUsage)
{
return Controller.IsButtonUsageAnyActive(buttonUsage);
}
public bool IsButtonUsageAllActive(ControllerButtonUsage buttonUsage)
{
return Controller.IsButtonUsageAllActive(buttonUsage);
}
#region Inject
public void InjectAllControllerRef(IController controller)
{
InjectController(controller);
}
public void InjectController(IController controller)
{
_controller = controller as MonoBehaviour;
Controller = controller;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,29 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Input
{
public interface IController
{
Handedness Handedness { get; }
bool IsConnected { get; }
bool IsPoseValid { get; }
bool TryGetPose(out Pose pose);
bool TryGetPointerPose(out Pose pose);
bool IsButtonUsageAnyActive(ControllerButtonUsage buttonUsage);
bool IsButtonUsageAllActive(ControllerButtonUsage buttonUsage);
event Action ControllerUpdated;
}
}

View File

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

View File

@@ -0,0 +1,19 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.
************************************************************************************/
namespace Oculus.Interaction.Input
{
public interface IControllerDataModifier
{
void Apply(ControllerDataAsset controllerDataAsset, Handedness handedness);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b392af6b92984f2887f80975b07141ba
timeCreated: 1629344165

View File

@@ -0,0 +1,118 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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;
using UnityEngine.Assertions;
namespace Oculus.Interaction.Input
{
public abstract class
DataModifier<TData, TConfig> : DataSource<TData, TConfig>
where TData : class, ICopyFrom<TData>, new()
{
[Header("Data Modifier")]
[SerializeField, Interface(nameof(_modifyDataFromSource))]
protected MonoBehaviour _iModifyDataFromSourceMono;
private IDataSource<TData, TConfig> _modifyDataFromSource;
[SerializeField]
[Tooltip("If this is false, then this modifier will simply pass through " +
"data without performing any modification. This saves on memory " +
"and computation")]
private bool _applyModifier = true;
private static TData InvalidAsset { get; } = new TData();
private TData _thisDataAsset;
private TData _currentDataAsset = InvalidAsset;
private TConfig _configCache;
protected override TData DataAsset => _currentDataAsset;
public virtual IDataSource<TData, TConfig> ModifyDataFromSource => _modifyDataFromSource == null
? (_modifyDataFromSource = _iModifyDataFromSourceMono as IDataSource<TData, TConfig>)
: _modifyDataFromSource;
public override int CurrentDataVersion
{
get
{
return _applyModifier
? base.CurrentDataVersion
: ModifyDataFromSource.CurrentDataVersion;
}
}
public void ResetSources(IDataSource<TData, TConfig> modifyDataFromSource, IDataSource updateAfter, UpdateModeFlags updateMode)
{
ResetUpdateAfter(updateAfter, updateMode);
_modifyDataFromSource = modifyDataFromSource;
_currentDataAsset = InvalidAsset;
_configCache = default;
}
protected override void UpdateData()
{
if (_applyModifier)
{
if (_thisDataAsset == null)
{
_thisDataAsset = new TData();
}
_thisDataAsset.CopyFrom(ModifyDataFromSource.GetData());
_currentDataAsset = _thisDataAsset;
Apply(_currentDataAsset);
}
else
{
_currentDataAsset = ModifyDataFromSource.GetData();
}
}
protected abstract void Apply(TData data);
protected override void Start()
{
base.Start();
Assert.IsNotNull(ModifyDataFromSource);
}
public override TConfig Config
{
get
{
return _configCache != null
? _configCache
: (_configCache = ModifyDataFromSource.Config);
}
}
#region Inject
public void InjectAllDataModifier(UpdateModeFlags updateMode, IDataSource updateAfter, IDataSource<TData, TConfig> modifyDataFromSource, bool applyModifier)
{
base.InjectAllDataSource(updateMode, updateAfter);
InjectModifyDataFromSource(modifyDataFromSource);
InjectApplyModifier(applyModifier);
}
public void InjectModifyDataFromSource(IDataSource<TData, TConfig> modifyDataFromSource)
{
_modifyDataFromSource = modifyDataFromSource;
}
public void InjectApplyModifier(bool applyModifier)
{
_applyModifier = applyModifier;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 12efd6a9760b4bcaafd9cefbe1db0bad
timeCreated: 1630526111

View File

@@ -0,0 +1,202 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction.Input
{
public interface IDataSource
{
int CurrentDataVersion { get; }
void MarkInputDataRequiresUpdate();
event Action InputDataAvailable;
}
public interface IDataSource<TData, TConfig> : IDataSource
{
TData GetData();
TConfig Config { get; }
}
public abstract class DataSource<TData, TConfig> : MonoBehaviour, IDataSource<TData, TConfig>
where TData : class, ICopyFrom<TData>, new()
{
public bool Started { get; private set; }
protected bool _started = false;
private bool _requiresUpdate = true;
[Flags]
public enum UpdateModeFlags
{
Manual = 0,
UnityUpdate = 1 << 0,
UnityFixedUpdate = 1 << 1,
UnityLateUpdate = 1 << 2,
AfterPreviousStep = 1 << 3
}
[Header("Update")]
[SerializeField]
private UpdateModeFlags _updateMode;
public UpdateModeFlags UpdateMode => _updateMode;
[SerializeField, Interface(typeof(IDataSource)), Optional]
private MonoBehaviour _updateAfter;
private IDataSource UpdateAfter;
private int _currentDataVersion;
protected bool UpdateModeAfterPrevious => (_updateMode & UpdateModeFlags.AfterPreviousStep) != 0;
// Notifies that new data is available for query via GetData() method.
// Do not use this event if you are reading data from a `Oculus.Interaction.Input.Hand` object,
// instead, use the `Updated` event on that class.
public event Action InputDataAvailable = delegate { };
public virtual int CurrentDataVersion => _currentDataVersion;
#region Unity Lifecycle
protected virtual void Start()
{
this.BeginStart(ref _started);
if (_updateAfter != null)
{
UpdateAfter = _updateAfter as IDataSource;
Assert.IsNotNull(UpdateAfter);
}
Started = true;
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
if (Started && UpdateModeAfterPrevious && UpdateAfter != null)
{
UpdateAfter.InputDataAvailable += MarkInputDataRequiresUpdate;
}
}
}
protected virtual void OnDisable()
{
if (_started)
{
if (UpdateAfter != null)
{
UpdateAfter.InputDataAvailable -= MarkInputDataRequiresUpdate;
}
}
}
protected virtual void Update()
{
if ((_updateMode & UpdateModeFlags.UnityUpdate) != 0)
{
MarkInputDataRequiresUpdate();
}
}
protected virtual void FixedUpdate()
{
if ((_updateMode & UpdateModeFlags.UnityFixedUpdate) != 0)
{
MarkInputDataRequiresUpdate();
}
}
protected virtual void LateUpdate()
{
if ((_updateMode & UpdateModeFlags.UnityLateUpdate) != 0)
{
MarkInputDataRequiresUpdate();
}
}
#endregion
protected void ResetUpdateAfter(IDataSource updateAfter, UpdateModeFlags updateMode)
{
bool wasActive = isActiveAndEnabled;
if (isActiveAndEnabled) { OnDisable(); }
_updateMode = updateMode;
UpdateAfter = updateAfter;
_requiresUpdate = true;
_currentDataVersion += 1;
if (wasActive) { OnEnable(); }
}
public TData GetData()
{
if (RequiresUpdate())
{
UpdateData();
_requiresUpdate = false;
}
return DataAsset;
}
protected bool RequiresUpdate()
{
return Started && _requiresUpdate && isActiveAndEnabled;
}
/// <summary>
/// Marks the DataAsset stored as outdated, which means it will be
/// re-processed JIT during the next call to GetData.
/// </summary>
public virtual void MarkInputDataRequiresUpdate()
{
_requiresUpdate = true;
_currentDataVersion += 1;
InputDataAvailable();
}
protected abstract void UpdateData();
/// <summary>
/// Returns the current DataAsset, without performing any updates.
/// </summary>
/// <returns>
/// Null if no call to GetData has been made since this data source was initialized.
/// </returns>
protected abstract TData DataAsset { get; }
public abstract TConfig Config { get; }
#region Inject
public void InjectAllDataSource(UpdateModeFlags updateMode, IDataSource updateAfter)
{
InjectUpdateMode(updateMode);
InjectUpdateAfter(updateAfter);
}
public void InjectUpdateMode(UpdateModeFlags updateMode)
{
_updateMode = updateMode;
}
public void InjectUpdateAfter(IDataSource updateAfter)
{
_updateAfter = updateAfter as MonoBehaviour;
UpdateAfter = updateAfter;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5e3eaa7f7e074674929b88928616f063
timeCreated: 1630527498

View File

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

View File

@@ -0,0 +1,53 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Input
{
public class Hmd : DataModifier<HmdDataAsset, HmdDataSourceConfig>, IHmd
{
public ITrackingToWorldTransformer TrackingToWorldTransformer =>
Config.TrackingToWorldTransformer;
public event Action HmdUpdated = delegate { };
protected override void Apply(HmdDataAsset data)
{
// Default implementation does nothing, to allow instantiation of this modifier directly
}
public override void MarkInputDataRequiresUpdate()
{
base.MarkInputDataRequiresUpdate();
if (Started)
{
HmdUpdated();
}
}
public bool GetRootPose(out Pose pose)
{
var currentData = GetData();
if (!currentData.IsTracked)
{
pose = Pose.identity;
return false;
}
pose = TrackingToWorldTransformer.ToWorldPose(currentData.Root);
return true;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2606bf2f0c914a7aba4390f29ba2eb6e
timeCreated: 1629334585

View File

@@ -0,0 +1,32 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Input
{
[Serializable]
public class HmdDataAsset : ICopyFrom<HmdDataAsset>
{
public Pose Root;
public bool IsTracked;
public int FrameId;
public void CopyFrom(HmdDataAsset source)
{
Root = source.Root;
IsTracked = source.IsTracked;
FrameId = source.FrameId;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 332eb9fb3feb493aa2632f2739bf3d41
timeCreated: 1629321466

View File

@@ -0,0 +1,22 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.
************************************************************************************/
namespace Oculus.Interaction.Input
{
/// <summary>
/// A set of constants that are passed to each child of a Hand modifier tree from the root DataSource.
/// </summary>
public class HmdDataSourceConfig
{
public ITrackingToWorldTransformer TrackingToWorldTransformer { get; set; }
}
}

View File

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

View File

@@ -0,0 +1,62 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Assertions;
namespace Oculus.Interaction.Input
{
/// <summary>
/// A set of constants that are passed to each child of a Hand modifier tree from the root DataSource.
/// </summary>
public class HmdRef : MonoBehaviour, IHmd
{
[SerializeField, Interface(typeof(Hmd))]
private MonoBehaviour _hmd;
private IHmd Hmd;
public event Action HmdUpdated
{
add => Hmd.HmdUpdated += value;
remove => Hmd.HmdUpdated -= value;
}
protected virtual void Awake()
{
Hmd = _hmd as IHmd;
}
protected virtual void Start()
{
Assert.IsNotNull(Hmd);
}
public bool GetRootPose(out Pose pose)
{
return Hmd.GetRootPose(out pose);
}
#region Inject
public void InjectAllHmdRef(IHmd hmd)
{
InjectHmd(hmd);
}
public void InjectHmd(IHmd hmd)
{
_hmd = hmd as MonoBehaviour;
Hmd = hmd;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,23 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Input
{
public interface IHmd
{
bool GetRootPose(out Pose pose);
event Action HmdUpdated;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities 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.Input
{
public class DummyDataModifier : Hand
{
public Vector3 offset;
public float animationTime;
#region IHandInputDataModifier Implementation
protected override void Apply(HandDataAsset handDataAsset)
{
if (!handDataAsset.IsTracked)
{
return;
}
var interpolant = Mathf.Sin(Mathf.PI * 2.0f * (Time.time % animationTime) / animationTime) * 0.5f + 0.5f;
handDataAsset.Root.position = handDataAsset.Root.position + interpolant * offset + offset * -0.5f;
ref var joint = ref handDataAsset.Joints[(int)HandJointId.HandIndex1];
var rot = Quaternion.AngleAxis(interpolant * 90 - 45, Vector3.forward);
joint = joint * rot;
}
#endregion
}
}

Some files were not shown because too many files have changed in this diff Show More